| @@ -1,5 +0,0 @@ | |||||
| .DS_Store | |||||
| __pycache__ | |||||
| # my venv | |||||
| p | |||||
| @@ -1,115 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2009 John-Mark Gurney <jmg@funkthat.com> | |||||
| '''MPEG-TS clip''' | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| import bisect | |||||
| import email | |||||
| import os.path | |||||
| from DIDLLite import VideoItem, Resource | |||||
| from FSStorage import registerklassfun | |||||
| from twisted.web import static | |||||
| class ClipProxyFile: | |||||
| def __init__(self, f, p): | |||||
| self.fp = open(f) | |||||
| self.p = p | |||||
| self.pos = 0 | |||||
| lp = p[-1] | |||||
| self.size = lp[0] + lp[1] | |||||
| def read(self, s=None): | |||||
| if s is None: | |||||
| s = self.size - self.pos | |||||
| p = bisect.bisect_right(self.p, (self.pos,)) | |||||
| if p > 0: | |||||
| p -= 1 | |||||
| # We might be able to do this with proper construction of | |||||
| # self.p, but this is easier. | |||||
| r = [] | |||||
| fp = self.fp | |||||
| records = iter(self.p[p:]) | |||||
| while s: | |||||
| rec = next(records) | |||||
| diff = self.pos - rec[0] | |||||
| rlen = min(s, rec[1] - diff) | |||||
| fp.seek(rec[2] + diff) | |||||
| r.append(fp.read(rlen)) | |||||
| s -= rlen | |||||
| self.pos += rlen | |||||
| return ''.join(r) | |||||
| def close(self): | |||||
| self.fp.close() | |||||
| def seek(self, p, d=0): | |||||
| assert d == 0 | |||||
| self.pos = p | |||||
| if self.pos > self.size: | |||||
| self.pos = self.size | |||||
| def tell(self): | |||||
| return self.pos | |||||
| class ClipProxy(static.File): | |||||
| isLeaf = True | |||||
| synchronized = [ 'parsefile', 'getsize', 'open' ] | |||||
| def __init__(self, f, *args): | |||||
| self.__mtime = None | |||||
| static.File.__init__(self, f, *args) | |||||
| self.parsefile(self.path) | |||||
| def parsefile(self, f): | |||||
| if self.getModificationTime() == self.__mtime: | |||||
| return | |||||
| self.__mtime = self.getModificationTime() | |||||
| i = email.message_from_file(open(f)) | |||||
| self.origfile = i['file'] | |||||
| self.date = eval(i['datetuple'], { '__builtins__': {} }) | |||||
| # date is UTC | |||||
| p = [ list(map(int, x.split())) for x in i.get_payload().split('\n') if x ] | |||||
| pos = 0 | |||||
| self.pos = par = [] | |||||
| for j in p: | |||||
| l = j[1] - j[0] + 188 | |||||
| par.append((pos, l, j[0])) | |||||
| pos += l | |||||
| self.__size = pos | |||||
| def getsize(self): | |||||
| return self.__size | |||||
| def open(self): | |||||
| return ClipProxyFile(self.origfile, self.pos) | |||||
| def restat(self): | |||||
| static.File.restat(self) | |||||
| self.parsefile(self.path) | |||||
| class ClipFile(VideoItem): | |||||
| def __init__(self, *args, **kwargs): | |||||
| file = kwargs.pop('file') | |||||
| mimetype = 'video/mpeg' | |||||
| kwargs['content'] = ClipProxy(file, mimetype) | |||||
| VideoItem.__init__(self, *args, **kwargs) | |||||
| self.url = '%s/%s' % (self.cd.urlbase, self.id) | |||||
| self.res = Resource(self.url, 'http-get:*:%s:*' % mimetype) | |||||
| def detectclipfile(origpath, fobj): | |||||
| path = os.path.basename(origpath) | |||||
| ext = os.path.splitext(path)[1] | |||||
| if ext == '.clip': | |||||
| return ClipFile, { 'file': origpath } | |||||
| return None, None | |||||
| registerklassfun(detectclipfile) | |||||
| @@ -1,33 +0,0 @@ | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| # Copyright 2005, Tim Potter <tpot@samba.org> | |||||
| # Connection Manager service | |||||
| from twisted.python import log | |||||
| from twisted.web import resource, static, soap | |||||
| from upnp import UPnPPublisher | |||||
| class ConnectionManagerControl(UPnPPublisher): | |||||
| def soap_GetProtocolInfo(self, *args, **kwargs): | |||||
| log.msg('GetProtocolInfo(%s, %s)' % (repr(args), repr(kwargs))) | |||||
| return { 'Source': 'http-get:*:*:*', 'Sink': '' } | |||||
| def soap_PrepareForConnection(self, *args, **kwargs): | |||||
| log.msg('PrepareForConnection(%s, %s)' % (repr(args), repr(kwargs))) | |||||
| def soap_ConnectionComplete(self, *args, **kwargs): | |||||
| log.msg('ConnectionComplete(%s, %s)' % (repr(args), repr(kwargs))) | |||||
| def soap_GetCurrentConnectionIDs(self, *args, **kwargs): | |||||
| log.msg('GetCurrentConnectionIDs(%s, %s)' % (repr(args), repr(kwargs))) | |||||
| def soap_GetCurrentConnectionInfo(self, *args, **kwargs): | |||||
| log.msg('GetProtocolInfo(%s, %s)' % (repr(args), repr(kwargs))) | |||||
| class ConnectionManagerServer(resource.Resource): | |||||
| def __init__(self): | |||||
| resource.Resource.__init__(self) | |||||
| self.putChild(b'scpd.xml', static.File('connection-manager-scpd.xml')) | |||||
| self.putChild(b'control', ConnectionManagerControl()) | |||||
| @@ -1,351 +0,0 @@ | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| # Copyright 2005, Tim Potter <tpot@samba.org> | |||||
| # Copyright 2006-2008 John-Mark Gurney <jmg@funkthat.com> | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| # | |||||
| # This module implements the Content Directory Service (CDS) service | |||||
| # type as documented in the ContentDirectory:1 Service Template | |||||
| # Version 1.01 | |||||
| # | |||||
| # | |||||
| # TODO: Figure out a nicer pattern for debugging soap server calls as | |||||
| # twisted swallows the tracebacks. At the moment I'm going: | |||||
| # | |||||
| # try: | |||||
| # .... | |||||
| # except: | |||||
| # traceback.print_exc(file = log.logfile) | |||||
| # | |||||
| reqname = 'requests' | |||||
| from twisted.python import log | |||||
| from twisted.web import resource, static | |||||
| from xml.etree.ElementTree import Element, SubElement, tostring | |||||
| from upnp import UPnPPublisher, errorCode | |||||
| from DIDLLite import DIDLElement, Container, Movie, Resource, MusicTrack | |||||
| from twisted.internet import defer, threads | |||||
| from twisted.python import failure | |||||
| import debug | |||||
| import traceback | |||||
| from urllib.parse import quote | |||||
| class doRecall(defer.Deferred): | |||||
| '''A class that will upon any callback from the Deferred object passed | |||||
| in, recall fun(*args, **kwargs), just as if a maybeDeferred has been | |||||
| processed. | |||||
| The idea is to let something deeper called by something sync "abort" | |||||
| the call until it's ready, and then reattempt. This isn't the best | |||||
| method as we throw away work, but it can be easier to implement. | |||||
| Example: | |||||
| def wrapper(five): | |||||
| try: | |||||
| return doacall(five) | |||||
| except defer.Deferred, x: | |||||
| return doRecallgen(x, wrapper, five) | |||||
| If doacall works, everything is fine, but if a Deferred object is | |||||
| raised, we put it in a doRecall class and return the deferred object | |||||
| generated by doRecall.''' | |||||
| def __init__(self, argdef, fun, *args, **kwargs): | |||||
| self.fun = fun | |||||
| self.args = args | |||||
| self.kwargs = kwargs | |||||
| self.defer = defer.Deferred() | |||||
| argdef.addCallback(self._done) | |||||
| def _done(self, *args, **kwargs): | |||||
| ret = self.fun(*self.args, **self.kwargs) | |||||
| if isinstance(ret, failure.Failure): | |||||
| self.defer.errback(ret) | |||||
| elif isinstance(ret, defer.Deferred): | |||||
| # We are fruther delayed, continue. | |||||
| ret.addCallback(self._done) | |||||
| else: | |||||
| self.defer.callback(ret) | |||||
| @staticmethod | |||||
| def wrapper(fun, *args, **kwargs): | |||||
| try: | |||||
| return fun(*args, **kwargs) | |||||
| except defer.Deferred as x: | |||||
| return doRecallgen(x, fun, *args, **kwargs) | |||||
| def doRecallgen(defer, fun, *args, **kwargs): | |||||
| i = doRecall(defer, fun, *args, **kwargs) | |||||
| return i.defer | |||||
| class ContentDirectoryControl(UPnPPublisher, dict): | |||||
| """This class implements the CDS actions over SOAP.""" | |||||
| namespace = 'urn:schemas-upnp-org:service:ContentDirectory:1' | |||||
| updateID = property(lambda x: x['0'].updateID) | |||||
| urlbase = property(lambda x: x._urlbase) | |||||
| def getnextID(self): | |||||
| ret = str(self.nextID) | |||||
| self.nextID += 1 | |||||
| return ret | |||||
| def addContainer(self, parent, title, klass = Container, *args, **kwargs): | |||||
| ret = self.addObject(parent, klass, title, *args, **kwargs) | |||||
| if ret is None: | |||||
| return | |||||
| self.children[ret] = self[ret] | |||||
| return ret | |||||
| def addItem(self, parent, klass, title, *args, **kwargs): | |||||
| if issubclass(klass, Container): | |||||
| return self.addContainer(parent, title, klass, *args, **kwargs) | |||||
| else: | |||||
| return self.addObject(parent, klass, title, *args, **kwargs) | |||||
| def addObject(self, parent, klass, title, *args, **kwargs): | |||||
| '''If the generated object (by klass) has an attribute content, it is installed into the web server.''' | |||||
| assert isinstance(self[parent], Container) | |||||
| nid = self.getnextID() | |||||
| try: | |||||
| i = klass(self, nid, parent, title, *args, **kwargs) | |||||
| except: | |||||
| import traceback | |||||
| traceback.print_exc() | |||||
| return | |||||
| if hasattr(i, 'content'): | |||||
| self.webbase.putChild(bytes(nid, 'ascii'), i.content) | |||||
| #log.msg('children:', `self.children[parent]`, `i`) | |||||
| self.children[parent].append(i) | |||||
| self[i.id] = i | |||||
| return i.id | |||||
| def __in__(self, k): | |||||
| return dict.__in__(self, k) | |||||
| def delItem(self, id): | |||||
| if id not in self: | |||||
| log.msg('already removed:', id) | |||||
| return | |||||
| #log.msg('removing:', id) | |||||
| if isinstance(self[id], Container): | |||||
| #log.msg('children:', Container.__repr__(self.children[id]), map(None, self.children[id])) | |||||
| while self.children[id]: | |||||
| self.delItem(self.children[id][0].id) | |||||
| assert len(self.children[id]) == 0 | |||||
| del self.children[id] | |||||
| # Remove from parent | |||||
| self.children[self[id].parentID].remove(self[id]) | |||||
| # Remove content | |||||
| if hasattr(self[id], 'content'): | |||||
| self.webbase.delEntity(id) | |||||
| del self[id] | |||||
| def getchildren(self, item): | |||||
| assert isinstance(self[item], Container) | |||||
| return self.children[item][:] | |||||
| def __init__(self, title, *args, **kwargs): | |||||
| debug.insertringbuf(reqname) | |||||
| super(ContentDirectoryControl, self).__init__(*args) | |||||
| self.webbase = kwargs['webbase'] | |||||
| self._urlbase = kwargs['urlbase'] | |||||
| del kwargs['webbase'], kwargs['urlbase'] | |||||
| fakeparent = '-1' | |||||
| self.nextID = 0 | |||||
| self.children = { fakeparent: []} | |||||
| self[fakeparent] = Container(None, None, '-1', 'fake') | |||||
| root = self.addContainer(fakeparent, title, **kwargs) | |||||
| assert root == '0' | |||||
| del self[fakeparent] | |||||
| del self.children[fakeparent] | |||||
| # Required actions | |||||
| def soap_GetSearchCapabilities(self, *args, **kwargs): | |||||
| """Required: Return the searching capabilities supported by the device.""" | |||||
| log.msg('GetSearchCapabilities()') | |||||
| return { 'SearchCaps': '' } | |||||
| def soap_GetSortCapabilities(self, *args, **kwargs): | |||||
| """Required: Return the CSV list of meta-data tags that can be used in | |||||
| sortCriteria.""" | |||||
| log.msg('GetSortCapabilities()') | |||||
| return { 'SortCaps': '' } | |||||
| def soap_GetSystemUpdateID(self, *args, **kwargs): | |||||
| """Required: Return the current value of state variable SystemUpdateID.""" | |||||
| return { 'Id': self.updateID } | |||||
| BrowseFlags = ('BrowseMetaData', 'BrowseDirectChildren') | |||||
| def soap_Browse(self, *args): | |||||
| l = {} | |||||
| debug.appendnamespace(reqname, l) | |||||
| if args[0] in self: | |||||
| l['object'] = self[args[0]] | |||||
| l['query'] = 'Browse(ObjectID=%s, BrowseFlags=%s, Filter=%s, ' \ | |||||
| 'StartingIndex=%s RequestedCount=%s SortCriteria=%s)' % \ | |||||
| tuple(map(repr, args)) | |||||
| def setresp(r): | |||||
| l['response'] = r | |||||
| return r | |||||
| return threads.deferToThread(self.thereal_soap_Browse, | |||||
| *args).addCallback(setresp) | |||||
| def thereal_soap_Browse(self, *args): | |||||
| """Required: Incrementally browse the native heirachy of the Content | |||||
| Directory objects exposed by the Content Directory Service.""" | |||||
| (ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount, | |||||
| SortCriteria) = args | |||||
| StartingIndex = int(StartingIndex) | |||||
| RequestedCount = int(RequestedCount) | |||||
| didl = DIDLElement() | |||||
| # return error code if we don't exist anymore | |||||
| if ObjectID not in self: | |||||
| raise errorCode(701) | |||||
| # check to see if object needs to be updated | |||||
| self[ObjectID].checkUpdate() | |||||
| # make sure we still exist, we could of deleted ourself | |||||
| if ObjectID not in self: | |||||
| raise errorCode(701) | |||||
| if BrowseFlag == 'BrowseDirectChildren': | |||||
| ch = self.getchildren(ObjectID)[StartingIndex: StartingIndex + RequestedCount] | |||||
| for i in ch: | |||||
| if i.needupdate: | |||||
| i.checkUpdate() | |||||
| didl.addItem(i) | |||||
| total = len(self.getchildren(ObjectID)) | |||||
| else: | |||||
| didl.addItem(self[ObjectID]) | |||||
| total = 1 | |||||
| r = { 'Result': didl.toString(), 'TotalMatches': total, | |||||
| 'NumberReturned': didl.numItems(), } | |||||
| if hasattr(self[ObjectID], 'updateID'): | |||||
| r['UpdateID'] = self[ObjectID].updateID | |||||
| else: | |||||
| r['UpdateID'] = self.updateID | |||||
| return r | |||||
| # Optional actions | |||||
| def soap_Search(self, *args, **kwargs): | |||||
| """Search for objects that match some search criteria.""" | |||||
| (ContainerID, SearchCriteria, Filter, StartingIndex, | |||||
| RequestedCount, SortCriteria) = args | |||||
| log.msg('Search(ContainerID=%s, SearchCriteria=%s, Filter=%s, ' \ | |||||
| 'StartingIndex=%s, RequestedCount=%s, SortCriteria=%s)' % | |||||
| (repr(ContainerID), repr(SearchCriteria), repr(Filter), | |||||
| repr(StartingIndex), repr(RequestedCount), repr(SortCriteria))) | |||||
| def soap_CreateObject(self, *args, **kwargs): | |||||
| """Create a new object.""" | |||||
| (ContainerID, Elements) = args | |||||
| log.msg('CreateObject(ContainerID=%s, Elements=%s)' % | |||||
| (repr(ContainerID), repr(Elements))) | |||||
| def soap_DestroyObject(self, *args, **kwargs): | |||||
| """Destroy the specified object.""" | |||||
| (ObjectID) = args | |||||
| log.msg('DestroyObject(ObjectID=%s)' % repr(ObjectID)) | |||||
| def soap_UpdateObject(self, *args, **kwargs): | |||||
| """Modify, delete or insert object metadata.""" | |||||
| (ObjectID, CurrentTagValue, NewTagValue) = args | |||||
| log.msg('UpdateObject(ObjectID=%s, CurrentTagValue=%s, ' \ | |||||
| 'NewTagValue=%s)' % (repr(ObjectID), repr(CurrentTagValue), | |||||
| repr(NewTagValue))) | |||||
| def soap_ImportResource(self, *args, **kwargs): | |||||
| """Transfer a file from a remote source to a local | |||||
| destination in the Content Directory Service.""" | |||||
| (SourceURI, DestinationURI) = args | |||||
| log.msg('ImportResource(SourceURI=%s, DestinationURI=%s)' % | |||||
| (repr(SourceURI), repr(DestinationURI))) | |||||
| def soap_ExportResource(self, *args, **kwargs): | |||||
| """Transfer a file from a local source to a remote | |||||
| destination.""" | |||||
| (SourceURI, DestinationURI) = args | |||||
| log.msg('ExportResource(SourceURI=%s, DestinationURI=%s)' % | |||||
| (repr(SourceURI), repr(DestinationURI))) | |||||
| def soap_StopTransferResource(self, *args, **kwargs): | |||||
| """Stop a file transfer initiated by ImportResource or | |||||
| ExportResource.""" | |||||
| (TransferID) = args | |||||
| log.msg('StopTransferResource(TransferID=%s)' % TransferID) | |||||
| def soap_GetTransferProgress(self, *args, **kwargs): | |||||
| """Query the progress of a file transfer initiated by | |||||
| an ImportResource or ExportResource action.""" | |||||
| (TransferID, TransferStatus, TransferLength, TransferTotal) = args | |||||
| log.msg('GetTransferProgress(TransferID=%s, TransferStatus=%s, ' \ | |||||
| 'TransferLength=%s, TransferTotal=%s)' % | |||||
| (repr(TransferId), repr(TransferStatus), repr(TransferLength), | |||||
| repr(TransferTotal))) | |||||
| def soap_DeleteResource(self, *args, **kwargs): | |||||
| """Delete a specified resource.""" | |||||
| (ResourceURI) = args | |||||
| log.msg('DeleteResource(ResourceURI=%s)' % repr(ResourceURI)) | |||||
| def soap_CreateReference(self, *args, **kwargs): | |||||
| """Create a reference to an existing object.""" | |||||
| (ContainerID, ObjectID) = args | |||||
| log.msg('CreateReference(ContainerID=%s, ObjectID=%s)' % | |||||
| (repr(ContainerID), repr(ObjectID))) | |||||
| def __repr__(self): | |||||
| return '<ContentDirectoryControl: cnt: %d, urlbase: %s, nextID: %d>' % (len(self), repr(self.urlbase), self.nextID) | |||||
| class ContentDirectoryServer(resource.Resource): | |||||
| def __init__(self, title, *args, **kwargs): | |||||
| resource.Resource.__init__(self) | |||||
| self.putChild(b'scpd.xml', static.File('content-directory-scpd.xml')) | |||||
| self.control = ContentDirectoryControl(title, *args, **kwargs) | |||||
| self.putChild(b'control', self.control) | |||||
| @@ -1,601 +0,0 @@ | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| # Copyright 2005, Tim Potter <tpot@samba.org> | |||||
| # Copyright 2006-2009 John-Mark Gurney <jmg@funkthat.com> | |||||
| __version__ = '$Change: 1665 $' | |||||
| # $Id: //depot/python/pymeds/main/DIDLLite.py#32 $ | |||||
| import functools | |||||
| import itertools | |||||
| import unittest | |||||
| import et | |||||
| for i in [ 'Element', 'SubElement', 'tostring', ]: | |||||
| locals()[i] = getattr(et.ET, i) | |||||
| class Resource(object): | |||||
| """An object representing a resource.""" | |||||
| validattrs = { | |||||
| 'protocolinfo': 'protocolInfo', | |||||
| 'importuri': 'importUri', | |||||
| 'size': 'size', | |||||
| 'duration': 'duration', | |||||
| 'protection': 'protection', | |||||
| 'bitrate': 'bitrate', | |||||
| 'bitspersample': 'bitsPerSample', | |||||
| 'samplefrequency': 'sampleFrequence', | |||||
| 'nraudiochannels': 'nrAudioChannels', | |||||
| 'resolution': 'resolution', | |||||
| 'colordepth': 'colorDepth', | |||||
| 'tspec': 'tspec', | |||||
| 'alloweduse': 'allowedUse', | |||||
| 'validitystart': 'validityStart', | |||||
| 'validityend': 'validityEnd', | |||||
| 'remainingtime': 'remainingTime', | |||||
| 'usageinfo': 'usageInfo', | |||||
| 'rightsinfouri': 'rightsInfoURI', | |||||
| 'contentinfouri': 'contentInfoURI', | |||||
| 'recordquality': 'recordQuality', | |||||
| } | |||||
| def __init__(self, data, protocolInfo): | |||||
| object.__init__(self) | |||||
| # Use thses so setattr can be more simple | |||||
| object.__setattr__(self, 'data', data) | |||||
| object.__setattr__(self, 'attrs', {}) | |||||
| self.protocolInfo = protocolInfo | |||||
| def __getattr__(self, key): | |||||
| try: | |||||
| return self.attrs[key.lower()] | |||||
| except KeyError: | |||||
| raise AttributeError(key) | |||||
| def __setattr__(self, key, value): | |||||
| key = key.lower() | |||||
| assert key in self.validattrs | |||||
| self.attrs[key] = value | |||||
| def toElement(self): | |||||
| root = Element('res') | |||||
| root.text = self.data | |||||
| for i in self.attrs: | |||||
| attr = self.validattrs[i] | |||||
| value = self.attrs[i] | |||||
| funname = 'format_%s' % attr | |||||
| if hasattr(self, funname): | |||||
| value = getattr(self, funname)(value) | |||||
| else: | |||||
| value = str(value) | |||||
| assert isinstance(value, str), \ | |||||
| 'value is not a string: %s' % repr(value) | |||||
| root.attrib[attr] = value | |||||
| return root | |||||
| @staticmethod | |||||
| def format_duration(s): | |||||
| if isinstance(s, str): | |||||
| return s | |||||
| # assume it is a number | |||||
| s = abs(s) | |||||
| secs = int(s) | |||||
| frac = s - secs | |||||
| minutes, secs = divmod(secs, 60) | |||||
| hours, minutes = divmod(minutes, 60) | |||||
| if frac: | |||||
| frac = ('%.2f' % frac)[1:] | |||||
| else: | |||||
| frac = '' | |||||
| return '%d:%02d:%02d%s' % (hours, minutes, secs, frac) | |||||
| class ResourceList(list): | |||||
| '''Special class to not overwrite mimetypes that already exist.''' | |||||
| def __init__(self, *args, **kwargs): | |||||
| self._mt = {} | |||||
| list.__init__(self, *args, **kwargs) | |||||
| def append(self, k): | |||||
| assert isinstance(k, Resource) | |||||
| mt = k.protocolInfo.split(':')[2] | |||||
| if mt in self._mt: | |||||
| return | |||||
| list.append(self, k) | |||||
| self._mt[mt] = k | |||||
| class Object(object): | |||||
| """The root class of the entire content directory class heirachy.""" | |||||
| klass = 'object' | |||||
| _optionattrs = { | |||||
| 'creator': 'dc', | |||||
| 'writeStatus': 'upnp', | |||||
| 'artist': 'upnp', | |||||
| 'actor': 'upnp', | |||||
| 'author': 'upnp', | |||||
| 'producer': 'upnp', | |||||
| 'director': 'upnp', | |||||
| 'publisher': 'dc', | |||||
| 'contributor': 'dc', | |||||
| 'genre': 'upnp', | |||||
| 'album': 'upnp', | |||||
| 'playlist': 'upnp', | |||||
| 'albumArtURI': 'upnp', | |||||
| 'artistDiscographyURI': 'upnp', | |||||
| 'lyricsURI': 'upnp', | |||||
| 'relation': 'dc', | |||||
| 'storageMedium': 'upnp', | |||||
| 'description': 'dc', | |||||
| 'longDescription': 'upnp', | |||||
| 'icon': 'upnp', | |||||
| 'region': 'upnp', | |||||
| 'rights': 'dc', | |||||
| 'date': 'dc', | |||||
| 'language': 'dc', | |||||
| 'playbackCount': 'upnp', | |||||
| 'lastPlaybackTime': 'upnp', | |||||
| 'lastPlaybackPosition': 'upnp', | |||||
| 'recordedStartDateTime': 'upnp', | |||||
| 'recordedDuration': 'upnp', | |||||
| 'recordedDayOfWeek': 'upnp', | |||||
| 'srsRecordScheduleID': 'upnp', | |||||
| 'srsRecordTaskID': 'upnp', | |||||
| 'recordable': 'upnp', | |||||
| 'programTitle': 'upnp', | |||||
| 'seriesTitle': 'upnp', | |||||
| 'programID': 'upnp', | |||||
| 'seriesID': 'upnp', | |||||
| 'channelID': 'upnp', | |||||
| 'episodeCount': 'upnp', | |||||
| 'episodeNumber': 'upnp', | |||||
| 'programCode': 'upnp', | |||||
| 'rating': 'upnp', | |||||
| 'channelGroupName': 'upnp', | |||||
| 'callSign': 'upnp', | |||||
| 'networkAffiliation': 'upnp', | |||||
| 'serviceProvider': 'upnp', | |||||
| 'price': 'upnp', | |||||
| 'payPerView': 'upnp', | |||||
| 'epgProviderName': 'upnp', | |||||
| 'dateTimeRange': 'upnp', | |||||
| 'radioCallSign': 'upnp', | |||||
| 'radioStationID': 'upnp', | |||||
| 'radioBand': 'upnp', | |||||
| 'channelNr': 'upnp', | |||||
| 'channelName': 'upnp', | |||||
| 'scheduledStartTime': 'upnp', | |||||
| 'scheduledEndTime': 'upnp', | |||||
| 'signalStrength': 'upnp', | |||||
| 'signalLocked': 'upnp', | |||||
| 'tuned': 'upnp', | |||||
| 'neverPlayable': 'upnp', | |||||
| 'bookmarkID': 'upnp', | |||||
| 'bookmarkedObjectID': 'upnp', | |||||
| 'deviceUDN': 'upnp', | |||||
| 'stateVariableCollection': 'upnp', | |||||
| 'DVDRegionCode': 'upnp', | |||||
| 'originalTrackNumber': 'upnp', | |||||
| 'toc': 'upnp', | |||||
| 'userAnnoation': 'upnp', | |||||
| } | |||||
| res = None | |||||
| content = property(lambda x: x._content) | |||||
| needupdate = None # do we update before sending? (for res) | |||||
| def __init__(self, cd, id, parentID, title, restricted=False, | |||||
| creator=None, **kwargs): | |||||
| self.cd = cd | |||||
| self.id = id | |||||
| self.parentID = parentID | |||||
| self.title = title | |||||
| self.creator = creator | |||||
| if restricted: | |||||
| self.restricted = '1' | |||||
| else: | |||||
| self.restricted = '0' | |||||
| if 'content' in kwargs: | |||||
| self._content = kwargs.pop('content') | |||||
| for i in kwargs: | |||||
| if i not in self._optionattrs: | |||||
| raise TypeError('invalid keyword arg: %s' % repr(i)) | |||||
| setattr(self, i, kwargs[i]) | |||||
| def __cmp__(self, other): | |||||
| if not isinstance(other, self.__class__): | |||||
| return cmp(self.__class__.__name__, | |||||
| other.__class__.__name__) | |||||
| return cmp(self.id, other.id) | |||||
| def __repr__(self): | |||||
| cls = self.__class__ | |||||
| return '<%s.%s: id: %s, parent: %s, title: %s>' % \ | |||||
| (cls.__module__, cls.__name__, self.id, self.parentID, | |||||
| self.title) | |||||
| def checkUpdate(self): | |||||
| # It's tempting to call doUpdate here, but each object has to | |||||
| # decide that. | |||||
| pass | |||||
| def toElement(self): | |||||
| root = Element(self.elementName) | |||||
| root.attrib['id'] = self.id | |||||
| root.attrib['parentID'] = self.parentID | |||||
| SubElement(root, 'dc:title').text = self.title | |||||
| SubElement(root, 'upnp:class').text = self.klass | |||||
| root.attrib['restricted'] = self.restricted | |||||
| for i in (x for x in self.__dict__ if x in self._optionattrs): | |||||
| obj = getattr(self, i) | |||||
| if obj is None: | |||||
| continue | |||||
| SubElement(root, '%s:%s' % (self._optionattrs[i], | |||||
| i)).text = str(getattr(self, i)) | |||||
| if self.res is not None: | |||||
| try: | |||||
| resiter = iter(self.res) | |||||
| except TypeError as x: | |||||
| resiter = [ self.res ] | |||||
| for res in resiter: | |||||
| root.append(res.toElement()) | |||||
| return root | |||||
| def toString(self): | |||||
| return tostring(self.toElement()) | |||||
| class Item(Object): | |||||
| """A class used to represent atomic (non-container) content | |||||
| objects.""" | |||||
| klass = Object.klass + '.item' | |||||
| elementName = 'item' | |||||
| refID = None | |||||
| needupdate = True | |||||
| def doUpdate(self, child=False): | |||||
| # do NOT update parent container per 2.2.9 for | |||||
| # ContainerUpdateID changes | |||||
| # must be Container.didUpdate, otherwise we could update the | |||||
| # parent when we really just want to update the ID | |||||
| Container.didUpdate(self.cd[self.parentID]) | |||||
| def toElement(self): | |||||
| root = Object.toElement(self) | |||||
| if self.refID is not None: | |||||
| SubElement(root, 'refID').text = self.refID | |||||
| return root | |||||
| class ImageItem(Item): | |||||
| klass = Item.klass + '.imageItem' | |||||
| class Photo(ImageItem): | |||||
| klass = ImageItem.klass + '.photo' | |||||
| class AudioItem(Item): | |||||
| """A piece of content that when rendered generates some audio.""" | |||||
| klass = Item.klass + '.audioItem' | |||||
| class MusicTrack(AudioItem): | |||||
| """A discrete piece of audio that should be interpreted as music.""" | |||||
| klass = AudioItem.klass + '.musicTrack' | |||||
| class AudioBroadcast(AudioItem): | |||||
| klass = AudioItem.klass + '.audioBroadcast' | |||||
| class AudioBook(AudioItem): | |||||
| klass = AudioItem.klass + '.audioBook' | |||||
| class VideoItem(Item): | |||||
| klass = Item.klass + '.videoItem' | |||||
| class Movie(VideoItem): | |||||
| klass = VideoItem.klass + '.movie' | |||||
| class VideoBroadcast(VideoItem): | |||||
| klass = VideoItem.klass + '.videoBroadcast' | |||||
| class MusicVideoClip(VideoItem): | |||||
| klass = VideoItem.klass + '.musicVideoClip' | |||||
| class PlaylistItem(Item): | |||||
| klass = Item.klass + '.playlistItem' | |||||
| class TextItem(Item): | |||||
| klass = Item.klass + '.textItem' | |||||
| class Container(Object, list): | |||||
| """An object that can contain other objects.""" | |||||
| klass = Object.klass + '.container' | |||||
| elementName = 'container' | |||||
| childCount = property(lambda x: len(x)) | |||||
| searchable = None | |||||
| updateID = 0 | |||||
| needcontupdate = False | |||||
| _optionattrs = Object._optionattrs.copy() | |||||
| _optionattrs.update({ | |||||
| 'searchClass': 'upnp', | |||||
| 'createClass': 'upnp', | |||||
| 'storageTotal': 'upnp', | |||||
| 'storageUsed': 'upnp', | |||||
| 'storageFree': 'upnp', | |||||
| 'storageMaxPartition': 'upnp', | |||||
| }) | |||||
| def __init__(self, cd, id, parentID, title, **kwargs): | |||||
| Object.__init__(self, cd, id, parentID, title, **kwargs) | |||||
| list.__init__(self) | |||||
| self.doingUpdate = False | |||||
| self.needcontupdate = False | |||||
| self.oldchildren = {} | |||||
| def genCurrent(self): | |||||
| '''This function returns a tuple/list/generator that returns | |||||
| tuples of (id, name). name must match what is returned by | |||||
| genChildren. If name is used for the title directly, no | |||||
| override is necessary.''' | |||||
| return ((x.id, x.title) for x in self) | |||||
| def genChildren(self): | |||||
| '''This function returns a list or dict of names of all | |||||
| the current children.''' | |||||
| raise NotImplementedError | |||||
| def createObject(self, i, arg=None): | |||||
| '''This function returns the (class, name, *args, **kwargs) | |||||
| that will be passed to the addItem method of the | |||||
| ContentDirectory. arg will be passed the value of the dict | |||||
| keyed by i if genChildren is a dict.''' | |||||
| raise NotImplementedError | |||||
| def sort(self, fun=None): | |||||
| if fun is not None: | |||||
| return list.sort(self, key=functools.cmp_to_key(fun)) | |||||
| else: | |||||
| return list.sort(self, key=lambda x: x.title) | |||||
| def doUpdate(self): | |||||
| if self.doingUpdate: | |||||
| return | |||||
| self.doingUpdate = True | |||||
| self.needcontupdate = False | |||||
| # Get the current children | |||||
| children = self.genChildren() | |||||
| if isinstance(children, dict): | |||||
| oldchildren = self.oldchildren | |||||
| self.oldchildren = children | |||||
| isdict = True | |||||
| else: | |||||
| children = set(children) | |||||
| isdict = False | |||||
| # Delete the old object that no longer exists. | |||||
| # Make a mapping of current names to ids. | |||||
| names = {} | |||||
| print('i:', repr(self), repr(self.genCurrent), repr(self.__class__)) | |||||
| for id, i in tuple(self.genCurrent()): | |||||
| if i not in children: | |||||
| didupdate = True | |||||
| # delete | |||||
| print('del:', repr(id), repr(i)) | |||||
| self.cd.delItem(id) | |||||
| self.needcontupdate = True | |||||
| else: | |||||
| names[i] = id | |||||
| # Make sure that the existing objects don't need to be | |||||
| # updated. | |||||
| # Create any new objects that don't currently exist. | |||||
| for i in children: | |||||
| if i in names: | |||||
| if isdict: | |||||
| print('oc:', repr(oldchildren[i]), repr(children[i])) | |||||
| if oldchildren[i] == children[i]: | |||||
| continue | |||||
| # Delete the old and recreate | |||||
| self.cd.delItem(names[i]) | |||||
| self.needcontupdate = True | |||||
| else: | |||||
| # XXX - some sort of comparision? | |||||
| continue | |||||
| # new object | |||||
| if isdict: | |||||
| args = (children[i], ) | |||||
| else: | |||||
| args = () | |||||
| try: | |||||
| #print 'i:', `i`, `isdict`, `args`, `self` | |||||
| pass | |||||
| except UnicodeEncodeError: | |||||
| print('i decode error') | |||||
| klass, name, args, kwargs = self.createObject(i, *args) | |||||
| if klass is not None: | |||||
| self.cd.addItem(self.id, klass, name, *args, | |||||
| **kwargs) | |||||
| self.needcontupdate = True | |||||
| # sort our children | |||||
| self.sort() | |||||
| self.doingUpdate = False | |||||
| if self.needcontupdate: | |||||
| self.didUpdate() | |||||
| def didUpdate(self): | |||||
| if self.doingUpdate: | |||||
| self.needcontupdate = True | |||||
| return | |||||
| if self.id == '0': | |||||
| self.updateID = (self.updateID + 1) | |||||
| else: | |||||
| self.updateID = (self.updateID + 1) % (1 << 32) | |||||
| Container.didUpdate(self.cd['0']) | |||||
| def _addSet(self, e, items): | |||||
| if items is not None: | |||||
| if not isinstance(items, (list, tuple)): | |||||
| items = [ items ] | |||||
| for i in items: | |||||
| el = SubElement(root, e) | |||||
| el.text = i | |||||
| # XXX - how to specify? | |||||
| el.attrib['includeDerived'] = '1' | |||||
| def toElement(self): | |||||
| root = Object.toElement(self) | |||||
| # only include if we have children, it's possible we don't | |||||
| # have our children yet, and childCount is optional. | |||||
| if self.childCount: | |||||
| root.attrib['childCount'] = str(self.childCount) | |||||
| if self.searchable is not None: | |||||
| root.attrib['searchable'] = str(self.searchable) | |||||
| return root | |||||
| def __repr__(self): | |||||
| cls = self.__class__ | |||||
| return '<%s.%s: id: %s, parent: %s, title: %s, cnt: %d>' % \ | |||||
| (cls.__module__, cls.__name__, self.id, self.parentID, | |||||
| self.title, len(self)) | |||||
| class TestContainerObj(Container): | |||||
| def genChildren(self): | |||||
| return self._genchildren | |||||
| def createObject(self, name): | |||||
| return Object, name, (), {} | |||||
| class MockContainer(object): | |||||
| def __init__(self): | |||||
| self.itemiter = itertools.count(1) | |||||
| def addItem(self, *args, **kwargs): | |||||
| return next(self.itemiter) | |||||
| def __getitem__(self, id): | |||||
| return Container(None, '0', None, None) | |||||
| class TestContainer(unittest.TestCase): | |||||
| def xtest_container(self): | |||||
| cont = MockContainer() | |||||
| c = TestContainerObj(cont, None, None, None) | |||||
| self.assertEqual(len(tuple(c.genCurrent())), 0) | |||||
| c._genchildren = [ 'objb', 'obja' ] | |||||
| c.doUpdate() | |||||
| self.assertEqual(tuple(c.genCurrent()), ((1, 'obja'), (2, 'objb'))) | |||||
| class Person(Container): | |||||
| klass = Container.klass + '.person' | |||||
| class MusicArtist(Person): | |||||
| klass = Person.klass + '.musicArtist' | |||||
| class PlaylistContainer(Container): | |||||
| klass = Container.klass + '.playlistContainer' | |||||
| class Album(Container): | |||||
| klass = Container.klass + '.album' | |||||
| class MusicAlbum(Album): | |||||
| klass = Album.klass + '.musicAlbum' | |||||
| class PhotoAlbum(Album): | |||||
| klass = Album.klass + '.photoAlbum' | |||||
| class Genre(Container): | |||||
| klass = Container.klass + '.genre' | |||||
| class MusicGenre(Genre): | |||||
| klass = Genre.klass + '.musicGenre' | |||||
| class MovieGenre(Genre): | |||||
| klass = Genre.klass + '.movieGenre' | |||||
| class StorageSystem(Container): | |||||
| klass = Container.klass + '.storageSystem' | |||||
| storageTotal = -1 | |||||
| storageUsed = -1 | |||||
| storageFree = -1 | |||||
| storageMaxParition = -1 | |||||
| storageMedium = 'UNKNOWN' | |||||
| class StorageVolume(Container): | |||||
| klass = Container.klass + '.storageVolume' | |||||
| storageTotal = -1 | |||||
| storageUsed = -1 | |||||
| storageFree = -1 | |||||
| storageMedium = 'UNKNOWN' | |||||
| class StorageFolder(Container): | |||||
| klass = Container.klass + '.storageFolder' | |||||
| storageUsed = -1 | |||||
| class DIDLElement(Element): | |||||
| def __init__(self): | |||||
| super().__init__('DIDL-Lite', {}) | |||||
| self.attrib['xmlns'] = 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/' | |||||
| self.attrib['xmlns:dc'] = 'http://purl.org/dc/elements/1.1/' | |||||
| self.attrib['xmlns:upnp'] = 'urn:schemas-upnp-org:metadata-1-0/upnp' | |||||
| def addItem(self, item): | |||||
| self.append(item.toElement()) | |||||
| def numItems(self): | |||||
| return len(self) | |||||
| def toString(self): | |||||
| return tostring(self) | |||||
| if __name__ == '__main__': | |||||
| root = DIDLElement() | |||||
| root.addItem(Container(None, '0\Movie\\', '0\\', 'Movie')) | |||||
| root.addItem(Container(None, '0\Music\\', '0\\', 'Music')) | |||||
| root.addItem(Container(None, '0\Photo\\', '0\\', 'Photo')) | |||||
| root.addItem(Container(None, '0\OnlineMedia\\', '0\\', 'OnlineMedia')) | |||||
| print(tostring(root)) | |||||
| @@ -1,302 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2006-2009 John-Mark Gurney <jmg@funkthat.com> | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| ffmpeg_path = '/a/home/jmg/src/ffmpeg/ffmpeg' | |||||
| ffmpeg_path = '/usr/local/bin/ffmpeg' | |||||
| ffmpeg_path = '/a/home/jmg/src/ffmpeg.tmp/ffmpeg' | |||||
| ffmpeg_path = '/usr/local/bin/ffmpeg-devel' | |||||
| import datetime | |||||
| import FileDIDL | |||||
| import errno | |||||
| import itertools | |||||
| import os | |||||
| import stat | |||||
| from DIDLLite import StorageFolder, Item, Resource, ResourceList | |||||
| from twisted.web import resource, server, static | |||||
| from twisted.python import log | |||||
| from twisted.internet import abstract, interfaces, process, protocol, reactor | |||||
| from zope.interface import implementer | |||||
| __all__ = [ 'registerklassfun', 'registerfiletoignore', | |||||
| 'FSObject', 'FSItem', 'FSDirectory', | |||||
| ] | |||||
| mimedict = static.loadMimeTypes() | |||||
| _klassfuns = [] | |||||
| def registerklassfun(fun, debug=False): | |||||
| _klassfuns.append((fun, debug)) | |||||
| _filestoignore = { | |||||
| '.DS_Store': None | |||||
| } | |||||
| def registerfiletoignore(f): | |||||
| _filestoignore[f] = None | |||||
| # Return this class when you want the file to be skipped. If you return this, | |||||
| # no other modules will be applied, and it won't be added. Useful for things | |||||
| # like .DS_Store which are known to useless on a media server. | |||||
| class IgnoreFile: | |||||
| pass | |||||
| def statcmp(a, b, cmpattrs = [ 'st_ino', 'st_dev', 'st_size', 'st_mtime', ]): | |||||
| if a is None or b is None: | |||||
| return False | |||||
| for i in cmpattrs: | |||||
| if getattr(a, i) != getattr(b, i): | |||||
| return False | |||||
| return True | |||||
| class FSObject(object): | |||||
| def __init__(self, path): | |||||
| self.FSpath = path | |||||
| self.pstat = None | |||||
| def checkUpdate(self, **kwargs): | |||||
| # need to handle no such file or directory | |||||
| # push it up? but still need to handle disappearing | |||||
| try: | |||||
| nstat = os.stat(self.FSpath) | |||||
| #print 'cU:', `self`, `self.pstat`, `nstat`, statcmp(self.pstat, nstat) | |||||
| if statcmp(self.pstat, nstat): | |||||
| return | |||||
| self.pstat = nstat | |||||
| self.doUpdate(**kwargs) | |||||
| except OSError as x: | |||||
| log.msg('os.stat, OSError: %s' % x) | |||||
| if x.errno in (errno.ENOENT, errno.ENOTDIR, errno.EPERM, ): | |||||
| # We can't access it anymore, delete it | |||||
| self.cd.delItem(self.id) | |||||
| return | |||||
| else: | |||||
| raise | |||||
| def __repr__(self): | |||||
| return '<%s.%s: path: %s, id: %s, parent: %s, title: %s>' % \ | |||||
| (self.__class__.__module__, self.__class__.__name__, | |||||
| repr(self.FSpath), self.id, self.parentID, repr(self.title)) | |||||
| #@implementer(interfaces.IConsumer) | |||||
| #class NullConsumer(file, abstract.FileDescriptor): | |||||
| # | |||||
| # def __init__(self): | |||||
| # file.__init__(self, '/dev/null', 'w') | |||||
| # abstract.FileDescriptor.__init__(self) | |||||
| # | |||||
| # def write(self, data): | |||||
| # pass | |||||
| class DynamTransfer(protocol.ProcessProtocol): | |||||
| def __init__(self, path, mods, request): | |||||
| self.path = path | |||||
| self.mods = mods | |||||
| self.request = request | |||||
| def outReceived(self, data): | |||||
| self.request.write(data) | |||||
| def outConnectionLost(self): | |||||
| if self.request: | |||||
| self.request.unregisterProducer() | |||||
| self.request.finish() | |||||
| self.request = None | |||||
| def errReceived(self, data): | |||||
| pass | |||||
| #log.msg(data) | |||||
| def stopProducing(self): | |||||
| if self.request: | |||||
| self.request.unregisterProducer() | |||||
| self.request.finish() | |||||
| if self.proc: | |||||
| self.proc.loseConnection() | |||||
| self.proc.signalProcess('INT') | |||||
| self.request = None | |||||
| self.proc = None | |||||
| pauseProducing = lambda x: x.proc.pauseProducing() | |||||
| resumeProducing = lambda x: x.proc.resumeProducing() | |||||
| def render(self): | |||||
| mods = self.mods | |||||
| path = self.path | |||||
| request = self.request | |||||
| mimetype = { 'xvid': 'video/x-msvideo', | |||||
| 'mpeg2': 'video/mpeg', | |||||
| 'mp4': 'video/mp4', | |||||
| } | |||||
| vcodec = mods[0] | |||||
| if mods[0] not in mimetype: | |||||
| vcodec = 'mp4' | |||||
| request.setHeader('content-type', mimetype[vcodec]) | |||||
| if request.method == 'HEAD': | |||||
| return '' | |||||
| audiomp3 = [ '-acodec', 'mp3', '-ab', '192k', '-ac', '2', ] | |||||
| audiomp2 = [ '-acodec', 'mp2', '-ab', '256k', '-ac', '2', ] | |||||
| audioac3 = [ '-acodec', 'ac3', '-ab', '640k', ] | |||||
| audioaac = [ '-acodec', 'aac', '-ab', '640k', ] | |||||
| optdict = { | |||||
| 'xvid': [ '-vcodec', 'xvid', | |||||
| #'-mv4', '-gmc', '-g', '240', | |||||
| '-f', 'avi', ] + audiomp3, | |||||
| 'mpeg2': [ '-vcodec', 'mpeg2video', #'-g', '60', | |||||
| '-f', 'mpegts', ] + audioac3, | |||||
| 'mp4': [ '-vcodec', 'libx264', #'-g', '60', | |||||
| '-f', 'mpegts', ] + audioaac, | |||||
| } | |||||
| args = [ 'ffmpeg', '-i', path, | |||||
| '-sameq', | |||||
| '-threads', '4', | |||||
| #'-vb', '8000k', | |||||
| #'-sc_threshold', '500000', '-b_strategy', '1', '-max_b_frames', '6', | |||||
| ] + optdict[vcodec] + [ '-', ] | |||||
| log.msg(*[repr(i) for i in args]) | |||||
| self.proc = process.Process(reactor, ffmpeg_path, args, | |||||
| None, None, self) | |||||
| self.proc.closeStdin() | |||||
| request.registerProducer(self, 1) | |||||
| return server.NOT_DONE_YET | |||||
| class DynamicTrans(resource.Resource): | |||||
| isLeaf = True | |||||
| def __init__(self, path, notrans): | |||||
| self.path = path | |||||
| self.notrans = notrans | |||||
| def render(self, request): | |||||
| #if request.getHeader('getcontentfeatures.dlna.org'): | |||||
| # request.setHeader('contentFeatures.dlna.org', 'DLNA.ORG_OP=01;DLNA.ORG_CI=0') | |||||
| # # we only want the headers | |||||
| # self.notrans.render(request) | |||||
| # request.unregisterProducer() | |||||
| # return '' | |||||
| if request.postpath: | |||||
| # Translation request | |||||
| return DynamTransfer(self.path, request.postpath, request).render() | |||||
| else: | |||||
| request.setHeader('transferMode.dlna.org', 'Streaming') | |||||
| request.setHeader('contentFeatures.dlna.org', 'DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=017000 00000000000000000000000000') | |||||
| return self.notrans.render(request) | |||||
| class FSItem(FSObject, Item): | |||||
| def __init__(self, *args, **kwargs): | |||||
| FSObject.__init__(self, kwargs.pop('path')) | |||||
| mimetype = kwargs.pop('mimetype') | |||||
| kwargs['content'] = DynamicTrans(self.FSpath, | |||||
| static.File(self.FSpath, mimetype)) | |||||
| kwargs['date'] = datetime.datetime.utcfromtimestamp(os.stat(self.FSpath).st_mtime).isoformat() + '+00:00' | |||||
| Item.__init__(self, *args, **kwargs) | |||||
| self.url = '%s/%s' % (self.cd.urlbase, self.id) | |||||
| self.mimetype = mimetype | |||||
| self.checkUpdate() | |||||
| def doUpdate(self): | |||||
| #print 'FSItem doUpdate:', `self` | |||||
| self.res = ResourceList() | |||||
| r = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) | |||||
| r.size = os.path.getsize(self.FSpath) | |||||
| self.res.append(r) | |||||
| if self.mimetype.split('/', 1)[0] == 'video': | |||||
| self.res.append(Resource(self.url + '/mpeg2', | |||||
| 'http-get:*:%s:*' % 'video/mpeg')) | |||||
| self.res.append(Resource(self.url + '/xvid', | |||||
| 'http-get:*:%s:*' % 'video/x-msvideo')) | |||||
| Item.doUpdate(self) | |||||
| def ignoreFiles(path, fobj): | |||||
| bn = os.path.basename(path) | |||||
| if bn in _filestoignore: | |||||
| return IgnoreFile, None | |||||
| elif bn[:2] == '._' and open(path).read(4) == '\x00\x05\x16\x07': | |||||
| # AppleDouble encoded Macintosh Resource Fork | |||||
| return IgnoreFile, None | |||||
| return None, None | |||||
| def defFS(path, fobj): | |||||
| if os.path.isdir(path): | |||||
| # new dir | |||||
| return FSDirectory, { 'path': path } | |||||
| elif os.path.isfile(path): | |||||
| # new file - fall through to below | |||||
| pass | |||||
| else: | |||||
| log.msg('skipping (not dir or reg): %s' % path) | |||||
| return None, None | |||||
| klass, mt = FileDIDL.buildClassMT(FSItem, path) | |||||
| return klass, { 'path': path, 'mimetype': mt } | |||||
| def dofileadd(path, name): | |||||
| klass = None | |||||
| fsname = os.path.join(path, name) | |||||
| try: | |||||
| fobj = open(fsname) | |||||
| except: | |||||
| fobj = None | |||||
| for i, debug in itertools.chain(( (ignoreFiles, False), ), _klassfuns, ( (defFS, False), )): | |||||
| try: | |||||
| try: | |||||
| # incase the call expects a clean file | |||||
| fobj.seek(0) | |||||
| except: | |||||
| pass | |||||
| #log.msg('testing:', `i`, `fsname`, `fobj`) | |||||
| klass, kwargs = i(fsname, fobj) | |||||
| if klass is not None: | |||||
| break | |||||
| except: | |||||
| if debug: | |||||
| import traceback | |||||
| traceback.print_exc(file=log.logfile) | |||||
| if klass is None or klass is IgnoreFile: | |||||
| return None, None, None, None | |||||
| #print 'matched:', os.path.join(path, name), `i`, `klass` | |||||
| return klass, name, (), kwargs | |||||
| class FSDirectory(FSObject, StorageFolder): | |||||
| def __init__(self, *args, **kwargs): | |||||
| path = kwargs['path'] | |||||
| del kwargs['path'] | |||||
| StorageFolder.__init__(self, *args, **kwargs) | |||||
| FSObject.__init__(self, path) | |||||
| def genCurrent(self): | |||||
| return ((x.id, os.path.basename(x.FSpath)) for x in self ) | |||||
| def genChildren(self): | |||||
| return os.listdir(self.FSpath) | |||||
| def createObject(self, i): | |||||
| return dofileadd(self.FSpath, i) | |||||
| def __repr__(self): | |||||
| return ('<%s.%s: path: %s, id: %s, parent: %s, title: %s, ' + \ | |||||
| 'cnt: %d>') % (self.__class__.__module__, | |||||
| self.__class__.__name__, self.FSpath, self.id, | |||||
| self.parentID, self.title, len(self)) | |||||
| @@ -1,72 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2006 John-Mark Gurney <jmg@funkthat.com> | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| # | |||||
| # Convert file information into a DIDL class. Dynamicly generate a new class | |||||
| # from a base class and the DIDL class to be determined. | |||||
| # | |||||
| __all__ = [ 'mimetoclass', 'buildClassMT', 'getClassMT', ] | |||||
| import os.path | |||||
| import weakref | |||||
| from DIDLLite import VideoItem, AudioItem, TextItem, ImageItem | |||||
| from twisted.python import log | |||||
| from twisted.web import static | |||||
| mimedict = static.loadMimeTypes() | |||||
| classdict = weakref.WeakValueDictionary() | |||||
| mimetoclass = { | |||||
| 'application/ogg': AudioItem, | |||||
| 'video': VideoItem, | |||||
| 'audio': AudioItem, | |||||
| 'text': TextItem, | |||||
| 'image': ImageItem, | |||||
| } | |||||
| def getClassMT(name, mimetype = None, fp = None): | |||||
| '''Return a tuple of the DIDLLite class and mimetype responsible for the named/mimetyped/fpd file.''' | |||||
| if mimetype is None: | |||||
| fn, ext = os.path.splitext(name) | |||||
| ext = ext.lower() | |||||
| try: | |||||
| mimetype = mimedict[ext] | |||||
| except KeyError: | |||||
| log.msg('no mime-type for: %s' % name) | |||||
| return None, None | |||||
| ty = mimetype.split('/')[0] | |||||
| if mimetype in mimetoclass: | |||||
| klass = mimetoclass[mimetype] | |||||
| elif ty in mimetoclass: | |||||
| klass = mimetoclass[ty] | |||||
| else: | |||||
| # XXX - We could fall file -i on it | |||||
| log.msg('no item for mimetype: %s' % mimetype) | |||||
| return None, None | |||||
| return klass, mimetype | |||||
| def buildClassMT(baseklass, name, *args, **kwargs): | |||||
| klass, mt = getClassMT(name, *args, **kwargs) | |||||
| if klass is None: | |||||
| return None, None | |||||
| try: | |||||
| return classdict[(baseklass, klass)], mt | |||||
| except KeyError: | |||||
| pass | |||||
| class ret(baseklass, klass): | |||||
| pass | |||||
| ret.__name__ = '+'.join(['%s.%s' % (x.__module__, x.__name__) for x in (baseklass, klass)]) | |||||
| classdict[(baseklass, klass)] = ret | |||||
| return ret, mt | |||||
| @@ -1,26 +0,0 @@ | |||||
| General Functionality | |||||
| error w/o media dir | |||||
| Disable debug | |||||
| PS3 DSM-520 Cidero Intel DMC-250 | |||||
| Discovered | |||||
| remains after 30m | |||||
| FSStorage | |||||
| zip | |||||
| tar | |||||
| rar | |||||
| files in root | |||||
| mpegtsmod | |||||
| single stream | |||||
| multi stream | |||||
| dvd | |||||
| pyvr | |||||
| Clip | |||||
| item | |||||
| audio | |||||
| audioraw | |||||
| FLAC | |||||
| single track | |||||
| multi track | |||||
| slinkmod | |||||
| @@ -1,231 +1,5 @@ | |||||
| PyMedS | |||||
| ====== | |||||
| PyMedS Releases | |||||
| =============== | |||||
| This is a UPnP Media Server based upon a plugable architecture to allow | |||||
| other media repositories than just a file system. | |||||
| Tested basic functionality with the following devices and/or programs: | |||||
| Sony PlayStation 3 | |||||
| VLC | |||||
| BubbleUPnP (Android) | |||||
| Historically tested basic functionality with the following devices and/or programs: | |||||
| Cidero UPnP A/V Controller | |||||
| Intel's Media Control Point and Media Renderer | |||||
| D-Link DSM-520 | |||||
| Sony PlayStation 3 | |||||
| Linksys DMC-250 | |||||
| Usage | |||||
| ----- | |||||
| Either make a directory media and put the files there, or make a symlink | |||||
| named media to your media files. Either will work. Run it as: | |||||
| ``` | |||||
| ./pymediaserv <localip> [ <http server port> ] | |||||
| ``` | |||||
| The following packages are required to run the media server: | |||||
| * Twisted (tested w/ 22.10.0) - https://twisted.org/ | |||||
| * SOAPpy-py3 - https://github.com/Synerty/SOAPpy-py3 | |||||
| The requirements are in `requirements.txt` and can be installed | |||||
| via `pip intall -r requirements.txt`. | |||||
| Misc Issues | |||||
| ----------- | |||||
| Thanks to Coherence for soap_lite that solved the issues w/ PS3 not seeing | |||||
| the media server. The PS3 with the latest firmware (2.50 and later) now | |||||
| plays a lot more media than before. It will stream ATSC streams (OTA HD) | |||||
| w/ AC3 (5.1) audio, but the stream has to have PAT followed by PMT to see | |||||
| the stream as valid. If it is PAT, PAT, PMT or PMT, PAT, the PS3 will | |||||
| think the stream is corrupted. Support for WMV and Xvid in AVI seems to | |||||
| work well. | |||||
| For more information, check out the software page at: | |||||
| https://www.funkthat.com/~jmg/jmpc/pymeds.html | |||||
| Good Luck! | |||||
| John-Mark Gurney <jmg@funkthat.com> | |||||
| License Information | |||||
| ------------------- | |||||
| This code is based upon code by Tim Potter. | |||||
| It is licensed under the MIT license at: | |||||
| http://opensource.org/licenses/mit-license.php | |||||
| Ideas for future improvements: | |||||
| I have received a few ECONNABORTED errors at times. The patch | |||||
| twisted.internet.tcp.py.patch catches this error, and handles | |||||
| it properly. | |||||
| Add res to the DVDTitle container to play the title, and for other | |||||
| containers that it makes sense for. | |||||
| Make the directory persistant so that the server's UUID does not | |||||
| change each run and we don't have to recreate the objects. This | |||||
| will mean we can add additional meta data. | |||||
| Figure out how to rearchitect ContentDirectoryControl so I don't | |||||
| need to use doRecall. This may be helped by not necessarily | |||||
| figuring out all the children of a member just to fetch it. | |||||
| childCount isn't a required attribute. Possibly doing the work | |||||
| in a thread would be a good solution. | |||||
| Autodetect IP address. | |||||
| Support sorting by other attributes. | |||||
| Finish support for playing DVD's. | |||||
| Support custom icon like MediaTomb does. | |||||
| Support a generic layer for transcoding, and supporting detection | |||||
| of types for devices to only present the optimal type. Less of | |||||
| an issue now the PS3 video support has been expanded. | |||||
| Use deferredGenerator to eliminate doRecall. | |||||
| Make the ShoutCast module only do one connection and farm it to all | |||||
| requestors. How will we handle a blocked, non-reading client, | |||||
| drop them? How do we detect them to prevent blocking other clients. | |||||
| Add support to call Browse in a thread. Convert Browse to be a | |||||
| deferredGenerator so we can | |||||
| v0.x: | |||||
| Ignore AppleDouble Resource Fork Files. (Maybe we should ignore all | |||||
| dot files.) | |||||
| Readded SOAPpy/fpconst requirement as it is necessary for twisted. | |||||
| Made it a bit closer to a real twisted project, in the process, | |||||
| pymediaserv looks more like a normal program. It allows you to | |||||
| override the media path and title of the server. | |||||
| Minor change to checkUpdate API, we didn't use the return value, so | |||||
| don't return one anymore. | |||||
| Support .scst files which is a file containing a URL to a .pls file. | |||||
| This means that you can support streaming radio stations that | |||||
| provides a .pls file such as KCSM 91.1. | |||||
| Fix ShoutCast error handling to be correct. | |||||
| Add support for an XML file containing an object so you can create | |||||
| an item that points to an mp3 streaming url that isn't a .pls. | |||||
| Add support for playing parts of TS streams. This is useful for | |||||
| splitting apart a TS stream w/ multiple clips (like HDV) w/o | |||||
| creating the files. | |||||
| If we get an error parsing the genres of ShoutCAST, try again. | |||||
| Print out the modules that failed to load. | |||||
| Added an Audio module that will stream audio using the ossaudiodev | |||||
| module. | |||||
| Added SLink module for accessing Sony CD Changers. | |||||
| Add support for WAX/ASX files in shoutcast. | |||||
| Removed support for shoutcast as the module used was too old. | |||||
| Add support for FLAC audio files. If you have libFLAC installed, | |||||
| it will dynamicly decompress the audio. It supports seeking so | |||||
| goto should also be supported. If you want faster decoding there | |||||
| is a interleave.c that can be compiled to _interleave.so as | |||||
| interleaving the data in python code is much slower. | |||||
| Multi-track FLAC files (ones w/ an embeded cue-sheet are supported) | |||||
| will appear as a directory with each track in the directory. | |||||
| Files in the root directory of zip archives should now | |||||
| work. | |||||
| Support automatic formating of some attributes. One is duration, | |||||
| you can now store the number of seconds (as an int or a float), | |||||
| and a correctly formated duration will be automaticly generated. | |||||
| Add ability to turn on debugging on a per module basis. Saves | |||||
| from having to output tons of debug data. | |||||
| Support transcoding to mp4 format, video will be h.264 and audio will | |||||
| be AAC. | |||||
| Switch iterzipfile to use twisted's zipstream. This fixes the | |||||
| problem where it would not work on Python 2.5 as Python removed | |||||
| a previously documented member. This also makes the code MUCH | |||||
| smaller. | |||||
| v0.5: | |||||
| Support multiple SSDP servers on the same box. | |||||
| Fix SSDP to set the max-age to 7 days. We now retransmit replies | |||||
| and reannounce ourselves randomly before our original announcement | |||||
| expires. This fixes the Server Disconnects I was seeing on the | |||||
| DSM-520! | |||||
| Change how the mpegtsmod handles multi-stream TS's. Instead of | |||||
| calling tssel.py, we fixup the PAT to only contain the channel | |||||
| we want. This does mean we send more data than we need, but | |||||
| means that we can make the stream seekable. | |||||
| Now works w/ PS3. The PS3 as of 2.50 supports AC3 audio in | |||||
| MPEG-TS streams. | |||||
| Add rar file support. This is optional, and if rarfile is not | |||||
| installed, things will work, just not be able to look into rar | |||||
| files. | |||||
| Fix problem when adding multiple res with same mime-type and adding | |||||
| transcoding mime-types to all files, not just video type files. | |||||
| Speed up detection of large zip files. Previously we would | |||||
| instantiate the ZipFile class, which was expensive. Use the | |||||
| is_zipfile function instead. | |||||
| Fix handling of Containers. Previously we would rescan the root | |||||
| dir many times due to calling doUpdate when we just wanted to | |||||
| update the SystemID. There was also a bug where we would do a | |||||
| lot more work than necessary. This significantly speeds up | |||||
| large directories. | |||||
| Improve mpegtsmod so that it doesn't always scan the first 2 megs | |||||
| of the file. Do a quick check of the first few KB first. | |||||
| Create a new module that fetchs an XML files, parses it, and | |||||
| presents the media in it. This is to integrate my Python PVR | |||||
| so that I can browse the shows I have recorded. | |||||
| Fix transcoding of MPEG-2, use mp2 audio instead of mp3. | |||||
| Sync up the programs in mpegts. | |||||
| Skip directories in ZipStorage for zip and rar archives. | |||||
| For ShoutCast streams, override the User-Agent, apparently some/most | |||||
| stations now block the default one. | |||||
| v0.3: | |||||
| Include some patches for twisted in the distro, in the directory | |||||
| patches. | |||||
| Look inside MPEG-TS for TVCT and/or PAT and if there is more | |||||
| than one program, make it a container w/ the different | |||||
| programs. Includes the program and MPEG-TS python module in | |||||
| the mpegts directory. | |||||
| Add support for multiple res elements and automatic transcoding | |||||
| to either avi/xvid or mpeg2 using ffmpeg. Update the path to | |||||
| ffmpeg in the FSStorage.py file. | |||||
| Look inside DVDs and handle titles and chapters. We can not yet | |||||
| play the streams. This requires pydvdread which is included | |||||
| in the directory pydvdread. It depends upon libdvdread and | |||||
| requires swig and a C compiler to build. | |||||
| Empty dirs w/ no content would disappear, and cause a short | |||||
| response to BrowseDirectChildren. The DSM-520 askes for one | |||||
| more than displayed, and uses the existant of the extra item | |||||
| as indication if there are more items. | |||||
| Understands zip and tar files now. It will dynamicly extract | |||||
| items, so you can zip up your jpegs and view them w/o having | |||||
| to unzip them. tar files can be gzip'd or bzip2'd. | |||||
| Each item now has an optinal content attribute, which if set is | |||||
| installed in the web server. | |||||
| Don't send 'foobar' when exiting, stops a traceback on another | |||||
| instance of PyMedS. | |||||
| Properly fix deleting items. If you had another item with zero | |||||
| children which was before the deleted item, it would get removed | |||||
| from the list instead of the deleted item. | |||||
| v0.2: | |||||
| No longer require restarting to see new files/dirs in hierarchy. | |||||
| Add FSStorage which is a set of classes for handling filesystem | |||||
| objects, also handles updateID's. | |||||
| Make the root container customizable, so you don't end up with | |||||
| a single entry (like media) at the root. This lets us use a | |||||
| FSDirectory as the root and get auto-enumeration | |||||
| Support returning custom error codes, so that we can return 701 | |||||
| No such object among others. | |||||
| Support deleting items, so we can remove them when no longer on | |||||
| the file system. | |||||
| Make Containers a subclass of list. This lets use populate | |||||
| childCount properly. | |||||
| Add required attributes to Storage* classes. | |||||
| Support custom container classes to addContainer. | |||||
| Add a few more custom mime-types. | |||||
| Sort listings by name for now. | |||||
| v0.1: | |||||
| Don't bind the UDP socket to the multicast address, so replies | |||||
| go out on our local IP. | |||||
| Send out notify requests when we register the services with SSDP. | |||||
| Send out byebye notifications when we are shutting down. | |||||
| Randomize the UUID for the server (this should be part of the | |||||
| saved state). | |||||
| Randomize the port, or optionally set it on the command line. | |||||
| Teach ContentDirectory.py the basics on handling Containers and | |||||
| generic browse support. You can addItem and addContainer, which | |||||
| each return either respective ObjectID. | |||||
| We already support partial chunking of responses, but we don't yet | |||||
| support filtering or sorting. | |||||
| This repo contains the tar balls that are the various releases that have | |||||
| been made over the years. | |||||
| @@ -1,204 +0,0 @@ | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| # Copyright 2005, Tim Potter <tpot@samba.org> | |||||
| # Copyright 2006-2007 John-Mark Gurney <jmg@funkthat.com> | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| # | |||||
| # Implementation of SSDP server under Twisted Python. | |||||
| # | |||||
| import random | |||||
| import string | |||||
| from twisted.python import log | |||||
| from twisted.internet.protocol import DatagramProtocol | |||||
| from twisted.internet import reactor, task | |||||
| # TODO: Is there a better way of hooking the SSDPServer into a reactor | |||||
| # without having to know the default SSDP port and multicast address? | |||||
| # There must be a twisted idiom for doing this. | |||||
| SSDP_PORT = 1900 | |||||
| SSDP_ADDR = '239.255.255.250' | |||||
| # TODO: Break out into a HTTPOverUDP class and implement | |||||
| # process_SEARCH(), process_NOTIFY() methods. Create UPNP specific | |||||
| # class to handle services etc. | |||||
| class SSDPServer(DatagramProtocol): | |||||
| """A class implementing a SSDP server. The notifyReceived and | |||||
| searchReceived methods are called when the appropriate type of | |||||
| datagram is received by the server.""" | |||||
| # not used yet | |||||
| stdheaders = [ ('Server', 'Twisted, UPnP/1.0, python-upnp'), ] | |||||
| elements = {} | |||||
| known = {} | |||||
| maxage = 7 * 24 * 60 * 60 | |||||
| def __init__(self): | |||||
| # XXX - no init? | |||||
| #DatagramProtocol.__init__(self) | |||||
| pass | |||||
| def startProtocol(self): | |||||
| self.transport.joinGroup(SSDP_ADDR) | |||||
| # so we don't get our own sends | |||||
| self.transport.setLoopbackMode(0) | |||||
| def doStop(self): | |||||
| '''Make sure we send out the byebye notifications.''' | |||||
| for st in list(self.known.keys()): | |||||
| self.doByebye(st) | |||||
| del self.known[st] | |||||
| DatagramProtocol.doStop(self) | |||||
| def datagramReceived(self, data, hostporttup): | |||||
| """Handle a received multicast datagram.""" | |||||
| host, port = hostporttup | |||||
| data = data.decode('ascii') | |||||
| header, payload = data.split('\r\n\r\n') | |||||
| lines = header.split('\r\n') | |||||
| cmd = lines[0].split(' ') | |||||
| lines = [x.replace(': ', ':', 1) for x in lines[1:]] | |||||
| lines = [x for x in lines if len(x) > 0] | |||||
| headers = [x.split(':', 1) for x in lines] | |||||
| headers = dict([(x[0].lower(), x[1]) for x in headers]) | |||||
| if cmd[0] == 'M-SEARCH' and cmd[1] == '*': | |||||
| # SSDP discovery | |||||
| self.discoveryRequest(headers, (host, port)) | |||||
| elif cmd[0] == 'NOTIFY' and cmd[1] == '*': | |||||
| # SSDP presence | |||||
| self.notifyReceived(headers, (host, port)) | |||||
| else: | |||||
| log.msg('Unknown SSDP command %s %s' % cmd) | |||||
| def discoveryRequest(self, headers, hostporttup): | |||||
| """Process a discovery request. The response must be sent to | |||||
| the address specified by (host, port).""" | |||||
| host, port = hostporttup | |||||
| log.msg('Discovery request for %s' % headers['st']) | |||||
| # Do we know about this service? | |||||
| if headers['st'] == 'ssdp:all': | |||||
| for i in self.known: | |||||
| hcopy = dict(headers.items()) | |||||
| hcopy['st'] = i | |||||
| self.discoveryRequest(hcopy, (host, port)) | |||||
| return | |||||
| if headers['st'] not in self.known: | |||||
| return | |||||
| #print 'responding' | |||||
| # Generate a response | |||||
| response = [] | |||||
| response.append('HTTP/1.1 200 OK') | |||||
| for k, v in self.known[headers['st']].items(): | |||||
| response.append('%s: %s' % (k, v)) | |||||
| response.extend(('', '')) | |||||
| delay = random.randint(0, int(headers['mx'])) | |||||
| reactor.callLater(delay, self.transport.write, | |||||
| b'\r\n'.join((bytes(x, 'ascii') for x in response)), (host, port)) | |||||
| def register(self, usn, st, location): | |||||
| """Register a service or device that this SSDP server will | |||||
| respond to.""" | |||||
| log.msg('Registering %s' % st) | |||||
| self.known[st] = {} | |||||
| self.known[st]['USN'] = usn | |||||
| self.known[st]['LOCATION'] = location | |||||
| self.known[st]['ST'] = st | |||||
| self.known[st]['EXT'] = '' | |||||
| self.known[st]['SERVER'] = 'Twisted, UPnP/1.0, python-upnp' | |||||
| self.known[st]['CACHE-CONTROL'] = 'max-age=%d' % self.maxage | |||||
| self.doNotifySchedule(st) | |||||
| reactor.callLater(random.uniform(.5, 1), lambda: self.doNotify(st)) | |||||
| reactor.callLater(random.uniform(1, 5), lambda: self.doNotify(st)) | |||||
| def doNotifySchedule(self, st): | |||||
| self.doNotify(st) | |||||
| reactor.callLater(random.uniform(self.maxage / 3, | |||||
| self.maxage / 2), lambda: self.doNotifySchedule(st)) | |||||
| def doByebye(self, st): | |||||
| """Do byebye""" | |||||
| log.msg('Sending byebye notification for %s' % st) | |||||
| resp = [ 'NOTIFY * HTTP/1.1', | |||||
| 'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT), | |||||
| 'NTS: ssdp:byebye', | |||||
| ] | |||||
| stcpy = dict(self.known[st].items()) | |||||
| stcpy['NT'] = stcpy['ST'] | |||||
| del stcpy['ST'] | |||||
| resp.extend([': '.join(x) for x in stcpy.items()]) | |||||
| resp.extend(('', '')) | |||||
| resp = b'\r\n'.join(bytes(x, 'ascii') for x in resp) | |||||
| self.transport.write(resp, (SSDP_ADDR, SSDP_PORT)) | |||||
| self.transport.write(resp, (SSDP_ADDR, SSDP_PORT)) | |||||
| def doNotify(self, st): | |||||
| """Do notification""" | |||||
| log.msg('Sending alive notification for %s' % st) | |||||
| resp = [ 'NOTIFY * HTTP/1.1', | |||||
| 'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT), | |||||
| 'NTS: ssdp:alive', | |||||
| ] | |||||
| stcpy = dict(self.known[st].items()) | |||||
| stcpy['NT'] = stcpy['ST'] | |||||
| del stcpy['ST'] | |||||
| resp.extend([': '.join(x) for x in stcpy.items()]) | |||||
| resp.extend(('', '')) | |||||
| self.transport.write(b'\r\n'.join(bytes(x, 'ascii') for x in resp), (SSDP_ADDR, SSDP_PORT)) | |||||
| def notifyReceived(self, headers, hostporttup): | |||||
| """Process a presence announcement. We just remember the | |||||
| details of the SSDP service announced.""" | |||||
| host, port = hostporttup | |||||
| if headers['nts'] == 'ssdp:alive': | |||||
| if headers['nt'] not in self.elements: | |||||
| # Register device/service | |||||
| self.elements[headers['nt']] = {} | |||||
| self.elements[headers['nt']]['USN'] = headers['usn'] | |||||
| self.elements[headers['nt']]['host'] = (host, port) | |||||
| log.msg('Detected presence of %s' % headers['nt']) | |||||
| #log.msg('headers: %s' % `headers`) | |||||
| elif headers['nts'] == 'ssdp:byebye': | |||||
| if headers['nt'] in self.elements: | |||||
| # Unregister device/service | |||||
| del(self.elements[headers['nt']]) | |||||
| log.msg('Detected absence for %s' % headers['nt']) | |||||
| else: | |||||
| log.msg('Unknown subtype %s for notification type %s' % | |||||
| (headers['nts'], headers['nt'])) | |||||
| def findService(self, name): | |||||
| """Return information about a service registered over SSDP.""" | |||||
| # TODO: Implement me. | |||||
| # TODO: Send out a discovery request if we haven't registered | |||||
| # a presence announcement. | |||||
| def findDevice(self, name): | |||||
| """Return information about a device registered over SSDP.""" | |||||
| # TODO: Implement me. | |||||
| # TODO: Send out a discovery request if we haven't registered | |||||
| # a presence announcement. | |||||
| @@ -1,323 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2006-2008 John-Mark Gurney <jmg@funkthat.com> | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| import itertools | |||||
| import os.path | |||||
| import time | |||||
| import FileDIDL | |||||
| from DIDLLite import StorageFolder, Item, VideoItem, AudioItem, TextItem, ImageItem, Resource | |||||
| from FSStorage import FSObject, registerklassfun | |||||
| from twisted.python import log, threadable | |||||
| from twisted.spread import pb | |||||
| from twisted.web import http | |||||
| from twisted.web import server | |||||
| from twisted.web import resource | |||||
| def inserthierdict(d, name, obj, sep): | |||||
| if not name: | |||||
| return | |||||
| if sep is not None: | |||||
| i = name.find(sep) | |||||
| if sep is None or i == -1: | |||||
| d[name] = obj | |||||
| return | |||||
| dname = name[:i] | |||||
| rname = name[i + 1:] | |||||
| # remaining path components | |||||
| inserthierdict(d.setdefaul(dname, {}), rname, obj, sep) | |||||
| def buildNameHier(names, objs, sep): | |||||
| ret = {} | |||||
| for n, o in itertools.izip(names, objs): | |||||
| #Skip directories in a TarFile or RarFile | |||||
| if hasattr(o, 'isdir') and o.isdir(): | |||||
| continue | |||||
| inserthierdict(ret, n, o, sep) | |||||
| return ret | |||||
| def zipinfocmp(za, zb): | |||||
| for i in [ 'filename', 'date_time', 'file_size', 'CRC' ]: | |||||
| r = cmp(getattr(za, i), getattr(zb, i)) | |||||
| if r: | |||||
| return r | |||||
| return 0 | |||||
| def zipinfohash(za): | |||||
| r = 0 | |||||
| for i in [ 'filename', 'date_time', 'file_size', 'CRC' ]: | |||||
| r ^= getattr(za, i).__hash__ | |||||
| return r | |||||
| class ZIWrap(object): | |||||
| __slots__ = [ '_zi' ] | |||||
| def __init__(self, zi): | |||||
| self._zi = zi | |||||
| __hash__ = zipinfohash | |||||
| __cmp__ = zipinfocmp | |||||
| def __getattr__(self, n): | |||||
| return getattr(self._zi, n) | |||||
| def __setattr__(self, n, k): | |||||
| if n == '_zi': | |||||
| object.__setattr__(self, n, k) | |||||
| return | |||||
| return setattr(self._zi, n, k) | |||||
| def __delattr__(sefl, n): | |||||
| return delattr(self._zi, n) | |||||
| class ZipFileTransfer(pb.Viewable): | |||||
| def __init__(self, zf, name, request): | |||||
| self.zf = zf | |||||
| self.size = zf.getinfo(name).file_size | |||||
| self.iter = zf.readiter(name) | |||||
| self.request = request | |||||
| self.written = 0 | |||||
| request.registerProducer(self, 0) | |||||
| def resumeProducing(self): | |||||
| if not self.request: | |||||
| return | |||||
| # get data and write to request. | |||||
| try: | |||||
| data = self.iter.next() | |||||
| if 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) | |||||
| except StopIteration: | |||||
| if self.request: | |||||
| self.request.unregisterProducer() | |||||
| self.request.finish() | |||||
| self.request = None | |||||
| def pauseProducing(self): | |||||
| pass | |||||
| def stopProducing(self): | |||||
| # close zipfile | |||||
| self.request = None | |||||
| # Remotely relay producer interface. | |||||
| def view_resumeProducing(self, issuer): | |||||
| self.resumeProducing() | |||||
| def view_pauseProducing(self, issuer): | |||||
| self.pauseProducing() | |||||
| def view_stopProducing(self, issuer): | |||||
| self.stopProducing() | |||||
| synchronized = ['resumeProducing', 'stopProducing'] | |||||
| threadable.synchronize(ZipFileTransfer) | |||||
| class ZipResource(resource.Resource): | |||||
| # processors = {} | |||||
| isLeaf = True | |||||
| def __init__(self, zf, name, mt): | |||||
| resource.Resource.__init__(self) | |||||
| self.zf = zf | |||||
| self.zi = zf.getinfo(name) | |||||
| self.name = name | |||||
| self.mt = mt | |||||
| def getFileSize(self): | |||||
| return self.zi.file_size | |||||
| def render(self, request): | |||||
| request.setHeader('content-type', self.mt) | |||||
| # We could possibly send the deflate data directly! | |||||
| if None and self.encoding: | |||||
| request.setHeader('content-encoding', self.encoding) | |||||
| if request.setLastModified(time.mktime(list(self.zi.date_time) + | |||||
| [ 0, 0, -1])) is http.CACHED: | |||||
| return '' | |||||
| request.setHeader('content-length', str(self.getFileSize())) | |||||
| if request.method == 'HEAD': | |||||
| return '' | |||||
| # return data | |||||
| ZipFileTransfer(self.zf, self.name, request) | |||||
| # and make sure the connection doesn't get closed | |||||
| return server.NOT_DONE_YET | |||||
| class ZipItem: | |||||
| '''Basic zip stuff initalization''' | |||||
| def __init__(self, *args, **kwargs): | |||||
| self.zo = kwargs['zo'] | |||||
| del kwargs['zo'] | |||||
| self.zf = kwargs['zf'] | |||||
| del kwargs['zf'] | |||||
| self.name = kwargs['name'] | |||||
| del kwargs['name'] | |||||
| def checkUpdate(self): | |||||
| # XXX - is this the correct order? | |||||
| self.doUpdate() | |||||
| self.zo.checkUpdate() | |||||
| class ZipFile(ZipItem, Item): | |||||
| def __init__(self, *args, **kwargs): | |||||
| self.mimetype = kwargs['mimetype'] | |||||
| del kwargs['mimetype'] | |||||
| ZipItem.__init__(self, *args, **kwargs) | |||||
| del kwargs['zf'] | |||||
| del kwargs['zo'] | |||||
| del kwargs['name'] | |||||
| self.zi = self.zf.getinfo(self.name) | |||||
| kwargs['content'] = ZipResource(self.zf, self.name, | |||||
| self.mimetype) | |||||
| Item.__init__(self, *args, **kwargs) | |||||
| self.url = '%s/%s' % (self.cd.urlbase, self.id) | |||||
| self.res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) | |||||
| self.res.size = self.zi.file_size | |||||
| class ZipChildDir(ZipItem, StorageFolder): | |||||
| '''This is to represent a child dir of the zip file.''' | |||||
| def __init__(self, *args, **kwargs): | |||||
| self.hier = kwargs['hier'] | |||||
| self.sep = kwargs['sep'] | |||||
| del kwargs['hier'], kwargs['sep'] | |||||
| ZipItem.__init__(self, *args, **kwargs) | |||||
| del kwargs['zf'], kwargs['zo'], kwargs['name'] | |||||
| StorageFolder.__init__(self, *args, **kwargs) | |||||
| def genChildren(self): | |||||
| return self.hier | |||||
| def genCurrent(self): | |||||
| return ((x.id, x.name.rsplit(self.sep, 1)[1]) for x in self) | |||||
| def createObject(self, i, arg): | |||||
| pathname = self.sep.join((self.name, i)) | |||||
| kwargs = { 'zf': self.zf, 'zo': self, 'name': pathname } | |||||
| if isinstance(self.hier[i], dict): | |||||
| # must be a dir | |||||
| klass = ZipChildDir | |||||
| kwargs['sep'] = self.sep | |||||
| kwargs['hier'] = self.hier[i] | |||||
| else: | |||||
| klass, mt = FileDIDL.buildClassMT(ZipFile, i) | |||||
| kwargs['mimetype'] = mt | |||||
| return klass, i, (), kwargs | |||||
| def __repr__(self): | |||||
| return '<ZipChildDir: len: %d>' % len(self) | |||||
| def tryTar(path): | |||||
| # Try to see if it's a tar file | |||||
| if path[-2:] == 'gz': | |||||
| comp = tarfile.TAR_GZIPPED | |||||
| elif path[-3:] == 'bz2': | |||||
| comp = tarfile.TAR_BZ2 | |||||
| else: | |||||
| comp = tarfile.TAR_PLAIN | |||||
| return tarfile.TarFileCompat(path, compression=comp) | |||||
| def canHandle(path): | |||||
| if zipfile.is_zipfile(path): | |||||
| return True | |||||
| if rarfile.is_rarfile(path): | |||||
| return True | |||||
| #tar is cheaper on __init__ than zipfile | |||||
| return tryTar(path) | |||||
| def genZipFile(path): | |||||
| if zipfile.is_zipfile(path): | |||||
| return zipfile.ZipFile(path) | |||||
| if rarfile.is_rarfile(path): | |||||
| return rarfile.RarFile(path) | |||||
| try: | |||||
| return tryTar(path) | |||||
| except: | |||||
| #import traceback | |||||
| #traceback.print_exc(file=log.logfile) | |||||
| raise | |||||
| class ZipObject(FSObject, StorageFolder): | |||||
| seps = [ '/', '\\' ] | |||||
| def __init__(self, *args, **kwargs): | |||||
| '''If a zip argument is passed it, use that as the zip archive.''' | |||||
| path = kwargs.pop('path') | |||||
| StorageFolder.__init__(self, *args, **kwargs) | |||||
| FSObject.__init__(self, path) | |||||
| def genChildren(self): | |||||
| # open the zipfile as necessary. | |||||
| # XXX - this might leave too many zip files around | |||||
| self.zip = genZipFile(self.FSpath) | |||||
| nl = self.zip.namelist() | |||||
| cnt = 0 | |||||
| cursep = None | |||||
| for i in self.seps: | |||||
| newsum = sum([ j.count(i) for j in nl ]) | |||||
| if newsum > cnt: | |||||
| cursep = i | |||||
| cnt = newsum | |||||
| self.sep = cursep | |||||
| il = self.zip.infolist() | |||||
| hier = buildNameHier(nl, [ ZIWrap(x) for x in il ], cursep) | |||||
| return hier | |||||
| def genCurrent(self): | |||||
| return ((x.id, x.name) for x in self) | |||||
| def createObject(self, i, arg): | |||||
| kwargs = { 'zf': self.zip, 'zo': self, 'name': i } | |||||
| if isinstance(arg, dict): | |||||
| # must be a dir | |||||
| klass = ZipChildDir | |||||
| kwargs['sep'] = self.sep | |||||
| kwargs['hier'] = arg | |||||
| else: | |||||
| klass, mt = FileDIDL.buildClassMT(ZipFile, i) | |||||
| kwargs['mimetype'] = mt | |||||
| return klass, i, (), kwargs | |||||
| def detectzipfile(path, fobj): | |||||
| try: | |||||
| canHandle(path) | |||||
| except: | |||||
| return None, None | |||||
| return ZipObject, { 'path': path } | |||||
| registerklassfun(detectzipfile) | |||||
| @@ -1,276 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2009 John-Mark Gurney <jmg@funkthat.com> | |||||
| '''Audio Source''' | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| import ossaudiodev | |||||
| import os.path | |||||
| from DIDLLite import Container, MusicGenre, AudioItem, Resource, ResourceList | |||||
| from FSStorage import registerklassfun | |||||
| from twisted.internet.abstract import FileDescriptor | |||||
| from twisted.internet import fdesc | |||||
| from twisted.python import log, threadable, failure | |||||
| from twisted.web import error, http, resource, server | |||||
| from zope.interface import implements | |||||
| mttobytes = { | |||||
| 'audio/l8': 1, | |||||
| 'audio/l16': 2, | |||||
| } | |||||
| def bytespersecmt(mt): | |||||
| tmp = [ x.strip() for x in mt.split(';') ] | |||||
| try: | |||||
| r = mttobytes[tmp[0].lower()] | |||||
| except KeyError: | |||||
| raise ValueError('invalid audio type: %s' % repr(tmp[0])) | |||||
| v = set(('rate', 'channels')) | |||||
| for i in tmp[1:]: | |||||
| arg, value = [ x.strip() for x in i.split('=', 1) ] | |||||
| if arg in v: | |||||
| v.remove(arg) | |||||
| r *= int(value) | |||||
| else: | |||||
| raise ValueError('invalid audio parameter %s in %s' % | |||||
| (repr(arg), repr(mt))) | |||||
| return r | |||||
| class AudioPlayer(FileDescriptor): | |||||
| def __init__(self, consumer, dev, mode, params): | |||||
| self._dev = ossaudiodev.open(dev, mode) | |||||
| # Set some sub-functions | |||||
| self.fileno = self._dev.fileno | |||||
| self.setparameters = self._dev.setparameters | |||||
| res = self.setparameters(*params) | |||||
| self._dev.nonblock() | |||||
| FileDescriptor.__init__(self) | |||||
| self.connected = True | |||||
| self.attached = consumer | |||||
| self.writefun = self.attached.write | |||||
| consumer.registerProducer(self, True) | |||||
| self.dobuffer = False | |||||
| self.buffer = None | |||||
| self.startReading() | |||||
| # Drop our useless write connection | |||||
| self._writeDisconnected = True | |||||
| def writeSomeData(self, data): | |||||
| print('wsd:', len(data)) | |||||
| return fdesc.writeToFD(self.fileno(), data) | |||||
| def doRead(self): | |||||
| return fdesc.readFromFD(self.fileno(), self.writefun) | |||||
| def connectionLost(self, reason): | |||||
| FileDescriptor.connectionLost(self, reason) | |||||
| print('AP, connectionLost') | |||||
| self.fileno = lambda: -1 | |||||
| self.setparameters = None | |||||
| if self._dev is not None: | |||||
| self._dev.close() | |||||
| self._dev = None | |||||
| self.attached = None | |||||
| def stopProducing(self): | |||||
| print('AP, sp') | |||||
| self.writefun = lambda x: None | |||||
| FileDescriptor.stopProducing(self) | |||||
| def pauseProducing(self): | |||||
| if not self.dobuffer: | |||||
| self.buffer = [] | |||||
| self.dobuffer = True | |||||
| self.writefun = self.buffer.append | |||||
| #FileDescriptor.pauseProducing(self) | |||||
| def resumeProducing(self): | |||||
| if self.dobuffer: | |||||
| self.attached.write(''.join(self.buffer)) | |||||
| self.dobuffer = False | |||||
| self.buffer = None | |||||
| self.writefun = self.attached.write | |||||
| #FileDescriptor.resumeProducing(self) | |||||
| def __repr__(self): | |||||
| return '<AudioPlayer: fileno: %d, connected: %s, self.disconnecting: %s, _writeDisconnected: %s>' % (self.fileno(), self.connected, self.disconnecting, self._writeDisconnected) | |||||
| class AudioResource(resource.Resource): | |||||
| isLeaf = True | |||||
| mtformat = { | |||||
| ossaudiodev.AFMT_S16_BE: 'audio/L16', | |||||
| ossaudiodev.AFMT_U8: 'audio/L8', | |||||
| } | |||||
| producerFactory = AudioPlayer | |||||
| def __init__(self, dev, default): | |||||
| resource.Resource.__init__(self) | |||||
| self.dev = dev | |||||
| self.default = default | |||||
| @staticmethod | |||||
| def getfmt(fmt): | |||||
| return getattr(ossaudiodev, 'AFMT_%s' % fmt) | |||||
| def getmimetype(self, *args): | |||||
| if len(args) == 0: | |||||
| args = self.default | |||||
| elif len(args) != 3: | |||||
| raise TypeError('getmimetype() takes exactly 0 or 3 aruments (%d given)' % len(args)) | |||||
| fmt, nchan, rate = args | |||||
| origfmt = fmt | |||||
| try: | |||||
| fmt = getattr(ossaudiodev, 'AFMT_%s' % fmt) | |||||
| nchan = int(nchan) | |||||
| rate = int(rate) | |||||
| except AttributeError: | |||||
| raise ValueError('Invalid audio format: %s' % repr(origfmt)) | |||||
| try: | |||||
| mt = self.mtformat[fmt] | |||||
| except KeyError: | |||||
| raise KeyError('No mime-type for audio format: %s.' % | |||||
| repr(origfmt)) | |||||
| return '%s;rate=%d;channels=%d' % (mt, rate, nchan) | |||||
| def render(self, request): | |||||
| default = self.default | |||||
| if request.postpath: | |||||
| default = request.postpath | |||||
| fmt, nchan, rate = default | |||||
| nchan = int(nchan) | |||||
| rate = int(rate) | |||||
| try: | |||||
| request.setHeader('content-type', | |||||
| self.getmimetype(fmt, nchan, rate)) | |||||
| except (ValueError, AttributeError, KeyError) as x: | |||||
| return error.ErrorPage(http.UNSUPPORTED_MEDIA_TYPE, | |||||
| 'Unsupported Media Type', str(x)).render(request) | |||||
| #except AttributeError: | |||||
| # return error.NoResource('Unknown audio format.').render(request) | |||||
| #except ValueError: | |||||
| # return error.NoResource('Unknown channels (%s) or rate (%s).' % (`nchan`, `rate`)).render(request) | |||||
| #except KeyError: | |||||
| # return error.ErrorPage(http.UNSUPPORTED_MEDIA_TYPE, | |||||
| # 'Unsupported Media Type', | |||||
| # 'No mime-type for audio format: %s.' % | |||||
| # `fmt`).render(request) | |||||
| if request.method == 'HEAD': | |||||
| return '' | |||||
| self.producerFactory(request, self.dev, 'r', | |||||
| (self.getfmt(fmt), nchan, rate, True)) | |||||
| # and make sure the connection doesn't get closed | |||||
| return server.NOT_DONE_YET | |||||
| synchronized = [ 'render' ] | |||||
| threadable.synchronize(AudioResource) | |||||
| class ossaudiodev_fmts: | |||||
| pass | |||||
| for i in (k for k in dir(ossaudiodev) if k[:5] == 'AFMT_' and \ | |||||
| isinstance(getattr(ossaudiodev, k), int)): | |||||
| setattr(ossaudiodev_fmts, i, getattr(ossaudiodev, i)) | |||||
| class AudioSource(AudioItem): | |||||
| def __init__(self, *args, **kwargs): | |||||
| file = kwargs.pop('file') | |||||
| fargs = eval(open(file).read().strip(), { '__builtins__': {}, }) | |||||
| # 'ossaudiodev': ossaudiodev_fmts }) | |||||
| self.dev = fargs.pop('dev') | |||||
| default = fargs.pop('default') | |||||
| kwargs['content'] = AudioResource(self.dev, default) | |||||
| AudioItem.__init__(self, *args, **kwargs) | |||||
| if False: | |||||
| self.bitrate = bitrate | |||||
| self.url = '%s/%s' % (self.cd.urlbase, self.id) | |||||
| self.res = ResourceList() | |||||
| self.res.append(Resource(self.url, 'http-get:*:%s:*' % | |||||
| kwargs['content'].getmimetype())) | |||||
| # XXX - add other non-default formats | |||||
| def getfmtstrings(f): | |||||
| r = [] | |||||
| for i in ( x for x in dir(ossaudiodev) if x[:5] == 'AFMT_' ): | |||||
| val = getattr(ossaudiodev, i) | |||||
| if val & f: | |||||
| f &= ~val | |||||
| r.append(i) | |||||
| while f: | |||||
| print(f, f & -f) | |||||
| r.append(f & -f) | |||||
| f ^= f & -f | |||||
| return r | |||||
| def detectaudiosource(origpath, fobj): | |||||
| path = os.path.basename(origpath) | |||||
| ext = os.path.splitext(path)[1] | |||||
| if ext == '.asrc': | |||||
| return AudioSource, { 'file': origpath } | |||||
| return None, None | |||||
| registerklassfun(detectaudiosource) | |||||
| from zope.interface import implements | |||||
| from twisted.internet.interfaces import IConsumer | |||||
| class FileConsumer: | |||||
| implements(IConsumer) | |||||
| def __init__(self, fp): | |||||
| self.fp = open(fp, 'w') | |||||
| self.producer = None | |||||
| def registerProducer(self, producer, streaming): | |||||
| if self.producer is not None: | |||||
| raise RuntimeError('already have a producer') | |||||
| self.streaming = streaming | |||||
| self.producer = producer | |||||
| producer.resumeProducing() | |||||
| def unregisterProducer(self): | |||||
| if self.producer is None: | |||||
| raise RuntimeError('none registered') | |||||
| self.producer = None | |||||
| def write(self, data): | |||||
| self.fp.write(data) | |||||
| if __name__ == '__main__': | |||||
| if False: | |||||
| i = ossaudiodev.open('/dev/dsp2', 'r') | |||||
| print(getfmtstrings(i.getfmts())) | |||||
| i.setparameters(ossaudiodev.AFMT_S16_BE, 2, 44100, True) | |||||
| print(repr(i.read(16))) | |||||
| else: | |||||
| aplr = AudioPlayer('/dev/dsp2', 'r', | |||||
| (ossaudiodev.AFMT_S16_BE, 2, 44100, True)) | |||||
| file = FileConsumer('test.output') | |||||
| file.registerProducer(aplr, True) | |||||
| from twisted.internet import reactor | |||||
| reactor.run() | |||||
| @@ -1,395 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2009 John-Mark Gurney <jmg@funkthat.com> | |||||
| '''Audio Raw Converter''' | |||||
| import cdrtoc | |||||
| from DIDLLite import MusicTrack, AudioItem, MusicAlbum, Resource, ResourceList | |||||
| from FSStorage import FSObject, registerklassfun | |||||
| from twisted.web import resource, server, static | |||||
| from twisted.internet.interfaces import IPullProducer | |||||
| from zope.interface import implements | |||||
| decoders = {} | |||||
| try: | |||||
| import flac | |||||
| decoders['flac'] = flac.FLACDec | |||||
| except ImportError: | |||||
| pass | |||||
| mtformat = { | |||||
| 16: 'audio/l16', # BE signed | |||||
| # 8: 'audio/l8', unsigned | |||||
| } | |||||
| def makeaudiomt(bitsps, rate, nchan): | |||||
| try: | |||||
| mt = mtformat[bitsps] | |||||
| except KeyError: | |||||
| raise KeyError('No mime-type for audio format: %s.' % | |||||
| repr(bitsps)) | |||||
| return '%s;rate=%d;channels=%d' % (mt, rate, nchan) | |||||
| def makemtfromdec(dec): | |||||
| return makeaudiomt(dec.bitspersample, dec.samplerate, | |||||
| dec.channels) | |||||
| class DecoderProducer: | |||||
| implements(IPullProducer) | |||||
| def __init__(self, consumer, decoder, tbytes, skipbytes): | |||||
| '''skipbytes should always be small. It is here in case | |||||
| someone requests the middle of a sample.''' | |||||
| self.decoder = decoder | |||||
| self.consumer = consumer | |||||
| self.tbytes = tbytes | |||||
| self.skipbytes = skipbytes | |||||
| #print 'DPregP', `self`, `self.tbytes`, `self.skipbytes` | |||||
| consumer.registerProducer(self, False) | |||||
| self.resumeProducing() | |||||
| def __repr__(self): | |||||
| return '<DecoderProducer: decoder: %s, bytes left: %d, skip: %d>' % (repr(self.decoder), self.tbytes, self.skipbytes) | |||||
| def pauseProducing(self): | |||||
| # XXX - bug in Twisted 8.2.0 on pipelined requests this is | |||||
| # called: http://twistedmatrix.com/trac/ticket/3919 | |||||
| pass | |||||
| def resumeProducing(self): | |||||
| #print 'DPrP', `self` | |||||
| r = self.decoder.read(256*1024) | |||||
| if r: | |||||
| #print 'DPrP:', len(r) | |||||
| if self.skipbytes: | |||||
| cnt = min(self.skipbytes, len(r)) | |||||
| r = r[cnt:] | |||||
| self.skipbytes -= cnt | |||||
| send = min(len(r), self.tbytes) | |||||
| r = r[:send] | |||||
| self.tbytes -= len(r) | |||||
| self.consumer.write(r) | |||||
| #print 'write %d bytes, remaining %d' % (len(r), self.tbytes) | |||||
| if self.tbytes: | |||||
| return | |||||
| #print 'DPurP', `self` | |||||
| self.consumer.unregisterProducer() | |||||
| self.consumer.finish() | |||||
| def stopProducing(self): | |||||
| #print 'DPsP', `self` | |||||
| self.decoder.close() | |||||
| self.decoder = None | |||||
| self.consumer = None | |||||
| class AudioResource(resource.Resource): | |||||
| isLeaf = True | |||||
| def __init__(self, f, dec, start, cnt): | |||||
| resource.Resource.__init__(self) | |||||
| self.f = f | |||||
| self.dec = dec | |||||
| self.start = start | |||||
| self.cnt = cnt | |||||
| def __repr__(self): | |||||
| return '<AudioResource file: %s, dec: %s, start:%d, cnt: %d>' % (repr(self.f), self.dec, self.start, self.cnt) | |||||
| def calcrange(self, rng, l): | |||||
| rng = rng.strip() | |||||
| unit, rangeset = rng.split('=') | |||||
| assert unit == 'bytes', repr(unit) | |||||
| start, end = rangeset.split('-') | |||||
| start = int(start) | |||||
| if end: | |||||
| end = int(end) | |||||
| else: | |||||
| end = l | |||||
| return start, end - start + 1 | |||||
| def render(self, request): | |||||
| #print 'render:', `request` | |||||
| decoder = self.dec(self.f) | |||||
| request.setHeader('content-type', makemtfromdec(decoder)) | |||||
| bytespersample = decoder.channels * decoder.bitspersample / 8 | |||||
| tbytes = self.cnt * bytespersample | |||||
| #print 'tbytes:', `tbytes`, 'cnt:', `self.cnt` | |||||
| skipbytes = 0 | |||||
| request.setHeader('content-length', tbytes) | |||||
| request.setHeader('accept-ranges', 'bytes') | |||||
| if request.requestHeaders.hasHeader('range'): | |||||
| #print 'range req:', `request.requestHeaders.getRawHeaders('range')` | |||||
| start, cnt = self.calcrange( | |||||
| request.requestHeaders.getRawHeaders('range')[0], | |||||
| tbytes) | |||||
| skipbytes = start % bytespersample | |||||
| #print 'going:', start / bytespersample | |||||
| decoder.goto(self.start + start / bytespersample) | |||||
| #print 'there' | |||||
| request.setHeader('content-length', cnt) | |||||
| request.setHeader('content-range', 'bytes %s-%s/%s' % | |||||
| (start, start + cnt - 1, tbytes)) | |||||
| tbytes = cnt | |||||
| else: | |||||
| decoder.goto(self.start) | |||||
| if request.method == 'HEAD': | |||||
| return '' | |||||
| DecoderProducer(request, decoder, tbytes, skipbytes) | |||||
| #print 'producing render', `decoder`, `tbytes`, `skipbytes` | |||||
| # and make sure the connection doesn't get closed | |||||
| return server.NOT_DONE_YET | |||||
| class AudioDisc(FSObject, MusicAlbum): | |||||
| def __init__(self, *args, **kwargs): | |||||
| self.cuesheet = kwargs.pop('cuesheet') | |||||
| self.cdtoc = kwargs.pop('toc', {}) | |||||
| self.kwargs = kwargs.copy() | |||||
| # XXX - need to convert to cdtoc? | |||||
| self.tags = tags = kwargs.pop('tags', {}) | |||||
| self.file = kwargs.pop('file') | |||||
| nchan = kwargs.pop('channels') | |||||
| samprate = kwargs.pop('samplerate') | |||||
| bitsps = kwargs.pop('bitspersample') | |||||
| samples = kwargs.pop('samples') | |||||
| picts = kwargs.pop('pictures', {}) | |||||
| totalbytes = nchan * samples * bitsps / 8 | |||||
| FSObject.__init__(self, kwargs.pop('path')) | |||||
| # XXX - exclude track 1 pre-gap? | |||||
| kwargs['content'] = cont = resource.Resource() | |||||
| cont.putChild('audio', AudioResource(file, | |||||
| kwargs.pop('decoder'), 0, samples)) | |||||
| #print 'doing construction' | |||||
| MusicAlbum.__init__(self, *args, **kwargs) | |||||
| #print 'adding resource' | |||||
| self.url = '%s/%s/audio' % (self.cd.urlbase, self.id) | |||||
| if 'cover' in picts: | |||||
| pict = picts['cover'][0] | |||||
| #print 'p:', `pict` | |||||
| cont.putChild('cover', static.Data(pict[7], pict[1])) | |||||
| self.albumArtURI = '%s/%s/cover' % (self.cd.urlbase, | |||||
| self.id) | |||||
| self.res = ResourceList() | |||||
| if 'DYEAR' in tags: | |||||
| self.year = tags['DYEAR'] | |||||
| if 'DGENRE' in tags: | |||||
| self.genre = tags['DGENRE'] | |||||
| #if 'DTITLE' in tags: | |||||
| # self.artist, self.album = tags['DTITLE'][0].split(' / ', 1) | |||||
| self.url = '%s/%s/audio' % (self.cd.urlbase, self.id) | |||||
| r = Resource(self.url, 'http-get:*:%s:*' % makeaudiomt(bitsps, | |||||
| samprate, nchan)) | |||||
| r.size = totalbytes | |||||
| r.duration = float(samples) / samprate | |||||
| r.bitrate = nchan * samprate * bitsps / 8 | |||||
| r.sampleFrequency = samprate | |||||
| r.bitsPerSample = bitsps | |||||
| r.nrAudioChannels = nchan | |||||
| self.res.append(r) | |||||
| #print 'completed' | |||||
| def sort(self, fun=lambda x, y: cmp(int(x.originalTrackNumber), int(y.originalTrackNumber))): | |||||
| return list.sort(self, fun) | |||||
| def genChildren(self): | |||||
| r = [ str(x['number']) for x in | |||||
| self.cuesheet['tracks_array'] if x['number'] not in | |||||
| (170, 255) ] | |||||
| #print 'gC:', `r` | |||||
| return r | |||||
| def findtrackidx(self, trk): | |||||
| for idx, i in enumerate(self.cuesheet['tracks_array']): | |||||
| if i['number'] == trk: | |||||
| return idx | |||||
| raise ValueError('track %d not found' % trk) | |||||
| @staticmethod | |||||
| def findindexintrack(trk, idx): | |||||
| for i in trk['indices_array']: | |||||
| if i['number'] == idx: | |||||
| return i | |||||
| raise ValueError('index %d not found in: %s' % (idx, trk)) | |||||
| def gettrackstart(self, i): | |||||
| idx = self.findtrackidx(i) | |||||
| track = self.cuesheet['tracks_array'][idx] | |||||
| index = self.findindexintrack(track, 1) | |||||
| return track['offset'] + index['offset'] | |||||
| def createObject(self, i, arg=None): | |||||
| '''This function returns the (class, name, *args, **kwargs) | |||||
| that will be passed to the addItem method of the | |||||
| ContentDirectory. arg will be passed the value of the dict | |||||
| keyed by i if genChildren is a dict.''' | |||||
| oi = i | |||||
| i = int(i) | |||||
| trkidx = self.findtrackidx(i) | |||||
| trkarray = self.cuesheet['tracks_array'] | |||||
| kwargs = self.kwargs.copy() | |||||
| start = self.gettrackstart(i) | |||||
| tags = self.tags | |||||
| #print 'tags:', `tags` | |||||
| kwargs['start'] = start | |||||
| kwargs['samples'] = trkarray[trkidx + 1]['offset'] - start | |||||
| #print 'track: %d, kwargs: %s' % (i, `kwargs`) | |||||
| kwargs['originalTrackNumber'] = i | |||||
| try: | |||||
| tinfo = self.cdtoc['tracks'][i] | |||||
| #print 'tinfo:', `tinfo` | |||||
| except KeyError: | |||||
| tinfo = {} | |||||
| if 'dtitle' in tags and ' / ' in tags['dtitle'][0]: | |||||
| kwargs['artist'], kwargs['album'] = tags['dtitle'][0].split(' / ') | |||||
| else: | |||||
| if 'TITLE' in self.cdtoc: | |||||
| kwargs['album'] = self.cdtoc['TITLE'] | |||||
| if 'PERFORMER' in tinfo: | |||||
| kwargs['artist'] = tinfo['PERFORMER'] | |||||
| tt = 'ttitle%d' % (i - 1) | |||||
| if tt in tags: | |||||
| if len(tags[tt]) != 1: | |||||
| # XXX - track this? | |||||
| print('hun? ttitle:', repr(tags[tt])) | |||||
| ttitle = tags[tt][0] | |||||
| if ' / ' in ttitle: | |||||
| kwargs['artist'], oi = ttitle.split(' / ') | |||||
| else: | |||||
| oi = ttitle | |||||
| else: | |||||
| if 'TITLE' in tinfo: | |||||
| oi = tinfo['TITLE'] | |||||
| #print 'title:', `oi` | |||||
| #print 'kwargs:', `kwargs` | |||||
| #print 'artist:', `kwargs['artist']` | |||||
| #import traceback | |||||
| #traceback.print_stack() | |||||
| return AudioRawTrack, oi, (), kwargs | |||||
| # XXX - figure out how to make custom mix-ins w/ other than AudioItem | |||||
| # metaclass? | |||||
| class AudioRawBase(FSObject): | |||||
| def __init__(self, *args, **kwargs): | |||||
| file = kwargs.pop('file') | |||||
| nchan = kwargs.pop('channels') | |||||
| samprate = kwargs.pop('samplerate') | |||||
| bitsps = kwargs.pop('bitspersample') | |||||
| samples = kwargs.pop('samples') | |||||
| startsamp = kwargs.pop('start', 0) | |||||
| tags = kwargs.pop('tags', {}) | |||||
| picts = kwargs.pop('pictures', {}) | |||||
| totalbytes = nchan * samples * bitsps / 8 | |||||
| FSObject.__init__(self, kwargs.pop('path')) | |||||
| #print 'AudioRaw:', `startsamp`, `samples` | |||||
| kwargs['content'] = cont = resource.Resource() | |||||
| cont.putChild('audio', AudioResource(file, | |||||
| kwargs.pop('decoder'), startsamp, samples)) | |||||
| self.baseObject.__init__(self, *args, **kwargs) | |||||
| if 'DYEAR' in tags: | |||||
| self.year = tags['DYEAR'][0] | |||||
| if 'DGENRE' in tags: | |||||
| self.genre = tags['DGENRE'][0] | |||||
| if 'DTITLE' in tags and ' / ' in tags['DTITLE'][0]: | |||||
| self.artist, self.album = tags['DTITLE'][0].split(' / ', 1) | |||||
| self.url = '%s/%s/audio' % (self.cd.urlbase, self.id) | |||||
| if 'cover' in picts: | |||||
| pict = picts['cover'][0] | |||||
| cont.putChild('cover', static.Data(pict[7], pict[1])) | |||||
| self.albumArtURI = '%s/%s/cover' % (self.cd.urlbase, | |||||
| self.id) | |||||
| self.res = ResourceList() | |||||
| r = Resource(self.url, 'http-get:*:%s:*' % makeaudiomt(bitsps, | |||||
| samprate, nchan)) | |||||
| r.size = totalbytes | |||||
| r.duration = float(samples) / samprate | |||||
| r.bitrate = nchan * samprate * bitsps / 8 | |||||
| r.sampleFrequency = samprate | |||||
| r.bitsPerSample = bitsps | |||||
| r.nrAudioChannels = nchan | |||||
| self.res.append(r) | |||||
| def doUpdate(self): | |||||
| print('dU:', repr(self), self.baseObject.doUpdate) | |||||
| print(self.__class__.__bases__) | |||||
| #import traceback | |||||
| #traceback.print_stack() | |||||
| self.baseObject.doUpdate(self) | |||||
| class AudioRaw(AudioRawBase, AudioItem): | |||||
| baseObject = AudioItem | |||||
| class AudioRawTrack(AudioRawBase, MusicTrack): | |||||
| baseObject = MusicTrack | |||||
| def detectaudioraw(origpath, fobj): | |||||
| for i in decoders.values(): | |||||
| try: | |||||
| obj = i(origpath) | |||||
| # XXX - don't support down sampling yet | |||||
| if obj.bitspersample not in (8, 16): | |||||
| continue | |||||
| args = { | |||||
| 'path': origpath, | |||||
| 'decoder': i, | |||||
| 'file': origpath, | |||||
| 'channels': obj.channels, | |||||
| 'samplerate': obj.samplerate, | |||||
| 'bitspersample': obj.bitspersample, | |||||
| 'samples': obj.totalsamples, | |||||
| 'tags': obj.tags, | |||||
| 'pictures': obj.pictures, | |||||
| } | |||||
| if obj.cuesheet is not None: | |||||
| try: | |||||
| args['toc'] = cdrtoc.parsetoc( | |||||
| obj.tags['cd.toc'][0]) | |||||
| except KeyError: | |||||
| pass | |||||
| except: | |||||
| import traceback | |||||
| print('WARNING: failed to parse toc:') | |||||
| traceback.print_exc() | |||||
| args['cuesheet'] = obj.cuesheet | |||||
| return AudioDisc, args | |||||
| return AudioRaw, args | |||||
| except: | |||||
| #import traceback | |||||
| #traceback.print_exc() | |||||
| pass | |||||
| return None, None | |||||
| registerklassfun(detectaudioraw, True) | |||||
| @@ -1,53 +0,0 @@ | |||||
| #!/usr/bin/python | |||||
| # | |||||
| # Small client for sending text to a socket and displaying the result. | |||||
| # | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| # Copyright 2005, Tim Potter <tpot@samba.org> | |||||
| from twisted.internet import reactor, error | |||||
| from twisted.internet.protocol import Protocol, ClientFactory | |||||
| class Send(Protocol): | |||||
| def connectionMade(self): | |||||
| self.transport.write('''POST /ContentDirectory/control HTTP/1.1\r | |||||
| Host: 192.168.126.1:80\r | |||||
| User-Agent: POSIX, UPnP/1.0, Intel MicroStack/1.0.1423\r | |||||
| SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"\r | |||||
| Content-Type: text/xml\r | |||||
| Content-Length: 511\r | |||||
| \r | |||||
| \r | |||||
| <?xml version="1.0" encoding="utf-8"?>\r | |||||
| <s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">\r | |||||
| <s:Body>\r | |||||
| <u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">\r | |||||
| <ObjectID>0\OnlineMedia\Internet radio\</ObjectID>\r | |||||
| <BrowseFlag>BrowseDirectChildren</BrowseFlag>\r | |||||
| <Filter>*</Filter>\r | |||||
| <StartingIndex>0</StartingIndex>\r | |||||
| <RequestedCount>7</RequestedCount>\r | |||||
| <SortCriteria></SortCriteria>\r | |||||
| </u:Browse>\r | |||||
| </s:Body>\r | |||||
| </s:Envelope>\r\n''') | |||||
| def dataReceived(self, data): | |||||
| print(data) | |||||
| def connectionLost(self, reason): | |||||
| if reason.type != error.ConnectionDone: | |||||
| print str(reason) | |||||
| reactor.stop() | |||||
| class SendFactory(ClientFactory): | |||||
| protocol = Send | |||||
| host = '192.168.126.128' | |||||
| port = 5643 | |||||
| reactor.connectTCP(host, port, SendFactory()) | |||||
| reactor.run() | |||||
| @@ -1,44 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| def checksum(n): | |||||
| ret = 0 | |||||
| while n > 0: | |||||
| n, m = divmod(n, 10) | |||||
| ret += m | |||||
| return ret | |||||
| def discid(tracks): | |||||
| '''Pass in a list of tuples. Each tuple must contain | |||||
| (minutes, seconds) of the track. The last tuple in the list should | |||||
| contain the start of lead out.''' | |||||
| last = len(tracks) - 1 | |||||
| tracksms = map(lambda x: x[0] * 60 + x[1], tracks) | |||||
| n = sum(map(checksum, tracksms[:-1])) | |||||
| t = tracksms[-1] - tracksms[0] | |||||
| discid = (long(n) % 0xff) << 24 | t << 8 | last | |||||
| ret = [ discid, last ] | |||||
| try: | |||||
| tracksframes = map(lambda x: x[0] * 60 * 75 + x[1] * 75 + x[2], tracks) | |||||
| ret.extend(tracksframes[:-1]) | |||||
| ret.append(tracksms[-1]) | |||||
| except IndexError: | |||||
| tracksframes = map(lambda x: x[0] * 60 * 75 + x[1] * 75, tracks) | |||||
| ret.extend(tracksframes[:-1]) | |||||
| ret.append(tracksms[-1]) | |||||
| return ret | |||||
| if __name__ == '__main__': | |||||
| tracks = [ (0, 3, 71), (5, 44, 0) ] | |||||
| tracksdiskinfo = [ 0x03015501, 1, 296, 344 ] | |||||
| diskinfo = discid(tracks) | |||||
| assert diskinfo == tracksdiskinfo | |||||
| print '%08x' % diskinfo[0], | |||||
| print ' '.join(map(str, diskinfo[1:])) | |||||
| @@ -1,139 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| import string | |||||
| types = frozenset([ 'CD_DA', 'CD_ROM', 'CD_ROMXA']) | |||||
| def decodestr(i, pos): | |||||
| return decodestrend(i, pos)[0] | |||||
| def decodestrend(i, pos): | |||||
| r = [] | |||||
| bspos = None | |||||
| dqpos = None | |||||
| while True: | |||||
| if bspos is None or bspos == -1 or bspos < pos: | |||||
| bspos = i.find('\\', pos) | |||||
| if dqpos is None or dqpos < pos: | |||||
| dqpos = i.index('"', pos) | |||||
| if bspos >= 0 and bspos < dqpos: | |||||
| r.append(i[pos:bspos]) | |||||
| c = i[bspos + 1] | |||||
| if c == '"': | |||||
| r.append('"') | |||||
| pos = bspos + 2 | |||||
| elif c in string.digits: | |||||
| r.append(chr(int(i[bspos + 1:bspos + 4], 8))) | |||||
| pos = bspos + 4 | |||||
| elif c == 'n': | |||||
| r.append('\n') | |||||
| pos = bspos + 2 | |||||
| else: | |||||
| raise ValueError('unknown escape char: %s' % repr(c)) | |||||
| else: | |||||
| r.append(i[pos:dqpos]) | |||||
| break | |||||
| return ''.join(r), dqpos | |||||
| def parsetoc(toc): | |||||
| # state machine info: | |||||
| # 0: header | |||||
| # 1: in CD_TEXT | |||||
| # 2: in LANGUAGE_MAP | |||||
| # 3: in LANGUAGE | |||||
| r = { 'tracks': {} } | |||||
| langmap = {} | |||||
| state = 0 | |||||
| curlang = None | |||||
| textobj = None | |||||
| langobj = None | |||||
| track = 0 | |||||
| for i in toc.split('\n'): | |||||
| i = i.strip() | |||||
| if not i: | |||||
| continue | |||||
| items = i.split() | |||||
| key = items[0] | |||||
| if state == 0: | |||||
| if i in types: | |||||
| r['type'] = i | |||||
| elif key == 'CATALOG': | |||||
| r['catalog'] = decodestr(i, i.index('"') + 1) | |||||
| elif key == 'CD_TEXT': | |||||
| state = 1 | |||||
| if track == 0: | |||||
| textobj = r | |||||
| elif key == 'TRACK': | |||||
| track += 1 | |||||
| textobj = { 'track': track } | |||||
| r['tracks'][track] = textobj | |||||
| elif key == 'TWO_CHANNEL_AUDIO': | |||||
| textobj['channels'] = 2 | |||||
| elif key == 'FOUR_CHANNEL_AUDIO': | |||||
| textobj['channels'] = 4 | |||||
| elif key == 'ISRC': | |||||
| textobj['isrc'] = decodestr(i, i.index('"') + 1) | |||||
| elif key == 'COPY': | |||||
| textobj['copy'] = True | |||||
| elif items[0] == 'NO' and items[1] == 'COPY': | |||||
| textobj['copy'] = False | |||||
| elif key == 'PRE_EMPHASIS': | |||||
| textobj['preemphasis'] = True | |||||
| elif items[0] == 'NO' and items[1] == 'PRE_EMPHASIS': | |||||
| textobj['preemphasis'] = False | |||||
| elif key == 'FILE': | |||||
| pass # XXX | |||||
| elif key == 'START': | |||||
| pass # XXX | |||||
| elif key == '//': | |||||
| pass | |||||
| else: | |||||
| raise ValueError('unknown line: %s' % repr(i)) | |||||
| elif state == 1: | |||||
| if key == 'LANGUAGE_MAP': | |||||
| state = 2 | |||||
| elif key == 'LANGUAGE': | |||||
| state = 3 | |||||
| langobj = textobj | |||||
| # XXX - don't try to use more than one! | |||||
| #lang = items[1].strip() | |||||
| #textobj[langmap[lang]] = langobj | |||||
| elif key == '}': | |||||
| textobj = None | |||||
| state = 0 | |||||
| elif state == 2: | |||||
| if key == '}': | |||||
| state = 1 | |||||
| else: | |||||
| key, value = (x.strip() for x in i.split(':')) | |||||
| value = int(value) | |||||
| langmap[key] = value | |||||
| elif state == 3: | |||||
| if key == '}': | |||||
| langobj = None | |||||
| state = 1 | |||||
| else: | |||||
| curl = i.find('{') | |||||
| dquo = i.find('"') | |||||
| if curl != -1 and curl < dquo: | |||||
| val = i[i.index('{') + 1:i.index('}')] | |||||
| val = ''.join(chr(int(x)) for x in | |||||
| val.split(',')) | |||||
| else: | |||||
| if dquo == -1: | |||||
| raise ValueError('no dquote') | |||||
| val = decodestr(i, dquo + 1) | |||||
| langobj[key] = val | |||||
| return r | |||||
| if __name__ == '__main__': | |||||
| import sys | |||||
| for i in sys.argv[1:]: | |||||
| print('file:', repr(i)) | |||||
| print(parsetoc(open(i).read())) | |||||
| @@ -1,67 +0,0 @@ | |||||
| #!/usr/bin/python | |||||
| # | |||||
| # Test harness for UPnP ContentDirectory:1 service. | |||||
| # | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| # Copyright 2005, Tim Potter <tpot@samba.org> | |||||
| from twisted.web import client | |||||
| from twisted.internet import reactor | |||||
| from twisted.python import usage | |||||
| import sys, string, SOAPpy | |||||
| class UPnPSOAPProxy: | |||||
| """A proxy for making UPnP SOAP calls.""" | |||||
| def __init__(self, url): | |||||
| self.url = url | |||||
| def _cbGotResult(self, result): | |||||
| return SOAPpy.parseSOAPRPC(result) | |||||
| def callRemote(self, method, *args, **kwargs): | |||||
| payload = SOAPpy.buildSOAP(args = args, kw = kwargs, method = method) | |||||
| payload = string.replace(payload, '\n', '\r\n') | |||||
| action = \ | |||||
| '"urn:schemas-upnp-org:service:ContentDirectory:1#%s"' % method | |||||
| page = client.getPage(self.url, postdata = payload, | |||||
| method = 'POST', headers = {'Content-Type': 'text/xml', | |||||
| 'SOAPACTION': action}) | |||||
| return page.addCallback(self._cbGotResult) | |||||
| class Options(usage.Options): | |||||
| pass | |||||
| def printResult(value): | |||||
| print value | |||||
| reactor.stop() | |||||
| def printError(error): | |||||
| print 'error', error | |||||
| reactor.stop() | |||||
| #proxy = UPnPSOAPProxy('http://192.168.126.128:5643/ContentDirectory/control') | |||||
| proxy = UPnPSOAPProxy('http://127.0.0.1:8080/ContentDirectory/control') | |||||
| #proxy = UPnPSOAPProxy('http://16.176.65.48:5643/ContentDirectory/control') | |||||
| #proxy.callRemote('GetSearchCapabilities').addCallbacks(printResult, printError) | |||||
| #proxy.callRemote('GetSortCapabilities').addCallbacks(printResult, printError) | |||||
| proxy.callRemote('Browse', | |||||
| ObjectID = '0\\Music\\Genres\\Others\\chimes.wav', | |||||
| #BrowseFlag = 'BrowseDirectChildren', | |||||
| BrowseFlag = 'BrowseMetadata', | |||||
| Filter = '*', | |||||
| StartingIndex = 0, | |||||
| RequestedCount = 700, | |||||
| SortCriteria = None).addCallbacks(printResult, printError) | |||||
| reactor.run() | |||||
| @@ -1 +0,0 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?><scpd xmlns="urn:schemas-upnp-org:service-1-0"><specVersion><major>1</major><minor>0</minor></specVersion><actionList><action><name>GetCurrentConnectionInfo</name><argumentList><argument><name>ConnectionID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable></argument><argument><name>RcsID</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_RcsID</relatedStateVariable></argument><argument><name>AVTransportID</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_AVTransportID</relatedStateVariable></argument><argument><name>ProtocolInfo</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_ProtocolInfo</relatedStateVariable></argument><argument><name>PeerConnectionManager</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_ConnectionManager</relatedStateVariable></argument><argument><name>PeerConnectionID</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable></argument><argument><name>Direction</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Direction</relatedStateVariable></argument><argument><name>Status</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_ConnectionStatus</relatedStateVariable></argument></argumentList></action><action><name>GetProtocolInfo</name><argumentList><argument><name>Source</name><direction>out</direction><relatedStateVariable>SourceProtocolInfo</relatedStateVariable></argument><argument><name>Sink</name><direction>out</direction><relatedStateVariable>SinkProtocolInfo</relatedStateVariable></argument></argumentList></action><action><name>GetCurrentConnectionIDs</name><argumentList><argument><name>ConnectionIDs</name><direction>out</direction><relatedStateVariable>CurrentConnectionIDs</relatedStateVariable></argument></argumentList></action></actionList><serviceStateTable><stateVariable sendEvents="no"><name>A_ARG_TYPE_ProtocolInfo</name><dataType>string</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_ConnectionStatus</name><dataType>string</dataType><allowedValueList><allowedValue>OK</allowedValue><allowedValue>ContentFormatMismatch</allowedValue><allowedValue>InsufficientBandwidth</allowedValue><allowedValue>UnreliableChannel</allowedValue><allowedValue>Unknown</allowedValue></allowedValueList></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_AVTransportID</name><dataType>i4</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_RcsID</name><dataType>i4</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_ConnectionID</name><dataType>i4</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_ConnectionManager</name><dataType>string</dataType></stateVariable><stateVariable sendEvents="yes"><name>SourceProtocolInfo</name><dataType>string</dataType></stateVariable><stateVariable sendEvents="yes"><name>SinkProtocolInfo</name><dataType>string</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_Direction</name><dataType>string</dataType><allowedValueList><allowedValue>Input</allowedValue><allowedValue>Output</allowedValue></allowedValueList></stateVariable><stateVariable sendEvents="yes"><name>CurrentConnectionIDs</name><dataType>string</dataType></stateVariable></serviceStateTable></scpd> | |||||
| @@ -1,7 +0,0 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?><scpd xmlns="urn:schemas-upnp-org:service-1-0"><specVersion><major>1</major><minor>0</minor></specVersion><actionList> | |||||
| <action><name>Browse</name><argumentList><argument><name>ObjectID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable></argument><argument><name>BrowseFlag</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_BrowseFlag</relatedStateVariable></argument><argument><name>Filter</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable></argument><argument><name>StartingIndex</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable></argument><argument><name>RequestedCount</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument><argument><name>SortCriteria</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable></argument><argument><name>Result</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable></argument><argument><name>NumberReturned</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument><argument><name>TotalMatches</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument><argument><name>UpdateID</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable></argument></argumentList></action> | |||||
| <action><name>GetSortCapabilities</name><argumentList><argument><name>SortCaps</name><direction>out</direction><relatedStateVariable>SortCapabilities</relatedStateVariable></argument></argumentList></action> | |||||
| <action><name>GetSystemUpdateID</name><argumentList><argument><name>Id</name><direction>out</direction><relatedStateVariable>SystemUpdateID</relatedStateVariable></argument></argumentList></action> | |||||
| <!-- <action><name>Search</name><argumentList><argument><name>ContainerID</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable></argument><argument><name>SearchCriteria</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_SearchCriteria</relatedStateVariable></argument><argument><name>Filter</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable></argument><argument><name>StartingIndex</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable></argument><argument><name>RequestedCount</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument><argument><name>SortCriteria</name><direction>in</direction><relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable></argument><argument><name>Result</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable></argument><argument><name>NumberReturned</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument><argument><name>TotalMatches</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable></argument><argument><name>UpdateID</name><direction>out</direction><relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable></argument></argumentList></action> --> | |||||
| <action><name>GetSearchCapabilities</name><argumentList><argument><name>SearchCaps</name><direction>out</direction><relatedStateVariable>SearchCapabilities</relatedStateVariable></argument></argumentList></action> | |||||
| </actionList><serviceStateTable><stateVariable sendEvents="no"><name>A_ARG_TYPE_BrowseFlag</name><dataType>string</dataType><allowedValueList><allowedValue>BrowseMetadata</allowedValue><allowedValue>BrowseDirectChildren</allowedValue></allowedValueList></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_SearchCriteria</name><dataType>string</dataType></stateVariable><stateVariable sendEvents="yes"><name>SystemUpdateID</name><dataType>ui4</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_Count</name><dataType>ui4</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_SortCriteria</name><dataType>string</dataType></stateVariable><stateVariable sendEvents="no"><name>SortCapabilities</name><dataType>string</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_Index</name><dataType>ui4</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_ObjectID</name><dataType>string</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_UpdateID</name><dataType>ui4</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_Result</name><dataType>string</dataType></stateVariable><stateVariable sendEvents="no"><name>SearchCapabilities</name><dataType>string</dataType></stateVariable><stateVariable sendEvents="no"><name>A_ARG_TYPE_Filter</name><dataType>string</dataType></stateVariable></serviceStateTable></scpd> | |||||
| @@ -1,74 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2006 John-Mark Gurney <jmg@funkthat.com> | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| from twisted.internet import reactor | |||||
| import twisted.internet.error | |||||
| # Make sure to update the global line when adding a new function | |||||
| __all__ = [ 'appendnamespace', 'insertnamespace', 'insertringbuf' ] | |||||
| DEFAULTRINGSIZE = 50 | |||||
| appendnamespace = lambda k, v: [] | |||||
| insertnamespace = lambda k, v: None | |||||
| insertringbuf = lambda k, l = DEFAULTRINGSIZE: None | |||||
| # This code from: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68429 | |||||
| class RingBuffer: | |||||
| def __init__(self,size_max): | |||||
| self.max = size_max | |||||
| self.data = [] | |||||
| def append(self,x): | |||||
| """append an element at the end of the buffer""" | |||||
| self.data.append(x) | |||||
| if len(self.data) == self.max: | |||||
| self.cur=0 | |||||
| self.__class__ = RingBufferFull | |||||
| def get(self): | |||||
| """ return a list of elements from the oldest to the newest""" | |||||
| return self.data | |||||
| class RingBufferFull: | |||||
| def __init__(self,n): | |||||
| raise "you should use RingBuffer" | |||||
| def append(self,x): | |||||
| self.data[self.cur]=x | |||||
| self.cur=(self.cur+1) % self.max | |||||
| def get(self): | |||||
| return self.data[self.cur:]+self.data[:self.cur] | |||||
| def doDebugging(opt): | |||||
| if not opt: | |||||
| return | |||||
| global insertnamespace, appendnamespace, insertringbuf | |||||
| def insertnamespace(k, v): | |||||
| assert isinstance(k, str) | |||||
| sf.namespace[k] = v | |||||
| def appendnamespace(k, v): | |||||
| try: | |||||
| sf.namespace[k].append(v) | |||||
| except KeyError: | |||||
| sf.namespace[k] = [ v ] | |||||
| def insertringbuf(k, l = DEFAULTRINGSIZE): | |||||
| insertnamespace(k, RingBuffer(l)) | |||||
| from twisted.manhole import telnet | |||||
| class Debug(telnet.Shell): | |||||
| def welcomeMessage(self): | |||||
| data = [ '', 'PyMedS Debugging Console', '', '' ] | |||||
| return '\r\n'.join(data) | |||||
| sf = telnet.ShellFactory() | |||||
| sf.protocol = Debug | |||||
| try: | |||||
| reactor.listenTCP(56283, sf) | |||||
| except twisted.internet.error.CannotListenError: | |||||
| print('WARNING: cannot bind to debugger port.') | |||||
| @@ -1,812 +0,0 @@ | |||||
| INTERNET DRAFT J. Cohen, Microsoft | |||||
| S. Aggarwal, Microsoft | |||||
| <draft-cohen-gena-p-base-01.txt> Y. Y. Goland, Microsoft | |||||
| Expires April, 2000 September 6, 2000 | |||||
| General Event Notification Architecture Base: Client to Arbiter | |||||
| Status of this memo | |||||
| This document is an Internet-Draft and is in full conformance with all | |||||
| provisions of Section 10 of RFC2026. | |||||
| Internet-Drafts are working documents of the Internet Engineering Task | |||||
| Force (IETF), its areas, and its working groups. Note that other | |||||
| groups may also distribute working documents as Internet-Drafts. | |||||
| Internet-Drafts are draft documents valid for a maximum of six months | |||||
| and may be updated, replaced, or obsoleted by other documents at any | |||||
| time. It is inappropriate to use Internet- Drafts as reference | |||||
| material or to cite them other than as "work in progress." | |||||
| The list of current Internet-Drafts can be accessed at | |||||
| http://www.ietf.org/ietf/1id-abstracts.txt | |||||
| The list of Internet-Draft Shadow Directories can be accessed at | |||||
| http://www.ietf.org/shadow.html. | |||||
| Please send comments to the SSDP mailing list. Subscription information | |||||
| for the SSDP mailing list is available at | |||||
| http://www.upnp.org/resources/ssdpmail.htm. | |||||
| Abstract | |||||
| This document provides for the ability to send and receive | |||||
| notifications using HTTP over TCP/IP and administratively scoped | |||||
| unreliable multicast UDP. Provisions are made for the use of | |||||
| intermediary arbiters, called subscription arbiters, which handle | |||||
| routing notifications to their intended destination. | |||||
| 1 Introduction | |||||
| This document provides for the sending of HTTP Notifications using | |||||
| administratively scoped unreliable Multicast UDP. Multicast UDP is | |||||
| useful in that it allows a single notification to be delivered to a | |||||
| potentially large group of recipients using only a single request. | |||||
| However administrative scoped unreliable Multicast UDP suffers from a | |||||
| number of problems including: how to route it, how to handle | |||||
| firewalling and how to deal with multiple scopes. This document only | |||||
| addresses the use of Multicast UDP within a single administrative scope | |||||
| and only countenances its use for scenarios where there is no | |||||
| notification proxy. | |||||
| In order to allow for notifications to be distributed beyond a single | |||||
| administrative multicast scope it is necessary to provide for relaying | |||||
| Cohen et al. [Page 1] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| arbiters. These arbiters, called subscription arbiters, are able to | |||||
| form, through an unspecified mechanism, relationships with other | |||||
| subscription arbiters in order to distribute notifications. This allows | |||||
| a client to send a single HTTP notification to its subscription arbiter | |||||
| and have that notification forwarded on to one or more recipients. It | |||||
| is the subscription arbiter, not the client, who is responsible for | |||||
| maintaining the list of potential recipients and distributing | |||||
| notifications to those recipients. | |||||
| In order for subscription arbiters to know to whom to distribute | |||||
| notifications clients who wish to receive notifications, known as | |||||
| subscribers, must subscribe to the subscription arbiter. | |||||
| 2 Changes | |||||
| 2.1 Since V00 | |||||
| [Ed. Note: Aren’t there times when the service needs to hear the | |||||
| subscriptions? When the identity of the subscriber will effect the | |||||
| output? Of course… Notification identifiers must be explicit in the | |||||
| notification, not implicit as a function of the address.] | |||||
| [Ed. Note: We need to look into adding support for sequence numbers on | |||||
| events, this is needed for things like UPnP. If this doesn't end up | |||||
| effecting how the arbiter works then we should put it into a separate | |||||
| draft.] | |||||
| [Talk about the scope header having values other than the URL of the | |||||
| resource, we are using the UPnP example where you would put the USN | |||||
| value into the scope. Everyone needs to pay attention to the scope | |||||
| header!!!!] | |||||
| [Add a note that if no Scope header is included then the default is to | |||||
| treat the Request-URI as the scope.] | |||||
| [What do you do if someone subscribes to something they are already | |||||
| subscribed to? The question is tricky because, short of using | |||||
| authentication, you don’t know who “someone” is and even then, because | |||||
| multiple programs may be running, you can’t be sure if you are really | |||||
| talking to the same person. My instinct would be to just give them a | |||||
| second parallel subscription.] | |||||
| [Think REALLY hard about allowing for multiple values in a NT header] | |||||
| 3 Definitions | |||||
| 3.1 Event | |||||
| Any occurrence that may potentially trigger a notification. | |||||
| 3.2 Subscription | |||||
| An established relationship in which a resource has indicated interest | |||||
| in receiving certain notifications. | |||||
| 3.3 Subscriber | |||||
| A resource that negotiates a subscription with a subscription arbiter. | |||||
| Cohen et al. [Page 2] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| 3.4 Implied Subscriber | |||||
| A resource that did not negotiate a subscription with a subscription | |||||
| arbiter but will still be notified of events on that arbiter. | |||||
| 3.5 Notifying Resource | |||||
| A resource that issues notifications, for example, a subscription | |||||
| arbiter. | |||||
| 3.6 Subscription Arbiter | |||||
| A resource that accepts subscriptions, receives notifications and | |||||
| forwards those notifications to its subscribers. | |||||
| 3.7 Notification | |||||
| A message sent by a notifying resource to provide notification of an | |||||
| event. | |||||
| 3.8 Notification Type | |||||
| A mechanism to classify notifications into categories. This allows | |||||
| subscribers to specify exactly what class of notifications they want to | |||||
| receive. It also allows notifying resources to specify what class of | |||||
| notification they are sending out. | |||||
| Notification types do not necessarily identify a single event but | |||||
| rather identify a group of related notifications. The notification sub- | |||||
| type is used to specify a particular event. | |||||
| 3.9 Notification Sub-Type | |||||
| Identification of a particular event within a notification type. | |||||
| For example, the fictional notification of type home:doors may contain | |||||
| notification sub-types such as door:open, close:door, etc. | |||||
| There is no requirement that the URI identifying the notification type | |||||
| and the URI identifying the notification sub-type have a syntactic | |||||
| relationship, only a semantic one. | |||||
| 3.10 Subscription ID | |||||
| A unique identifier for a subscription. Subscription Ids MUST be URIs | |||||
| and MUST be unique across all subscriptions across all resources for | |||||
| all time. | |||||
| 3.11 Scope | |||||
| Scopes are used in a subscription to indicate the notifying resource | |||||
| the subscriber is interested in. | |||||
| Cohen et al. [Page 3] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| 4 Notification Model | |||||
| [Ed. Note: Aren’t there times when the service needs to hear the | |||||
| subscriptions? When the identity of the subscriber will effect the | |||||
| output? Of course… Notification identifiers must be explicit in the | |||||
| notification, not implicit as a function of the address.] | |||||
| [Ed. Note: We need to look into adding support for sequence numbers on | |||||
| events, this is needed for things like UPnP. If this doesn't end up | |||||
| effecting how the arbiter works then we should put it into a separate | |||||
| draft.] | |||||
| The notification model for GENA is based on the following picture: | |||||
| [Subscriber] <- [1+ Subscription Arbiters] <- [Notifying Resource] | |||||
| Notification Request Notification Request | |||||
| -> | |||||
| Subscription Request | |||||
| Subscribers send subscription requests to their subscription arbiter. | |||||
| The arbiter will then forward to the subscriber any notifications it | |||||
| receives which match the subscriber's subscription. | |||||
| Notifying resources send notifications to their subscription arbiter to | |||||
| be passed on to subscribers. | |||||
| Subscription arbiters communicate with each other in order to pass | |||||
| along notifications and subscriptions. Subscription arbiter to | |||||
| subscription arbiter communication is out of scope for this | |||||
| specification. | |||||
| For the purposes of this protocol all communication is between | |||||
| subscribers/notifying resources and their subscription arbiter. This | |||||
| does not preclude direct communication between a subscriber and a | |||||
| notifying resource. Rather it means that the notifying resource is | |||||
| acting as a subscription arbiter. | |||||
| This document also deals with a degenerate case where no subscription | |||||
| arbiter is available but administratively scoped unreliable multicast | |||||
| UDP facilities are. In that case provisions are made to allow a | |||||
| notifying resource to send its notifications directly to a previously | |||||
| agreed upon administratively scoped multicast UDP address where | |||||
| interested resources can listen in to the notification. | |||||
| 4.1 Sending HTTP Notifications through a Subscription Arbiter | |||||
| A notifying resource finds its subscription arbiter through an | |||||
| unspecified mechanism. The notifying resource will send all of its | |||||
| notifications to the subscription arbiter who will then forward those | |||||
| subscriptions on to subscribers. | |||||
| Cohen et al. [Page 4] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| This document does not provide a mechanism by which the notifying | |||||
| resource can retrieve information about which resources have subscribed | |||||
| to receive notifications from the notifying resource. | |||||
| 4.2 Receiving HTTP Notifications through a Subscription Arbiter | |||||
| A subscribing resource finds its subscription arbiter through an | |||||
| unspecified mechanism. It is the responsibility of the subscribing | |||||
| resource to send subscription requests to the subscription arbiter in | |||||
| order to inform the arbiter as to which notifications the subscriber | |||||
| would like to receive. | |||||
| A subscription request can be thought of as a persistent search filter | |||||
| on the set of notifications that the subscription arbiter is aware of. | |||||
| Whenever the subscription arbiter receives a notification that matches | |||||
| the search filter it will forward the notification to the subscriber. | |||||
| This document defines a very basic search filter that allows a | |||||
| subscribing resource to specify a particular resource and a type of | |||||
| notification the subscribing resource is interested in. Whenever a | |||||
| notification of the specified type is made by the specified resource | |||||
| the subscription arbiter will forward the notification to the | |||||
| subscriber. | |||||
| 5 Subscription Arbiters and Forwarded Notifications | |||||
| When forwarding a notification the subscription arbiter will change the | |||||
| Request-URI and the Host header value to match the subscriber who is to | |||||
| be notified. Subscription arbiters MUST NOT make any other changes to | |||||
| be made to the message unless the definition of the header or body | |||||
| element specifically provides for such alteration and/or for security | |||||
| reasons. | |||||
| 6 Stuff | |||||
| In the case where a subscriber sends a subscription request to an | |||||
| arbiter: Is it OK if the arbiter rejects subscription requests that | |||||
| don't match certain notification type criteria? or should the arbiter | |||||
| be totally unaware of any notification types at this point? | |||||
| In the case where a notifying resource sends a notify request to an | |||||
| arbiter: Is it OK if the arbiter rejects notification requests that | |||||
| don't match certain notification type criteria? or should the arbiter | |||||
| just accept the notification request and filter the available | |||||
| subscriptions taking the notification type as criteria? | |||||
| In the hypothetical case where the arbiter just accepted subscriptions | |||||
| of certain types, could an arbiter just be dedicated to one | |||||
| subscription type? If the previous statement was affirmative then the | |||||
| existence of a notification type for that particular arbiter wouldn't | |||||
| even make sense right? | |||||
| We need to discuss the implicit relationship between an arbiter and its | |||||
| subscribers, all the unstated assumptions. | |||||
| Cohen et al. [Page 5] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| The subscription server may not necessarily have been the one who | |||||
| created a SID, it could have been the service who is using the | |||||
| subscription server to do replication. In that case a subscription | |||||
| request could come in with a SID on it, a ref-counted SID. But this bug | |||||
| me because a SID indicates a relationship. How does one change one’s | |||||
| call back for a SID? Especially if you want to send in the change | |||||
| command from somewhere other than the place receiving the notification | |||||
| so you can’t just check the address on the packet. Do we need tow types | |||||
| of SIDs? I don’t just want to overload NT. I think NT and NTS should be | |||||
| left alone to just give notification type. They shouldn’t be overloaded | |||||
| to also provide functional information… functional is the wrong word. | |||||
| They shouldn’t be overloaded to provide routing information. | |||||
| Event type. | |||||
| Event Sub-type. | |||||
| Event group (if any). | |||||
| Individual subscription. | |||||
| The problem is that the last two are not always both necessary. For | |||||
| example, an individual subscription ID is not necessary if the events | |||||
| are only sent to a multicast group. Additionally the event group ID | |||||
| isn’t necessary if the event only goes to a single end point. | |||||
| Maybe we need an end point identifier? We need a way to say “Stop | |||||
| sending the events to this call-back, send it to this other call-back.” | |||||
| THIS ISN’T GOOD ENOUGH. What if two programs are both at the same call- | |||||
| back? You need ref counting. | |||||
| I guess what I’m trying to avoid is implicit ref-counting, I would | |||||
| rather have explicit ref-counting. That is, I would like the ref count | |||||
| to be included in each request in the person of an ID that is unique to | |||||
| every listener, this is independent of call-back address. | |||||
| Make sure that if you leave off the NT then this means you want to | |||||
| receive all notifications from the identified scope. | |||||
| Also make sure that we can use scope as a selector for video streams on | |||||
| a video server. | |||||
| We need to add a “connect and flood” mechanism such that if you connect | |||||
| to a certain TCP port you will get events. There is no | |||||
| subscribe/unsubscribe. We also need to discuss this feature for | |||||
| multicasting. If you cut the connection then you won’t get any more | |||||
| events. | |||||
| 7 NOTIFY HTTP Method | |||||
| The NOTIFY method is used to transmit a notification. The Request-URI | |||||
| of the notification method is the notifying resource's subscription | |||||
| arbiter who will handle forwarding the message to interested | |||||
| subscribers. | |||||
| The NOTIFY method may be sent using unreliable administrative scoped | |||||
| multicast UDP as specified in [HTTPMU]. In such a case the multicast | |||||
| Cohen et al. [Page 6] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| channel is treated as the subscription arbiter. When a NOTIFY is sent | |||||
| using multicast UDP as a transport there is no response. | |||||
| The NOTIFY method MUST contain a NT header and MAY contain a body, a | |||||
| NTS header and SID. Subscribers MAY ignore the body in a subscription | |||||
| request. Subscription arbiters MAY remove and/or alter the value of the | |||||
| SID header in order to set it to the value that their subscriber is | |||||
| expecting. Note that in general notifying resources will not put SID | |||||
| headers on their notifications. This is generally a value that | |||||
| subscription arbiters add. | |||||
| Note that notifications to implied subscribers may not necessarily have | |||||
| SIDs. The client can tell the subscription arbiter to stop sending the | |||||
| notification by returning a 412 (Precondition Failed). | |||||
| 7.1 Response Codes | |||||
| 200 (OK) - This is the standard response to a NOTIFY received by a | |||||
| subscriber. | |||||
| 202 (Accepted) - This is the standard response to a NOTIFY received by | |||||
| a subscription arbiter. | |||||
| 412 (Precondition Failed) - The client doesn't recognize the SID or the | |||||
| request doesn't have a SID and the client doesn't want to receive the | |||||
| notification. | |||||
| 7.2 Examples | |||||
| 7.2.1 TCP/IP | |||||
| NOTIFY /foo/bar HTTP/1.1 | |||||
| Host: blah:923 | |||||
| NT: ixl:pop | |||||
| NTS: clock:bark | |||||
| SID: uuid:kj9d4fae-7dec-11d0-a765-00a0c91e6bf6 | |||||
| HTTP/1.1 200 O.K. | |||||
| A notification of type ixl:pop sub-type clock:bark has been sent out in | |||||
| response to the specified subscription. The request-URI could either | |||||
| identify a particular resource who is to be notified or a subscription | |||||
| arbiter who will then take responsibility for forwarding the | |||||
| notification to the appropriate subscribers. | |||||
| 7.2.2 Multicast UDP | |||||
| NOTIFY * HTTP/1.1 | |||||
| Host: somemulticastIPaddress:923 | |||||
| NT: ixl:pop | |||||
| NTS: clock:bark | |||||
| Cohen et al. [Page 7] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| As in the previous example this is a notification of type ixl:pop sub- | |||||
| type clock:bark but it has been sent out to the multicast channel as an | |||||
| unsolicited notification. Hence it does not have a SID header. Also, | |||||
| because it was sent out to a multicast UDP channel it also doesn’t have | |||||
| a response. | |||||
| 8 SUBSCRIBE HTTP Method | |||||
| [Talk about the scope header having values other than the URL of the | |||||
| resource, we are using the UPnP example where you would put the USN | |||||
| value into the scope. Everyone needs to pay attention to the scope | |||||
| header!!!!] | |||||
| The SUBSCRIBE method is used to provide a subscription arbiter with a | |||||
| search filter to be used in determining what notifications to forward | |||||
| to the subscriber. | |||||
| The Request-URI of the SUBSCRIBE method specifies the subscription | |||||
| arbiter which will handle the subscription. | |||||
| A SUBSCRIBE request MUST have a NT header unless it is a re- | |||||
| subscription request. The NT header specifies what sort of notification | |||||
| the subscriber wishes to be notified of. | |||||
| A SUBSCRIBE request MUST have a Call-Back header unless it is a re- | |||||
| subscription request. The Call-Back header specifies how the subscriber | |||||
| is to be contacted in order to deliver the notification. | |||||
| A SUBSCRIBE method MUST NOT have a NTS header. The base subscription | |||||
| search filter only supports filtering on the NT value of a | |||||
| notification. This limitation is meant to keep the subscription | |||||
| functionality at the minimum useful level. It is expected that future | |||||
| specifications will provide for more flexible subscription search | |||||
| filters. | |||||
| A SUBSCRIBE method MUST have a scope header unless it is a re- | |||||
| subscription request. The scope header identifies the resource that the | |||||
| subscriber wishes to receive notifications about. | |||||
| The Timeout request header, whose syntax is defined in section 9.8 of | |||||
| [WEBDAV] MAY be used on a SUBSCRIBE request. The header is used to | |||||
| request that a subscription live for the specified period of time | |||||
| before having to be renewed. Subscription arbiters are free to ignore | |||||
| this header. | |||||
| A subscription arbiter MUST ignore the body of a SUBSCRIBE request if | |||||
| it does not understand that body. | |||||
| A successful response to the SUBSCRIBE method MUST include a Timeout | |||||
| response header and a SUBID header. | |||||
| Cohen et al. [Page 8] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| [Add a note that if no Scope header is included then the default is to | |||||
| treat the Request-URI as the scope.] | |||||
| [What do you do if someone subscribes to something they are already | |||||
| subscribed to? The question is tricky because, short of using | |||||
| authentication, you don’t know who “someone” is and even then, because | |||||
| multiple programs may be running, you can’t be sure if you are really | |||||
| talking to the same person. My instinct would be to just give them a | |||||
| second parallel subscription.] | |||||
| 8.1 Re-Subscribing | |||||
| When the period of time specified in the Timeout response header passes | |||||
| the subscription MAY expire. In order to keep the subscription alive | |||||
| the subscriber MUST issue a SUBSCRIBE method with a SID header set to | |||||
| the subscription to be re-subscribed. A re-subscribe request MUST NOT | |||||
| have a NT header but it MAY have a Timeout and/or a Call-Back header. | |||||
| Note that the value in the Timeout response header will not take into | |||||
| account the time needed from when the value was generated until it was | |||||
| passed through the arbiter, put on the wire, sent to the subscriber, | |||||
| parsed by the subscriber’s system and finally passed to the | |||||
| subscriber’s program. Hence the value should be taken as an absolute | |||||
| upper bound. Subscribers are encouraged to re-subscribe a good period | |||||
| of time before the actual expiration of the subscription. | |||||
| 8.2 Response Codes | |||||
| 200 (OK) - The subscription request has been successfully processed and | |||||
| a subscription ID assigned. | |||||
| 400 (Bad Request) - A required header is missing. | |||||
| 412 Precondition Failed - Either the subscription arbiter doesn't | |||||
| support any of the call-backs, doesn't support the NT or doesn't | |||||
| support the scope. This code is also used on resubscription requests to | |||||
| indicate that the subscription no longer exists. | |||||
| 8.3 Examples | |||||
| 8.3.1 Subscription | |||||
| SUBSCRIBE dude HTTP/1.1 | |||||
| Host: iamthedude:203 | |||||
| NT: ixl:pop | |||||
| Call-Back: <http://blah/bar:923> | |||||
| Scope: http://icky/pop | |||||
| Timeout: Infinite | |||||
| HTTP/1.1 200 O.K. | |||||
| Subscription-ID: uuid:kj9d4fae-7dec-11d0-a765-00a0c91e6bf6 | |||||
| Timeout: Second-604800 | |||||
| Cohen et al. [Page 9] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| This subscription request asks the subscription arbiter | |||||
| http://iamthedude/dude:203 for a subscription on notifications of type | |||||
| ixl:pop from the resource http://icky/pop. | |||||
| 8.3.2 Re-Subscription | |||||
| SUBSCRIBE dude HTTP/1.1 | |||||
| Host: iamthedude:203 | |||||
| Subscription-ID: uuid:kj9d4fae-7dec-11d0-a765-00a0c91e6bf6 | |||||
| Timeout: Infinite | |||||
| HTTP/1.1 200 O.K. | |||||
| Subscription-ID: uuid:kj9d4fae-7dec-11d0-a765-00a0c91e6bf6 | |||||
| Timeout: Second-604800 | |||||
| The subscription has been successfully renewed. | |||||
| 9 UNSUBSCRIBE HTTP Method | |||||
| The UNSUBSCRIBE method is used to terminate a subscription. The | |||||
| UNSUBSCRIBE method MUST include a SID header with the value of the | |||||
| subscription to be un-subscribed. | |||||
| If the SID identifies a subscription that the subscription arbiter does | |||||
| not recognize or knows is already expired then the arbiter MUST respond | |||||
| with a 200 (OK). | |||||
| 9.1 Response Codes | |||||
| 200 (OK) - Unsubscribe succeeded. | |||||
| 412 (Precondition Failed) – The SID was not recognized. | |||||
| 9.2 Example | |||||
| UNSUBSCRIBE dude HTTP/1.1 | |||||
| Host: iamtheproxy:203 | |||||
| SID: uuid:kj9d4fae-7dec-11d0-a765-00a0c91e6bf6 | |||||
| HTTP/1.1 200 O.k. | |||||
| 10 New HTTP Headers | |||||
| 10.1 NT Header | |||||
| [Think REALLY hard about allowing for multiple values in a NT header] | |||||
| The NT header is used to indicate the notification type. | |||||
| NT = "NT" ":" absoluteURI ; See section 3 of [RFC2396] | |||||
| Cohen et al. [Page 10] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| 10.2 NTS Response Header | |||||
| The NTS response header is used to indicate the notification sub-type | |||||
| of a notification. | |||||
| NTS = "NTS" ":" absoluteURI | |||||
| 10.3 Call-Back Header | |||||
| The Call-Back header specifies, in order of preference, the means the | |||||
| subscriber would like the subscription arbiter to use to deliver | |||||
| notifications. | |||||
| Call-Back = "Call-Back" ":" *Coded-URI; See section 9.4 of [WEBDAV] | |||||
| 10.4 Timeout Response Header | |||||
| The Timeout response header has the same syntax as the Timeout request | |||||
| header defined in section 9.8 of [WEBDAV]. The subscription arbiter | |||||
| informs the subscriber how long the subscription arbiter will keep | |||||
| their subscription active without a re-subscribe using the Timeout | |||||
| response header. | |||||
| 10.5 SID Header | |||||
| The SID header contains a subscription ID. | |||||
| SID = "SID" ":" absoluteURI | |||||
| 10.6 Scope Request Header | |||||
| The scope request header indicates the resource the subscriber wishes | |||||
| to receive notifications about. | |||||
| SCOPE = "Scope" ":" absoluteURI | |||||
| 11 Future Work | |||||
| This specification defines a minimally useful set of notification | |||||
| functionality. It does not, however, address three critical issues that | |||||
| are needed by some notification environments. It is expected that all | |||||
| of these features can be provided in extension specifications to this | |||||
| base specification. | |||||
| The first issue is polling. In some environments, especially those with | |||||
| intermittent connectivity, it would be desirable for subscription | |||||
| arbiters to be able to pool up notifications and then to deliver them | |||||
| when the subscriber asks for them. | |||||
| The second issue is subscription arbiter to subscription arbiter | |||||
| communication. It is likely that subscription arbiters will want to | |||||
| communicate directly with each other in order to efficiently distribute | |||||
| Cohen et al. [Page 11] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| notifications and subscriptions. This requires provision for | |||||
| notification routing and loop prevention. | |||||
| The third issue is support for depth functionality. In some systems one | |||||
| wishes to receive a notification about a resource and any of its | |||||
| children as the term is defined in [WEBDAV]. | |||||
| 12 Security Considerations | |||||
| TBD. | |||||
| [Notes: | |||||
| The really horrible security concerns don't start until you build the | |||||
| subscription arbiter to arbiter protocol. Otherwise the arbiter is very | |||||
| close to a proxy in that it takes confidential information from a | |||||
| subscriber and/or notifying resource and is expected to do the right | |||||
| thing (TM) with it. Authentication and such prevents bogus | |||||
| notifications and subscriptions. | |||||
| Another problem is if someone sends in a subscription request with the | |||||
| call-back pointing at someone else. This could be used for a denial of | |||||
| service attack. Arbiters should authenticate subscribers and probably | |||||
| the call-back points as well.] | |||||
| 13 Acknowledgements | |||||
| Jesus Ruiz-Scougall (Exchange) | |||||
| Erik Christensen (VB) (Exchange) | |||||
| Ting Cai | |||||
| 14 IANA Considerations | |||||
| None. | |||||
| 15 Copyright | |||||
| The following copyright notice is copied from RFC 2026 [Bradner, 1996], | |||||
| Section 10.4, and describes the applicable copyright for this document. | |||||
| Copyright (C) The Internet Society February 10, 1998. All Rights | |||||
| Reserved. | |||||
| This document and translations of it may be copied and furnished to | |||||
| others, and derivative works that comment on or otherwise explain it or | |||||
| assist in its implementation may be prepared, copied, published and | |||||
| distributed, in whole or in part, without restriction of any kind, | |||||
| provided that the above copyright notice and this paragraph are | |||||
| included on all such copies and derivative works. However, this | |||||
| document itself may not be modified in any way, such as by removing the | |||||
| copyright notice or references to the Internet Society or other | |||||
| Internet organizations, except as needed for the purpose of developing | |||||
| Internet standards in which case the procedures for copyrights defined | |||||
| in the Internet Standards process must be followed, or as required to | |||||
| translate it into languages other than English. | |||||
| Cohen et al. [Page 12] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| The limited permissions granted above are perpetual and will not be | |||||
| revoked by the Internet Society or its successors or assignees. | |||||
| This document and the information contained herein is provided on an | |||||
| "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING | |||||
| TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT | |||||
| NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL | |||||
| NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR | |||||
| FITNESS FOR A PARTICULAR PURPOSE. | |||||
| 16 Intellectual Property | |||||
| The following notice is copied from RFC 2026 [Bradner, 1996], Section | |||||
| 10.4, and describes the position of the IETF concerning intellectual | |||||
| property claims made against this document. | |||||
| The IETF takes no position regarding the validity or scope of any | |||||
| intellectual property or other rights that might be claimed to pertain | |||||
| to the implementation or use other technology described in this | |||||
| document or the extent to which any license under such rights might or | |||||
| might not be available; neither does it represent that it has made any | |||||
| effort to identify any such rights. Information on the IETF's | |||||
| procedures with respect to rights in standards-track and standards- | |||||
| related documentation can be found in BCP-11. Copies of claims of | |||||
| rights made available for publication and any assurances of licenses to | |||||
| be made available, or the result of an attempt made to obtain a general | |||||
| license or permission for the use of such proprietary rights by | |||||
| implementers or users of this specification can be obtained from the | |||||
| IETF Secretariat. | |||||
| The IETF invites any interested party to bring to its attention any | |||||
| copyrights, patents or patent applications, or other proprietary rights | |||||
| which may cover technology that may be required to practice this | |||||
| standard. Please address the information to the IETF Executive | |||||
| Director. | |||||
| 17 References | |||||
| [WEBDAV] | |||||
| [Bradner, 1996] S. Bradner, "The Internet Standards Process - Revision | |||||
| 3." RFC 2026, BCP 9. Harvard University. October, 1996. | |||||
| [HTTPMU] <draft-goland-http-udp-00.txt> | |||||
| [HTTP11] R. Fielding, J. Gettys, J. C. Mogul, H. Frystyk, L. Masinter, | |||||
| P. Leach and T. Berners-Lee. Hypertext Transfer Protocol – HTTP/1.1. | |||||
| Internet Draft – Work in Progress. http://www.ietf.org/internet- | |||||
| drafts/draft-ietf-http-v11-spec-rev-06.txt, November 18, 1998. | |||||
| [RFC2396] http://www.rfc-editor.org/rfc/rfc2396.txt | |||||
| Cohen et al. [Page 13] | |||||
| INTERNET-DRAFT GENA Base September 6, 2000 | |||||
| 18 Authors' Addresses | |||||
| Josh Cohen, Sonu Aggarwal, Yaron Y. Goland | |||||
| Microsoft Corporation | |||||
| One Microsoft Way | |||||
| Redmond, WA 98052-6399 | |||||
| Email: {joshco,sonuag,yarong}@microsoft.com | |||||
| Cohen et al. [Page 14] | |||||
| @@ -1,348 +0,0 @@ | |||||
| UPnP Forum Technical Committee Yaron Y. Goland | |||||
| Document: draft-goland-fxpp-01.txt CrossGain | |||||
| Jeffrey C. Schlimmer | |||||
| Microsoft Corporation | |||||
| 19 June 2000 | |||||
| Flexible XML Processing Profile (FXPP) | |||||
| Status of this Memo | |||||
| This document is under review by the UPnP Forum Technical Committee. | |||||
| It was previously submitted to the IETF as an Internet Draft and has | |||||
| expired. This document is formatted in a manner consistent with the | |||||
| IETF formatting guidelines to facilitate possible future | |||||
| consideration by the IETF. | |||||
| This document is available on http://www.upnp.org. | |||||
| Abstract | |||||
| This document provides an independent reference for the XML | |||||
| processing profile developed by the WebDAV WG in [RFC2518]. It does | |||||
| this by copying Section 14 and Appendix 4 as well as examples from | |||||
| Appendix 3 of [RFC2518] and editing out any WebDAV specific parts. | |||||
| This document also defines handling of unknown XML attributes. | |||||
| This information has been broken out into its own independent | |||||
| reference in order to make it easier for other standards to | |||||
| reference just the WebDAV XML processing profile without having to | |||||
| reference the entire WebDAV standard or require their readers to | |||||
| understand which parts of the profile are WebDAV specific and which | |||||
| parts are not. | |||||
| 1. Introduction | |||||
| This document provides an independent reference for the XML | |||||
| processing profile developed by the WebDAV WG in [RFC2518]. It does | |||||
| this by copying Section 14 and Appendix 4 as well as examples from | |||||
| Appendix 3 of [RFC2518] and editing out any WebDAV specific parts. | |||||
| This document also defines handling of unknown XML attributes. | |||||
| This information has been broken out into its own independent | |||||
| reference in order to make it easier for other standards to | |||||
| reference just the WebDAV XML processing profile without having to | |||||
| reference the entire WebDAV standard or require their readers to | |||||
| understand which parts of the profile are WebDAV specific and which | |||||
| parts are not. | |||||
| Goland, Schlimmer 1 | |||||
| UPnP Forum FXPP 19 June 2000 | |||||
| The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", | |||||
| "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in | |||||
| this document are to be interpreted as described in [RFC2119]. | |||||
| 2. XML Support Requirement | |||||
| All FXPP compliant systems MUST support [XML]. | |||||
| 3. XML Ignore Rule | |||||
| All FXPP compliant XML processors (a) MUST ignore any unknown XML | |||||
| element and all its children, and (b) MUST ignore any unknown XML | |||||
| attribute and its value. This rule may be overridden on an element- | |||||
| by-element or attribute-by-attribute basis, but implementers should | |||||
| be aware that systems unfamiliar with the element or attribute will | |||||
| follow the ignore rule. | |||||
| 4. XML Mime Type Support | |||||
| A FXPP compliant system MUST be able to both accept and send | |||||
| text/xml and application/xml. | |||||
| 5. Ordering of XML Elements | |||||
| Unless the definition of a XML element explicitly specifies | |||||
| otherwise the ordering of XML elements has no semantic significance | |||||
| to FXPP compliant systems. | |||||
| Note to Implementers - A generic FXPP compliant XML processor will | |||||
| not know which of the elements it is processing have meaningful | |||||
| ordering. As such, such processors need to maintain the order of | |||||
| the elements when presenting the parsed information so as not to | |||||
| loose any meaningful data. | |||||
| 6. XML Namespace Support | |||||
| All FXPP compliant systems MUST support the XML namespace extensions | |||||
| as specified in [REC-XML-NAMES]. | |||||
| FXPP compliant XML processors MUST interpret a qualified name as a | |||||
| URI constructed by appending the LocalPart to the namespace name | |||||
| URI. | |||||
| Example | |||||
| <del:glider xmlns:del="http://www.del.jensen.org/"> | |||||
| <del:glidername> | |||||
| Johnny Updraft | |||||
| </del:glidername> | |||||
| <del:glideraccidents/> | |||||
| </del:glider> | |||||
| Goland, Schlimmer 2 | |||||
| UPnP Forum FXPP 19 June 2000 | |||||
| In this example, the qualified element name "del:glider" is | |||||
| interpreted as the URL "http://www.del.jensen.org/glider". | |||||
| <bar:glider xmlns:bar="http://www.del.jensen.org/"> | |||||
| <bar:glidername> | |||||
| Johnny Updraft | |||||
| </bar:glidername> | |||||
| <bar:glideraccidents/> | |||||
| </bar:glider> | |||||
| Even though this example is syntactically different from the | |||||
| previous example, it is semantically identical. Each instance of | |||||
| the namespace name "bar" is replaced with | |||||
| "http://www.del.jensen.org/" and then appended to the local name for | |||||
| each element tag. The resulting tag names in this example are | |||||
| exactly the same as for the previous example. | |||||
| <foo:r xmlns:foo="http://www.del.jensen.org/glide"> | |||||
| <foo:rname> | |||||
| Johnny Updraft | |||||
| </foo:rname> | |||||
| <foo:raccidents/> | |||||
| </foo:r> | |||||
| This example is semantically identical to the two previous ones. | |||||
| Each instance of the namespace name "foo" is replaced with | |||||
| "http://www.del.jensen.org/glide" which is then appended to the | |||||
| local name for each element tag, the resulting tag names are | |||||
| identical to those in the previous examples. | |||||
| 7. XML Element Declaration Syntax | |||||
| The following format is recommended for FXPP compliant | |||||
| specifications as a means to provide uniform declaration of XML | |||||
| elements. | |||||
| Name: | |||||
| Namespace: | |||||
| Purpose: | |||||
| Value: | |||||
| DTD | |||||
| The name is the name of the XML element. The Namespace is the | |||||
| namespace the element belongs to. The purpose is a short | |||||
| description of the use of the XML element. As DTDs are not very | |||||
| good at expressing the format of characters inside of an XML element | |||||
| when an XML element needs to contain formatted pcdata the optional | |||||
| Value description will be used to provide a BNF for the character | |||||
| data. At the end of the template is the ELEMENT DTD declaration for | |||||
| the element. | |||||
| 8. Notes on Empty XML Elements | |||||
| Goland, Schlimmer 3 | |||||
| UPnP Forum FXPP 19 June 2000 | |||||
| XML supports two mechanisms for indicating that an XML element does | |||||
| not have any content. The first is to declare an XML element of the | |||||
| form <A></A>. The second is to declare an XML element of the form | |||||
| <A/>. The two XML elements are semantically identical. | |||||
| It is a violation of the XML specification to use the <A></A> form | |||||
| if the associated DTD declares the element to be EMPTY (e.g., | |||||
| <!ELEMENT A EMPTY>). If such a statement is included, then the | |||||
| empty element format, <A/> must be used. If the element is not | |||||
| declared to be EMPTY, then either form <A></A> or <A/> may be used | |||||
| for empty elements. | |||||
| 9. Notes on Illegal XML Processing | |||||
| XML is a flexible data format that makes it easy to submit data that | |||||
| appears legal but in fact is not. The philosophy of "Be flexible in | |||||
| what you accept and strict in what you send" still applies, but it | |||||
| must not be applied inappropriately. XML is extremely flexible in | |||||
| dealing with issues of white space, element ordering, inserting new | |||||
| elements, etc. This flexibility does not require extension, | |||||
| especially not in the area of the meaning of elements. | |||||
| There is no kindness in accepting illegal combinations of XML | |||||
| elements. At best it will cause an unwanted result and at worst it | |||||
| can cause real damage. | |||||
| 9.1. Example - XML Syntax Error | |||||
| The following request body for a WebDAV PROPFIND method is illegal. | |||||
| <?xml version="1.0" encoding="utf-8" ?> | |||||
| <D:propfind xmlns:D="DAV:"> | |||||
| <D:allprop/> | |||||
| <D:propname/> | |||||
| </D:propfind> | |||||
| The definition of the propfind element only allows for the allprop | |||||
| or the propname element, not both. Thus the above is an error and | |||||
| must be responded to with a 400 (Bad Request). | |||||
| Imagine, however, that a server wanted to be "kind" and decided to | |||||
| pick the allprop element as the true element and respond to it. A | |||||
| client running over a bandwidth limited line who intended to execute | |||||
| a propname would be in for a big surprise if the server treated the | |||||
| command as an allprop. | |||||
| Additionally, if a server were lenient and decided to reply to this | |||||
| request, the results would vary randomly from server to server, with | |||||
| some servers executing the allprop directive, and others executing | |||||
| the propname directive. This reduces interoperability rather than | |||||
| increasing it. | |||||
| Goland, Schlimmer 4 | |||||
| UPnP Forum FXPP 19 June 2000 | |||||
| 9.2. Example - Unknown XML Element | |||||
| The previous example was illegal because it contained two elements | |||||
| that were explicitly banned from appearing together in the propfind | |||||
| element. However, XML is an extensible language, so one can imagine | |||||
| new elements being defined for use with propfind. Below is the | |||||
| request body of a PROPFIND and, like the previous example, must be | |||||
| rejected with a 400 (Bad Request) by a server that does not | |||||
| understand the expired-props element. | |||||
| <?xml version="1.0" encoding="utf-8" ?> | |||||
| <D:propfind xmlns:D="DAV:" | |||||
| xmlns:E="http://www.foo.bar/standards/props/"> | |||||
| <E:expired-props/> | |||||
| </D:propfind> | |||||
| To understand why a 400 (Bad Request) is returned let us look at the | |||||
| request body as the server unfamiliar with expired-props sees it. | |||||
| <?xml version="1.0" encoding="utf-8" ?> | |||||
| <D:propfind xmlns:D="DAV:" | |||||
| xmlns:E="http://www.foo.bar/standards/props/"> | |||||
| </D:propfind> | |||||
| As the server does not understand the expired-props element, | |||||
| according to the WebDAV-specific XML processing rules specified in | |||||
| section 14 of [RFC 2518], it must ignore it. Thus the server sees | |||||
| an empty propfind, which by the definition of the propfind element | |||||
| is illegal. | |||||
| Please note that had the extension been additive it would not | |||||
| necessarily have resulted in a 400 (Bad Request). For example, | |||||
| imagine the following request body for a PROPFIND: | |||||
| <?xml version="1.0" encoding="utf-8" ?> | |||||
| <D:propfind xmlns:D="DAV:" | |||||
| xmlns:E="http://www.foo.bar/standards/props/"> | |||||
| <D:propname/> | |||||
| <E:leave-out>*boss*</E:leave-out> | |||||
| </D:propfind> | |||||
| The previous example contains the fictitious element leave-out. Its | |||||
| purpose is to prevent the return of any property whose name matches | |||||
| the submitted pattern. If the previous example were submitted to a | |||||
| server unfamiliar with leave-out, the only result would be that the | |||||
| leave-out element would be ignored and a propname would be executed. | |||||
| 10. References | |||||
| [RFC2119] S. Bradner. Key words for use in RFCs to Indicate | |||||
| Requirement Levels. RFC 2119, March 1997. | |||||
| Goland, Schlimmer 5 | |||||
| UPnP Forum FXPP 19 June 2000 | |||||
| [RFC2068] R. Fielding, J. Gettys, J. Mogul, H. Frystyk, and T. | |||||
| Berners-Lee. Hypertext Transfer Protocol -- HTTP/1.1. RFC 2068, | |||||
| January 1997. | |||||
| [RFC2158] Y. Goland, E. Whitehead, A. Faizi, S. Carter, and D. | |||||
| Jensen. HTTP Extensions for Distributed Authoring WEBDAV. RFC 2518, | |||||
| February 1999. | |||||
| [XML] T. Bray, J. Paoli, C. M. Sperberg-McQueen, "Extensible Markup | |||||
| Language (XML)." World Wide Web Consortium Recommendation REC-xml- | |||||
| 19980210. http://www.w3.org/TR/1998/REC-xml-19980210. | |||||
| 11. Author's Addresses | |||||
| Yaron Y. Goland | |||||
| CrossGain | |||||
| 2039 152nd Avenue NE | |||||
| Redmond, WA 98052 | |||||
| <mailto:yarongo@crossgain.com> | |||||
| Jeffrey C. Schlimmer | |||||
| Microsoft Corporation | |||||
| One Microsoft Way | |||||
| Redmond, WA 98052 | |||||
| <mailto:jeffsch@Microsoft.com> | |||||
| Goland, Schlimmer 6 | |||||
| @@ -1,928 +0,0 @@ | |||||
| UPnP Forum Technical Committee Yaron Y. Goland | |||||
| Document: draft-goland-http-udp-04.txt CrossGain | |||||
| Jeffrey C. Schlimmer | |||||
| Microsoft | |||||
| 02 October 2000 | |||||
| Multicast and Unicast UDP HTTP Messages | |||||
| Status of this Memo | |||||
| This document is under review by the UPnP Forum Technical Committee. | |||||
| It was previously submitted to the IETF as an Internet Draft and has | |||||
| expired. This document is formatted in a manner consistent with the | |||||
| IETF formatting guidelines to facilitate possible future | |||||
| consideration by the IETF. | |||||
| This document is available on http://www.upnp.org. | |||||
| Abstract | |||||
| This document provides rules for encapsulating HTTP messages in | |||||
| multicast and unicast UDP packets to be sent within a single | |||||
| administrative scope. No provisions are made for guaranteeing | |||||
| delivery beyond re-broadcasting. | |||||
| 1. Introduction | |||||
| This document provides rules for encapsulating HTTP messages in | |||||
| multicast and unicast UDP messages. No provisions are made for | |||||
| guaranteeing delivery beyond re-broadcasting. | |||||
| This technology is motivated by applications such as SSDP where it | |||||
| is expected that messages which are primarily transmitted over TCP | |||||
| HTTP need to be transmitted over Multicast or Unicast UDP, because | |||||
| of the unique requirements of extremely lightweight servers. | |||||
| This document will not specify a mechanism suitable for replacing | |||||
| HTTP over TCP. Rather this document will define a limited mechanism | |||||
| only suitable for extreme circumstances where the use of TCP is | |||||
| impossible. Thus this mechanism will not have the robustness of | |||||
| functionality and congestion control provided by TCP. It is expected | |||||
| that in practice the mechanisms specified here in will only be used | |||||
| as a means to get to TCP based HTTP communications. | |||||
| 2. Changes | |||||
| 2.1. Since 00 | |||||
| Divided each section of the spec into three parts, problem | |||||
| definition, proposed solution and design rationale. When the spec is | |||||
| ready for standardization the problem definition and design | |||||
| rationale sections will be removed. Design rationale is presented in | |||||
| Goland, Schlimmer 1 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| question/answer form because I have found that to be very effective | |||||
| in addressing design issues. | |||||
| Clarified that a HTTPU/HTTPMU URI without an abs_path translates to | |||||
| "*" in the request-URI. | |||||
| Added the S header to allow request and responses to be associated. | |||||
| Note that while clients aren't required to send out S headers, | |||||
| servers are required to return them. | |||||
| Got rid of MM. The lower bound is always 0. | |||||
| The introduction of the S header makes proxying and caching possible | |||||
| so the sections on those topics have been expanded, but they should | |||||
| be considered experimental at best. | |||||
| 2.2. Since 02 | |||||
| Added requirement for HTTP/1.1 as the version identifier in the | |||||
| request line. (See section on HTTP Version in Request Line.) | |||||
| Removed requirement that requests without an S header MUST NOT be | |||||
| responded to. (See section on Unicast UDP HTTP Messages.) | |||||
| Clarified that a server should respond to each request it receives | |||||
| but not duplicate those responses. (See section on Retrying | |||||
| Requests.) | |||||
| Clarified caching when responding to repeated requests. (See section | |||||
| on Caching.) | |||||
| Expanded that if a server has > 1 response per HTTPMU request, it | |||||
| should spread them out. (See section on MX header.) | |||||
| Tied behavior of duplicate responses with the same S header value to | |||||
| the semantics of the method (was discard duplicates). (See section | |||||
| on S header.) | |||||
| Outlined initial security considerations. (See section on Security.) | |||||
| 2.3. Since 03 | |||||
| Clarified the "no abs_path" requirement for HTTPU/HTTPMU request- | |||||
| URIs. | |||||
| Clarified use of "*" as a request-URI. | |||||
| Removed requirement for HTTPU/HTTPMU servers to support "chunked" | |||||
| transfer-coding. | |||||
| 3. Terminology | |||||
| Since this document describes a set of extensions to the HTTP/1.1 | |||||
| protocol, the augmented BNF used herein to describe protocol | |||||
| Goland, Schlimmer 2 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| elements is exactly the same as described in section 2.1 of | |||||
| [RFC2616]. Since this augmented BNF uses the basic production rules | |||||
| provided in section 2.2 of [RFC2616], these rules apply to this | |||||
| document as well. | |||||
| The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", | |||||
| "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this | |||||
| document are to be interpreted as described in RFC 2119 [RFC2119]. | |||||
| 4. HTTPU URL | |||||
| 4.1. Problem Definition | |||||
| A mechanism is needed to allow for communications that are to be | |||||
| sent over Unicast UDP HTTP to be identified in the URI namespace. | |||||
| 4.2. Proposed Solution | |||||
| The HTTPU URL specifies that the HTTP request be sent over unicast | |||||
| UDP according to the rules laid out in this document. | |||||
| HTTPU_URL = "HTTPU:" "//" host [ ":" port ] [ abs path [ "?" query]] | |||||
| The BNF productions host, port and abs path are defined in | |||||
| [RFC2616]. | |||||
| The syntax of the HTTPU URL is to be processed identically to the | |||||
| HTTP URL with the exception of the transport. | |||||
| One MUST NOT assume that if a HTTP, HTTPU or HTTPMU URL are | |||||
| identical in all ways save the protocol that they necessarily point | |||||
| to the same resource. | |||||
| 4.3. Design Rationale | |||||
| 4.3.1. Why would we ever need a HTTPU/HTTPMU URL? | |||||
| Imagine one wants to tell a system to send responses over HTTPU. How | |||||
| would one express this? If one uses a HTTP URL there is no way for | |||||
| the system to understand that you really meant HTTPU. | |||||
| 5. HTTPMU URL | |||||
| 5.1. Problem Definition | |||||
| A mechanism is needed to allow for communications that are to be | |||||
| sent over Multicast UDP HTTP to be identified in the URI namespace. | |||||
| 5.2. Proposed Solution | |||||
| The HTTPMU URL specifies that the HTTP request that HTTP request is | |||||
| to be sent over multicast UDP according to the rules laid out in | |||||
| this document. | |||||
| Goland, Schlimmer 3 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| HTTPMU_URL = "HTTPMU:" "//" host [ ":" port ] [ abs path [ "?" | |||||
| query]] | |||||
| The BNF productions host, port and abs path are defined in | |||||
| [RFC2616]. | |||||
| The syntax of the HTTPMU URL is to be processed identically to the | |||||
| HTTP URL with the exception of the transport. | |||||
| One MUST NOT assume that if a HTTP, HTTPU or HTTPMU URL are | |||||
| identical in all ways save the protocol that they necessarily point | |||||
| to the same resource. | |||||
| If a HTTPMU URL does not have an abs path element then when the HTTP | |||||
| multicast UDP request is made the request-URI MUST be "*". | |||||
| For example, HTTPU://www.foo.com would translate into a request-URI | |||||
| of "*". A request-URI of HTTPU://www.foo.com/ would still translate | |||||
| to the absoluteURI "HTTPU://www.foo.com/". | |||||
| 5.3. Design Rationale | |||||
| 5.3.1. In the HTTPMU URL a request such as http://www.foo.com is | |||||
| translated to a "*" in the request-URI rather than a "/", why | |||||
| isn't the same the case for HTTPU? | |||||
| A HTTPU request is a point-to-point request. There is one sender and | |||||
| one receiver. Thus the semantics of the URL are identical to HTTP | |||||
| with the exception of the transport. | |||||
| Generally, a HTTPMU client will want to send its request to many | |||||
| receivers at once, where each receiver represents a different set of | |||||
| resources. A client can specify this in the HTTPMU request itself by | |||||
| using the request-URI "*". Unfortunately, there is no present way to | |||||
| construct an HTTP URL that will have this request-URI. As such, a | |||||
| mechanism had to be added. | |||||
| 5.3.2. Why would an HTTPMU client want to use a request-URI of "*" | |||||
| anyway? | |||||
| In TCP HTTP, the client will often specify a single resource on | |||||
| which the request should operate. For example, a GET of the URL | |||||
| http://foo.org/baz.gif should retrieve the resource at that single, | |||||
| well-defined location. | |||||
| One big reason for a client to send a request over multicast UDP, | |||||
| though, is the ability to send a request to many receivers at once, | |||||
| even when the number of receivers is not known. | |||||
| Specifying an absoluteURI in the request, though, would defeat this; | |||||
| all receivers without that exact resource would be forced to reject | |||||
| the request. | |||||
| Goland, Schlimmer 4 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| By specifying a request-URI of "*" client signifies that the request | |||||
| "does not apply to a particular resource, but to the server itself, | |||||
| and is only allowed when the method used does not necessarily apply | |||||
| to a resource." [RFC 2616] | |||||
| 5.3.3. So when would an HTTPMU client want to use a request-URI other | |||||
| than "*"? | |||||
| This may be useful when a client knows the URI for the resource, but | |||||
| not the server on which the resource lives. If the client knows | |||||
| both, though, it is expected that TCP HTTP or HTTPU would be used. | |||||
| Servers MUST NOT assume that an HTTPMU request containing an | |||||
| absoluteURI necessarily refers to the same resource as a HTTPU | |||||
| request with the same absoluteURI. For example, servers that support | |||||
| both HTTPMU and HTTPU may reject a request for a particular resource | |||||
| when received through HTTPMU, but accept it when received through | |||||
| HTTPU. | |||||
| 6. HTTP Version in Request Line | |||||
| 6.1. Problem Definition | |||||
| A message format identifier is needed for the HTTPU and HTTPMU | |||||
| request lines. | |||||
| 6.2. Proposed Solution | |||||
| Request lines for HTTPU and HTTPMU requests MUST use HTTP/1.1 as the | |||||
| version. | |||||
| Request-Line = Method SP Request-URI SP HTTP/1.1 CRLF | |||||
| The BNF production Method is defined in [RFC2616]. | |||||
| 6.3. Design Rationale | |||||
| 6.3.1. Why not define separate HTTPU and HTTPMU versions? | |||||
| While HTTP/1.1 does hint at underlying features (like pipelining), | |||||
| it principally specifies a message format. HTTPU and HTTPMU use the | |||||
| same message format as defined by HTTP/1.1. Reusing this message | |||||
| format identifier enables syntactic parsing / generating of HTTPU | |||||
| and HTTPMU request by existing HTTP message mungers. | |||||
| 6.3.2. If the version in the request line is the same as an HTTP | |||||
| request, once a request was stored, how could one distinguish an | |||||
| HTTPU (or HTTPMU) request from an HTTP request? | |||||
| TBD | |||||
| 7. Unicast UDP HTTP Messages | |||||
| Goland, Schlimmer 5 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| 7.1. Problem Definition | |||||
| A mechanism is needed to send HTTP messages over the unicast UDP | |||||
| transport. | |||||
| 7.2. Proposed Solution | |||||
| HTTP messages sent over unicast UDP function identically to HTTP | |||||
| messages sent over TCP as defined in [RFC2616] except as specified | |||||
| below. | |||||
| For brevity's sake HTTP messages sent over unicast UDP will be | |||||
| referred to as HTTPU messages. | |||||
| HTTPU messages MUST fit entirely in a single UDP message. If a HTTPU | |||||
| message can not be fit into a single UDP message then it MUST NOT be | |||||
| sent using unicast UDP. Incomplete HTTPU messages SHOULD be ignored. | |||||
| The request-URI of a HTTPU message MUST always be fully qualified. | |||||
| A single unicast UDP message MUST only contain a single HTTPU | |||||
| message. As such, an HTTPU server MAY reject messages with "chunked" | |||||
| transfer-coding. | |||||
| When responding to a HTTPU request with an S header the rules for | |||||
| the proper handling of S headers, as specified below MUST be | |||||
| followed. | |||||
| 7.3. Design Rationale | |||||
| See also the subsection on the S header below for the design | |||||
| rationale of the S header. | |||||
| 7.3.1. Why can't a single HTTP message be sent over multiple UDP | |||||
| messages? | |||||
| The ability to send unlimited size messages across the Internet is | |||||
| one of the key features of TCP. The goal of this paper is not to | |||||
| reinvent TCP but rather to provide a very simple emergency back up | |||||
| HTTP system that can leverage UDP where TCP cannot be used. As such | |||||
| features to allow a single HTTP message to span multiple UDP | |||||
| messages is not provided. | |||||
| 7.3.2. Why are request-URIs sent over HTTPU required to be fully | |||||
| qualified? | |||||
| A relative URI in a HTTP message is assumed to be relative to a HTTP | |||||
| URL. However this would clearly be inappropriate for a HTTPU or | |||||
| HTTPMU message. The easiest solution would be to simply state that a | |||||
| relative URI is relative to the type of message it was sent in. But | |||||
| one of the goals of this draft is to allow current HTTP message | |||||
| processors to be able to munch on HTTPU/HTTPMU messages and this | |||||
| would cause a change to those processors. | |||||
| Goland, Schlimmer 6 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| The cost of this simplification is that you repeat the host | |||||
| information, once in the URI and once in the host header. | |||||
| But again, taking out the host header would make a lot of existing | |||||
| HTTP message munchers very unhappy. | |||||
| 7.3.3. Why is the requirement for ignoring incomplete HTTPU messages a | |||||
| SHOULD instead of a MUST? | |||||
| Some systems use a lot of redundant data or have good mechanisms for | |||||
| handling partial data. As such they could actually do something | |||||
| intelligent with a partial message. A SHOULD allows them to do this | |||||
| while still making it clear that in the majority case partial | |||||
| HTTPU/HTTPMU messages are going to get thrown out. | |||||
| 7.3.4. Why aren't multiple HTTP messages allowed into a single UDP | |||||
| message if they will fit? | |||||
| It was easier to ban it, and it didn't seem to buy us much. It was | |||||
| especially worrying because it would start to convince people that | |||||
| they could actually order their UDP requests in a pipelinesque | |||||
| manner. It was easier to just keep things simple and ban it. | |||||
| 7.3.5. Why aren't we allowed to leave off content-lengths if only a | |||||
| single HTTPU message is allowed in a UDP message? | |||||
| In general we try to only change from RFC 2616 when we are forced | |||||
| to. Although including a content-length is annoying it makes it easy | |||||
| to use HTTP/1.1 message parsing/generating systems with this spec. | |||||
| 7.3.6. Why might a HTTPU message choose to not have an S header? | |||||
| Leaving off the S header would be useful for throwaway events. In | |||||
| systems with a high event rate it is usually easier to just throw | |||||
| away an event rather than re-sending it. As such there is no real | |||||
| benefit to correlating unnecessary responses with requests. | |||||
| 7.3.7. Why isn't the MX header used on HTTPU messages? | |||||
| As HTTPU messages are point-to-point there will be exactly one | |||||
| response. MX is only useful in cases, such as HTTPMU requests, where | |||||
| there can be many potential responses from numerous different | |||||
| clients. MX helps to prevent the client from getting creamed with | |||||
| responses. | |||||
| 7.3.8. Can I send 1xx responses over HTTPU? | |||||
| Yes. Error handling is identical to RFC 2616. | |||||
| 8. Multicast UDP HTTP Requests | |||||
| 8.1. Problem Definition | |||||
| Goland, Schlimmer 7 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| A mechanism is needed to send HTTP messages over the multicast UDP | |||||
| transport. | |||||
| 8.2. Proposed Solution | |||||
| HTTP messages sent over multicast UDP MUST obey all the requirements | |||||
| for HTTPU messages in addition to the requirements provided below. | |||||
| For brevity's sake HTTP messages sent over multicast UDP will be | |||||
| referred to as HTTPMU messages. | |||||
| Resources that support receiving multicast UDP HTTP requests MUST | |||||
| honor the MX header if included in the request. | |||||
| If a resource has a single response, it MUST generate a random | |||||
| number between 0 and MX that represents the number of seconds the | |||||
| resource MUST wait before sending a response. If a resource has | |||||
| multiple responses per request, it SHOULD send these resources | |||||
| spread over the interval [0..MX]. This prevents all responses from | |||||
| being sent at once. | |||||
| HTTP clients SHOULD keep listening for responses for a reasonable | |||||
| delta of time after MX. That delta will be based on the type of | |||||
| network the request is being sent over. This means that if a server | |||||
| cannot respond to a request before MX then there is little point in | |||||
| sending the response, as the client will most likely not be | |||||
| listening for it. | |||||
| When used with a multicast UDP HTTP request, the "*" request-URI | |||||
| means "to everyone who is listening to this IP address and port." | |||||
| A HTTPMU request without a MX header MUST NOT be responded to. | |||||
| 8.3. Design Rationale | |||||
| 8.3.1. Why is there a "delta" after the MX time when the client should | |||||
| still be listening? | |||||
| So let's say the MX value is 5 seconds. The HTTP resource generates | |||||
| a number between 0 and 5 and gets 5. After 5 seconds of waiting the | |||||
| HTTP resource will send its response. | |||||
| Now for some math: | |||||
| 0.5 seconds - Time it took the client's request to reach | |||||
| the HTTP resource. | |||||
| 5 seconds - Time the HTTP resource waited after | |||||
| receiving the message to respond, based on | |||||
| the MX value. | |||||
| 0.5 seconds - Time for the response to get back to the | |||||
| client. | |||||
| Total time elapsed - 6 seconds | |||||
| Goland, Schlimmer 8 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| If the client only waits 5 seconds, the MX value, then they would | |||||
| have stopped listening for this response by the time it arrived, | |||||
| hence the need for the delta. | |||||
| 8.3.2. What should the "delta" after MX expires be? | |||||
| Unfortunately this is an impossible question to answer. How fast is | |||||
| your network? How far is the message going? Is there any congestion? | |||||
| In general delta values will be set based on a combination of | |||||
| heuristics and application necessity. That is, if you are displaying | |||||
| information to a user any data that comes in after 20 or 30 seconds | |||||
| is probably too late. | |||||
| 8.3.3. When would a HTTPMU request not be responded to? | |||||
| When a HTTP resource is making a general announcement, such as "I am | |||||
| here", it generally isn't useful to have everyone respond confirming | |||||
| they received the message. This is especially the case given that | |||||
| the HTTP resource probably doesn't know who should have received the | |||||
| announcement so the absence of a HTTP client in the responses | |||||
| wouldn't be meaningful. | |||||
| Whether a particular request requires a response is dependant on the | |||||
| application, and is beyond the scope of this specification. | |||||
| 8.3.4. Why do we require the MX header on HTTPMU requests that are to | |||||
| be responded to? | |||||
| This is to prevent overloading the HTTP client. If all the HTTP | |||||
| resources responded simultaneously the client would probably loose | |||||
| most of the responses as its UDP buffer overflowed. | |||||
| 9. Retrying Requests | |||||
| 9.1. Problem Definition | |||||
| UDP is an unreliable transport with no failure indicators; as such | |||||
| some mechanism is needed to reasonably increase the chance that a | |||||
| HTTPU/HTTPMU message will be delivered. | |||||
| 9.2. Proposed Solution | |||||
| UDP is an inherently unreliable transport and subject to routers | |||||
| dropping packets without notice. Applications requiring delivery | |||||
| guarantees SHOULD NOT use HTTPU or HTTPMU. | |||||
| In order to increase the probability that a HTTPU or HTTPMU message | |||||
| is delivered the message MAY be repeated several times. If a | |||||
| multicast resource would send a response(s) to any copy of the | |||||
| request, it SHOULD send its response(s) to each copy of the request | |||||
| it receives. It MUST NOT repeat its response(s) per copy of the | |||||
| reuqest. | |||||
| Goland, Schlimmer 9 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| In order to prevent the network from being flooded a message SHOULD | |||||
| NOT be repeated more than MAX_RETRIES time. A random period of time | |||||
| between 0 and MAX_RETRY_INTERVAL SHOULD be selected between each | |||||
| retry to determine how long to wait before issuing the retry. | |||||
| 9.3. Design Rationale | |||||
| 9.3.1. Why is the requirement "applications requiring delivery | |||||
| guarantees should not use HTTPU or HTTPMU" only a SHOULD and not | |||||
| a MUST? | |||||
| Because there might come a day when it makes sense to use HTTPU or | |||||
| HTTPMU for guaranteed delivery and there is no reason to completely | |||||
| ban the possibility. | |||||
| 9.3.2. Why is the requirement that a request not be repeated more than | |||||
| MAX_RETRIES times a SHOULD and not a MUST? | |||||
| Local knowledge may make the limit unnecessary. For example, if one | |||||
| knew that the message was being delivered using a super reliable | |||||
| network then repeats are not necessary. Similarly if one knew that | |||||
| the network the requests were going through were particularly | |||||
| unreliable and assuming one had properly accounted for the effects | |||||
| of additional messages on that congestion, one might have a good | |||||
| reason to send more than MAX_RETRIES. | |||||
| 9.3.3. Why SHOULD multicast resources respond to each copy of a request | |||||
| it receives? | |||||
| Because the earlier responses might have been lost. | |||||
| 9.3.4. Why MUST multicast resources not repeat its response(s) to each | |||||
| copy of a request it receives? | |||||
| This strategy provides the lowest network loading for any desired | |||||
| level of reliability, or equivalently, the highest reliability for | |||||
| any specified level of network loading. | |||||
| 10. Caching | |||||
| 10.1. Problem Definition | |||||
| Caching is a feature that has demonstrated its usefulness in HTTP, | |||||
| provisions need to be made to ensure that HTTPU/HTTPMU messages can | |||||
| be cached using a consistent algorithm. | |||||
| 10.2. Proposed Solution | |||||
| [Ed. Note: Never having tried to actually build a HTTPU/HTTPMU | |||||
| generic cache we suspect there are some very serious gotchas here | |||||
| that we just haven't found yet. This section should definitely be | |||||
| treated as "under development."] | |||||
| Goland, Schlimmer 10 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| Caching rules for HTTPU/HTTPMU responses are no different than | |||||
| normal HTTP responses. HTTPU/HTTPMU responses are matched to their | |||||
| requests through the S header value. | |||||
| When responding to a multicast request, a resource MAY cache its | |||||
| response(s) and retransmit from the cache in response to duplicate | |||||
| requests. | |||||
| 10.3. Design Rationale | |||||
| 10.3.1. Wouldn't it be useful to be able to cache HTTPU/HTTPMU requests | |||||
| if they don't have responses? | |||||
| Yes, it probably would, especially if we are talking about a client- | |||||
| side cache. It is probably worth investigating the use of cache | |||||
| control headers on requests for this very purpose. | |||||
| 11. Proxying UDP HTTP Requests | |||||
| 11.1. Problem Definition | |||||
| For security or caching reasons it is sometimes necessary to place a | |||||
| proxy in a message path. Provisions need to be made to ensure that | |||||
| HTTPU/HTTPMU messages can be proxied. | |||||
| 11.2. Proposed Solution | |||||
| [Ed. Note: This section should be considered experimental. No one | |||||
| has really had to design much less implement a HTTPU/HTTPMU proxy | |||||
| yet.] | |||||
| All transport independent rules for proxying, such as length of time | |||||
| to cache a response, hop-by-hop header rules, etc. are the same for | |||||
| HTTPU/HTTPMU as they are for HTTP messages. | |||||
| [Ed. Note: I'm not sure how far to go into the "transport | |||||
| independent rules". The RFC 2616 doesn't really call them out very | |||||
| well but I also don't want to have to re-write RFC 2616 spec inside | |||||
| this spec.] | |||||
| The transport dependent rules, however, are different. For example, | |||||
| using TCP any pipelined messages are guaranteed to be delivered in | |||||
| order. There are no ordering guarantees of any form for HTTPU/HTTPMU | |||||
| proxies. | |||||
| In general a proxy is required to forward a HTTPU/HTTPMU message | |||||
| exactly once. It SHOULD NOT repeat the message. Rather the client is | |||||
| expected to repeat the message and, as the proxy receives the | |||||
| repeats, they will be forwarded. | |||||
| Note that it is acceptable, if not encouraged, for proxies to | |||||
| analyze network conditions and determine the likelihood, on both | |||||
| incoming and outgoing connections, of UDP messages being dropped. If | |||||
| Goland, Schlimmer 11 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| the likelihood is too high then it would be expected for the proxy, | |||||
| taking into consideration the possibility of making congestion even | |||||
| worse, to repeat requests and responses on its own. In a sense the | |||||
| proxy could be thought of as a signal regenerator. This is why the | |||||
| prohibition against repeating messages is a SHOULD NOT rather than a | |||||
| MUST NOT. | |||||
| HTTPMU messages are sent with the assumption that the message will | |||||
| only be seen by the multicast address they were sent to. Thus when a | |||||
| proxy forwards the request it is expected to only do so to the | |||||
| appropriate multicast channel. Note, however, that proxies may act | |||||
| as multicast bridges. | |||||
| Also note that proxied HTTPMU messages with a HTTPMU URL without an | |||||
| absolute path are to be treated as if they were sent to the | |||||
| specified multicast address with the request-URI "*". | |||||
| If a HTTPMU request is sent with a host that does not resolve to a | |||||
| multicast address then the request MUST be rejected with a 400 Bad | |||||
| Request error. | |||||
| There is no requirement that a HTTPU proxy support HTTPMU or vice | |||||
| versa. | |||||
| 11.3. Design Rationale | |||||
| 11.3.1. Why would anyone proxy HTTPMU requests? | |||||
| Proxying HTTPMU requests can be a neat way to create virtual | |||||
| multicast channels. Just hook a bunch of proxies together with | |||||
| unicast connections and tell the proxies' users that they are all on | |||||
| the same multicast scope. | |||||
| 12. HTTP Headers | |||||
| 12.1. AL (Alternate Location) General Header | |||||
| 12.1.1. Problem Definition | |||||
| There are many instances in which a system needs to provide location | |||||
| information using multiple URIs. The LOCATION header only allows a | |||||
| single URI. Therefore a mechanism is needed to allow multiple | |||||
| location URIs to be returned. | |||||
| 12.1.2. Proposed Solution | |||||
| AL = "AL" ":" 1*("<" AbsoluteURI ">") ; AbsoluteURI is defined in | |||||
| section 3.2.1 of [RFC2616] | |||||
| The AL header is an extension of the LOCATION header whose semantics | |||||
| are the same as the LOCATION header. That is, the AL header allows | |||||
| one to return multiple locations where as the LOCATION header allows | |||||
| one to return only one. The contents of an AL header are ordered. If | |||||
| Goland, Schlimmer 12 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| both a LOCATION header and an AL header are included in the same | |||||
| message then the URI in the LOCATION header is to be treated as if | |||||
| it were the first entry in the AL header. The AL header MAY be used | |||||
| by itself but implementers should be aware that existing systems | |||||
| will ignore the header. | |||||
| 12.1.3. Design Rationale | |||||
| 12.1.3.1. Why not just fix the BNF for the LOCATION header? | |||||
| This is tempting but the goal of maintaining compatibility with RFC | |||||
| 2616's message format overrides the usefulness of this solution. | |||||
| 12.2. MX Request Header | |||||
| 12.2.1. Problem Definition | |||||
| A mechanism is needed to ensure that responses to HTTPMU requests do | |||||
| not come at a rate greater than the requestor can handle. | |||||
| 12.2.2. Proposed Solution | |||||
| MX = "MX" ":" Integer | |||||
| Integer = First_digit *More_digits | |||||
| First_digit = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | |||||
| More_digits = "0" | First_digit | |||||
| The value of the MX header indicates the maximum number of seconds | |||||
| that a multicast UDP HTTP resource MUST wait before it sends a | |||||
| response stimulated by a multicast request. | |||||
| HTTP resources MAY treat any MX header value greater than MX_MAX as | |||||
| being equal to MX_MAX. | |||||
| 12.2.3. Design Rationale | |||||
| 12.2.3.1. Why is MX in seconds? | |||||
| In practice wait periods shorter than a second proved useless and | |||||
| longer proved too coarse. Of course as faster networks get deployed | |||||
| finer-grain times would be useful, but we need a compromise | |||||
| measurement that will meet everyone's needs. Seconds seem to do that | |||||
| quite well. | |||||
| 12.2.3.2. Couldn't MX still overload the requestor if there are too | |||||
| many responders? | |||||
| Absolutely. If there are a 100,000 clients that want to respond even | |||||
| pushing them over 30 seconds on a 10 Mbps link is still going to | |||||
| blow both the client and the network away. However the only way to | |||||
| prevent these sorts of situations is to know the current available | |||||
| network bandwidth and the total number of likely responders ahead of | |||||
| Goland, Schlimmer 13 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| time. Both generally prove between difficult to impossible to figure | |||||
| out. So we are left with heuristics and the MX header. | |||||
| 12.3. S (Sequence) General Header | |||||
| 12.3.1. Problem Definition | |||||
| A mechanism is needed to associate HTTPU/HTTPMU requests with | |||||
| responses, as UDP does not have any connection semantics. | |||||
| 12.3.2. Proposed Solution | |||||
| S = "S" ":" AbsoluteURI | |||||
| The S header is a URI that is unique across the entire URI namespace | |||||
| for all time. When an S header is sent on a HTTPU/HTTPMU request it | |||||
| MUST be returned, with the same value, on the response. | |||||
| If a client receives multiple responses with the same S header then | |||||
| the client MAY assume that all the responses are in response to the | |||||
| same request. If the messages differ from each other then the client | |||||
| MUST behave based on the specification of the request method. | |||||
| 12.3.3. Design Rationale | |||||
| 12.3.3.1. Why do we need the S header? | |||||
| Without an S header the only way to match requests with responses is | |||||
| to ensure that there is enough information in the response to know | |||||
| what request it was intended to answer. Even in that case it is | |||||
| still possible to confuse which request a response goes to if it | |||||
| does not have the equivalent of an S header. | |||||
| 12.3.3.2. Why aren't S headers mandatory on all requests with a | |||||
| response? | |||||
| Some systems don't need them. | |||||
| 12.3.3.3. Why aren't S headers guaranteed to be sequential so you could | |||||
| do ordering? | |||||
| Because HTTPU/HTTPMU is not interested in ordering. If one wants | |||||
| ordering one should use TCP. | |||||
| 12.3.3.4. Do S headers allow detecting and removing duplicates? | |||||
| Yes, for methods (like GET) that define a single responses to a | |||||
| request. No, for methods (like SEARCH) that define multiple | |||||
| responses to a request. | |||||
| 13. Interaction of HTTP, HTTPU and HTTPMU Messages | |||||
| 13.1. Problem Definition | |||||
| Goland, Schlimmer 14 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| [Ed. Note: Concerns include HTTPU request redirected to HTTP? > 1 | |||||
| HTTPU responses to 1 HTTPMU request?] | |||||
| 13.2. Proposed Solution | |||||
| TBD | |||||
| 13.3. Design Rationale | |||||
| TBD | |||||
| 14. Security Considerations | |||||
| All the normal HTTP security considerations apply. | |||||
| 14.1. Cookies | |||||
| There is no danger that the S header will be used as a cookie since | |||||
| the client generates it, and the server returns it. (A cookie is | |||||
| generated by a server and returned by the client.) | |||||
| 14.2. Spoofing | |||||
| Servers and multicast resources could fake S headers, but this is | |||||
| not a major threat if some form of authentication over UDP is used. | |||||
| (Defining authentication over UDP is beyond the scope of this | |||||
| document, but briefly, one could assume the challenge and send the | |||||
| authentication response as part of the HTTPU/MU request.) | |||||
| 14.3. Lost Requests | |||||
| TBD | |||||
| 14.4. Oversized Requests | |||||
| TBD | |||||
| 15. Acknowledgements | |||||
| Thanks to John Stracke for his excellent comments. Dale Worley | |||||
| devised the single-response-per-each-copy-of-request mechanism | |||||
| outlined in the section on Retrying Requests. Chris Rude clarified | |||||
| request URI rules. | |||||
| 16. Constants | |||||
| MAX_RETRIES - 3 | |||||
| MAX_RETRY_INTERVAL - 10 seconds | |||||
| MAX_MX - 120 seconds | |||||
| 17. Reference | |||||
| Goland, Schlimmer 15 | |||||
| UPnP Forum UDP HTTP 24 Aug 2000 | |||||
| [RFC2119] S. Bradner. Key words for use in RFCs to Indicate | |||||
| Requirement Levels. RFC 2119, March 1997. | |||||
| [RFC2616] R. Fielding, J. Gettys, J. C. Mogul, H. Frystyk, L. | |||||
| Masinter, P. Leach and T. Berners-Lee. Hypertext Transfer Protocol - | |||||
| HTTP/1.1. RFC 2616, November 1998. | |||||
| 18. Authors' Address | |||||
| Yaron Y. Goland | |||||
| CrossGain | |||||
| 2039 152nd Avenue NE | |||||
| Redmond, WA 98052 | |||||
| <mailto:yarongo@crossgain.com> | |||||
| Jeffrey C. Schlimmer | |||||
| Microsoft Corporation | |||||
| One Microsoft Way | |||||
| Redmond, WA 98052 | |||||
| <mailto:jeffsch@microsoft.com> | |||||
| Goland, Schlimmer 16 | |||||
| @@ -1,17 +0,0 @@ | |||||
| <device> | |||||
| <serviceList> | |||||
| <service> | |||||
| <serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType> | |||||
| <serviceId>RenderingControl</serviceId> | |||||
| </service> | |||||
| <service> | |||||
| <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType> | |||||
| <serviceId>ConnectionManager</serviceId> | |||||
| </service> | |||||
| <service> | |||||
| <Optional/> | |||||
| <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType> | |||||
| <serviceId>AVTransport</serviceId> | |||||
| </service> | |||||
| </serviceList> | |||||
| </device> | |||||
| @@ -1,17 +0,0 @@ | |||||
| <device> | |||||
| <serviceList> | |||||
| <service> | |||||
| <Optional/> | |||||
| <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType> | |||||
| <serviceId>AVTransport</serviceId> | |||||
| </service> | |||||
| <service> | |||||
| <serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType> | |||||
| <serviceId>ContentDirectory</serviceId> | |||||
| </service> | |||||
| <service> | |||||
| <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType> | |||||
| <serviceId>ConnectionManager</serviceId> | |||||
| </service> | |||||
| </serviceList> | |||||
| </device> | |||||
| @@ -1,477 +0,0 @@ | |||||
| <scpd> | |||||
| <serviceStateTable> | |||||
| <stateVariable> | |||||
| <name>TransportState</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| <allowedValueList> | |||||
| <allowedValue>STOPPED</allowedValue> | |||||
| <allowedValue>PLAYING</allowedValue> | |||||
| </allowedValueList> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>TransportStatus</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| <allowedValueList> | |||||
| <allowedValue>OK</allowedValue> | |||||
| <allowedValue>ERROR_OCCURRED</allowedValue> | |||||
| </allowedValueList> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>PlaybackStorageMedium</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>RecordStorageMedium</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>PossiblePlaybackStorageMedia</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>PossibleRecordStorageMedia</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>CurrentPlayMode</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| <allowedValueList> | |||||
| <allowedValue>NORMAL</allowedValue> | |||||
| </allowedValueList> | |||||
| <defaultValue>NORMAL</defaultValue> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>TransportPlaySpeed</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| <allowedValueList> | |||||
| <allowedValue>1</allowedValue> | |||||
| </allowedValueList> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <name>RecordMediumWriteStatus </name> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>CurrentRecordQualityMode</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>PossibleRecordQualityModes</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>NumberOfTracks</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui4</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>CurrentTrack</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui4</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>CurrentTrackDuration</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>CurrentMediaDuration</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>CurrentTrackMetaData</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>CurrentTrackURI</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>AVTransportURI</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>AVTransportURIMetaData</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>NextAVTransportURI</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>NextAVTransportURIMetaData</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>RelativeTimePosition</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>AbsoluteTimePosition</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>RelativeCounterPosition</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>i4</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>AbsoluteCounterPosition</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>i4</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <Optional/> | |||||
| <name>CurrentTransportActions</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>LastChange</name> | |||||
| <sendEventsAttribute>yes</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_SeekMode</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| <allowedValueList> | |||||
| <allowedValue>TRACK_NR</allowedValue> | |||||
| </allowedValueList> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_SeekTarget</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_InstanceID</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui4</dataType> | |||||
| </stateVariable> | |||||
| </serviceStateTable> | |||||
| <actionList> | |||||
| <action> | |||||
| <name>SetAVTransportURI</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentURI</name> | |||||
| <direction>in</direction> <relatedStateVariable>AVTransportURI</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentURIMetaData</name> | |||||
| <direction>in</direction> <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> <Optional/> | |||||
| <name>SetNextAVTransportURI</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>NextURI</name> | |||||
| <direction>in</direction> <relatedStateVariable>NextAVTransportURI</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>NextURIMetaData</name> | |||||
| <direction>in</direction> <relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>GetMediaInfo</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>NrTracks</name> | |||||
| <direction>out</direction> <relatedStateVariable>NumberOfTracks</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>MediaDuration</name> | |||||
| <direction>out</direction> <relatedStateVariable>CurrentMediaDuration</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentURI</name> | |||||
| <direction>out</direction> <relatedStateVariable>AVTransportURI</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentURIMetaData</name> | |||||
| <direction>out</direction> <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>NextURI</name> | |||||
| <direction>out</direction> <relatedStateVariable>NextAVTransportURI</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>NextURIMetaData</name> | |||||
| <direction>out</direction> <relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>PlayMedium</name> | |||||
| <direction>out</direction> <relatedStateVariable>PlaybackStorageMedium</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>RecordMedium</name> | |||||
| <direction>out</direction> <relatedStateVariable>RecordStorageMedium</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>WriteStatus</name> | |||||
| <direction>out</direction> <relatedStateVariable>RecordMediumWriteStatus </relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>GetTransportInfo</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentTransportState</name> | |||||
| <direction>out</direction> <relatedStateVariable>TransportState</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentTransportStatus</name> | |||||
| <direction>out</direction> <relatedStateVariable>TransportStatus</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentSpeed</name> | |||||
| <direction>out</direction> <relatedStateVariable>TransportPlaySpeed</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>GetPositionInfo</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Track</name> | |||||
| <direction>out</direction> <relatedStateVariable>CurrentTrack</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>TrackDuration</name> | |||||
| <direction>out</direction> <relatedStateVariable>CurrentTrackDuration</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>TrackMetaData</name> | |||||
| <direction>out</direction> <relatedStateVariable>CurrentTrackMetaData</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>TrackURI</name> | |||||
| <direction>out</direction> <relatedStateVariable>CurrentTrackURI</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>RelTime</name> | |||||
| <direction>out</direction> <relatedStateVariable>RelativeTimePosition</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>AbsTime</name> | |||||
| <direction>out</direction> <relatedStateVariable>AbsoluteTimePosition</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>RelCount</name> | |||||
| <direction>out</direction> <relatedStateVariable>RelativeCounterPosition</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>AbsCount</name> | |||||
| <direction>out</direction> <relatedStateVariable>AbsoluteCounterPosition</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>GetDeviceCapabilities</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>PlayMedia</name> | |||||
| <direction>out</direction> <relatedStateVariable>PossiblePlaybackStorageMedia</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>RecMedia</name> | |||||
| <direction>out</direction> <relatedStateVariable>PossibleRecordStorageMedia</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>RecQualityModes</name> | |||||
| <direction>out</direction> <relatedStateVariable>PossibleRecordQualityModes</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>GetTransportSettings</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>PlayMode</name> | |||||
| <direction>out</direction> <relatedStateVariable>CurrentPlayMode</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>RecQualityMode</name> | |||||
| <direction>out</direction> <relatedStateVariable>CurrentRecordQualityMode</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>Stop</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>Play</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Speed</name> | |||||
| <direction>in</direction> <relatedStateVariable>TransportPlaySpeed</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> <Optional/> | |||||
| <name>Pause</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> <Optional/> | |||||
| <name>Record</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>Seek</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Unit</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_SeekMode</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Target</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_SeekTarget</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>Next</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>Previous</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> <Optional/> | |||||
| <name>SetPlayMode</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>NewPlayMode</name> | |||||
| <direction>in</direction> <relatedStateVariable>CurrentPlayMode</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> <Optional/> | |||||
| <name>SetRecordQualityMode</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>NewRecordQualityMode</name> | |||||
| <direction>in</direction> <relatedStateVariable>CurrentRecordQualityMode</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> <Optional/> | |||||
| <name>GetCurrentTransportActions</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Actions</name> | |||||
| <direction>out</direction> <relatedStateVariable>CurrentTransportActions</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| </actionList> | |||||
| </scpd> | |||||
| @@ -1,170 +0,0 @@ | |||||
| <scpd> | |||||
| <serviceStateTable> | |||||
| <stateVariable> | |||||
| <name>SourceProtocolInfo</name> | |||||
| <sendEventsAttribute>yes</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>SinkProtocolInfo</name> | |||||
| <sendEventsAttribute>yes</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>CurrentConnectionIDs</name> | |||||
| <sendEventsAttribute>yes</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_ConnectionStatus</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| <allowedValueList> | |||||
| <allowedValue>OK</allowedValue> | |||||
| <allowedValue>ContentFormatMismatch</allowedValue> | |||||
| <allowedValue>InsufficientBandwidth</allowedValue> | |||||
| <allowedValue>UnreliableChannel</allowedValue> | |||||
| <allowedValue>Unknown</allowedValue> | |||||
| </allowedValueList> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_ConnectionManager</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_Direction</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| <allowedValueList> | |||||
| <allowedValue>Input</allowedValue> | |||||
| <allowedValue>Output</allowedValue> | |||||
| </allowedValueList> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_ProtocolInfo</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_ConnectionID</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>i4</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_AVTransportID</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>i4</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_RcsID</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>i4</dataType> | |||||
| </stateVariable> | |||||
| </serviceStateTable> | |||||
| <actionList> | |||||
| <action> | |||||
| <name>GetProtocolInfo</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>Source</name> | |||||
| <direction>out</direction> <relatedStateVariable>SourceProtocolInfo</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Sink</name> | |||||
| <direction>out</direction> <relatedStateVariable>SinkProtocolInfo</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <Optional/> | |||||
| <name>PrepareForConnection</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>RemoteProtocolInfo</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_ProtocolInfo</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>PeerConnectionManager</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_ConnectionManager</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>PeerConnectionID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Direction</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_Direction</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>ConnectionID</name> | |||||
| <direction>out</direction> <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>AVTransportID</name> | |||||
| <direction>out</direction> <relatedStateVariable>A_ARG_TYPE_AVTransportID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>RcsID</name> | |||||
| <direction>out</direction> <relatedStateVariable>A_ARG_TYPE_RcsID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <Optional/> | |||||
| <name>ConnectionComplete</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>ConnectionID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>GetCurrentConnectionIDs</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>ConnectionIDs</name> | |||||
| <direction>out</direction> <relatedStateVariable>CurrentConnectionIDs</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>GetCurrentConnectionInfo</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>ConnectionID</name> | |||||
| <direction>in</direction> <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>RcsID</name> | |||||
| <direction>out</direction> <relatedStateVariable>A_ARG_TYPE_RcsID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>AVTransportID</name> | |||||
| <direction>out</direction> <relatedStateVariable>A_ARG_TYPE_AVTransportID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>ProtocolInfo</name> | |||||
| <direction>out</direction> <relatedStateVariable>A_ARG_TYPE_ProtocolInfo</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>PeerConnectionManager</name> | |||||
| <direction>out</direction> <relatedStateVariable>A_ARG_TYPE_ConnectionManager</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>PeerConnectionID</name> | |||||
| <direction>out</direction> <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Direction</name> | |||||
| <direction>out</direction> <relatedStateVariable>A_ARG_TYPE_Direction</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Status</name> | |||||
| <direction>out</direction> <relatedStateVariable>A_ARG_TYPE_ConnectionStatus</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| </actionList> | |||||
| </scpd> | |||||
| @@ -1,405 +0,0 @@ | |||||
| <scpd> | |||||
| <serviceStateTable> | |||||
| <stateVariable> <Optional/> | |||||
| <name>TransferIDs</name> | |||||
| <sendEventsAttribute>yes</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_ObjectID</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_Result</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> <Optional/> | |||||
| <name>A_ARG_TYPE_SearchCriteria</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_BrowseFlag</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| <allowedValueList> | |||||
| <allowedValue>BrowseMetadata</allowedValue> | |||||
| <allowedValue>BrowseDirectChildren</allowedValue> | |||||
| </allowedValueList> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_Filter</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_SortCriteria</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_Index</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui4</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_Count</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui4</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_UpdateID</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui4</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> <Optional/> | |||||
| <name>A_ARG_TYPE_TransferID</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui4</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> <Optional/> | |||||
| <name>A_ARG_TYPE_TransferStatus</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| <allowedValueList> | |||||
| <allowedValue>COMPLETED</allowedValue> | |||||
| <allowedValue>ERROR</allowedValue> | |||||
| <allowedValue>IN_PROGRESS</allowedValue> | |||||
| <allowedValue>STOPPED</allowedValue> | |||||
| </allowedValueList> | |||||
| </stateVariable> | |||||
| <stateVariable> <Optional/> | |||||
| <name>A_ARG_TYPE_TransferLength</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> <Optional/> | |||||
| <name>A_ARG_TYPE_TransferTotal</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> <Optional/> | |||||
| <name>A_ARG_TYPE_TagValueList</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> <Optional/> | |||||
| <name>A_ARG_TYPE_URI</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>uri</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>SearchCapabilities</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>SortCapabilities</name> | |||||
| <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>SystemUpdateID</name> | |||||
| <sendEventsAttribute>yes</sendEventsAttribute> | |||||
| <dataType>ui4</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> <Optional/> | |||||
| <name>ContainerUpdateIDs</name> | |||||
| <sendEventsAttribute>yes</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| </serviceStateTable> | |||||
| <actionList> | |||||
| <action> | |||||
| <name>GetSearchCapabilities</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>SearchCaps</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>SearchCapabilities </relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>GetSortCapabilities</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>SortCaps</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>SortCapabilities</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>GetSystemUpdateID</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>Id</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>SystemUpdateID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>Browse</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>ObjectID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>BrowseFlag</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_BrowseFlag</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Filter</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>StartingIndex</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>RequestedCount</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>SortCriteria</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Result</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>NumberReturned</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>TotalMatches</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>UpdateID</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>Search</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>ContainerID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>SearchCriteria</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_SearchCriteria </relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Filter</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>StartingIndex</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>RequestedCount</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>SortCriteria</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Result</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>NumberReturned</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>TotalMatches</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>UpdateID</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>CreateObject</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>ContainerID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Elements</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>ObjectID</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Result</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>DestroyObject</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>ObjectID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>UpdateObject</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>ObjectID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentTagValue</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_TagValueList </relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>NewTagValue</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_TagValueList </relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>ImportResource</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>SourceURI</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DestinationURI</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>TransferID</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_TransferID </relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>ExportResource</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>SourceURI</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DestinationURI</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>TransferID</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_TransferID </relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>StopTransferResource</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>TransferID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_TransferID </relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetTransferProgress</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>TransferID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_TransferID </relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>TransferStatus</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_TransferStatus </relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>TransferLength</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_TransferLength </relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>TransferTotal</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_TransferTotal</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>DeleteResource</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>ResourceURI</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>CreateReference</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>ContainerID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>ObjectID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>NewID</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| </actionList> | |||||
| </scpd> | |||||
| @@ -1,718 +0,0 @@ | |||||
| <scpd> | |||||
| <serviceStateTable> | |||||
| <stateVariable> | |||||
| <name>PresetNameList</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>LastChange</name> <sendEventsAttribute>yes</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>Brightness</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui2</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>Contrast</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui2</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>Sharpness</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui2</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>RedVideoGain</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui2</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>GreenVideoGain</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui2</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>BlueVideoGain</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui2</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>RedVideoBlackLevel</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui2</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>GreenVideoBlackLevel</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui2</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>BlueVideoBlackLevel</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui2</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>ColorTemperature</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui2</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>HorizontalKeystone</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>i2</dataType> | |||||
| <allowedValueRange> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>VerticalKeystone</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>i2</dataType> | |||||
| <allowedValueRange> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>Mute</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>boolean</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>Volume</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui2</dataType> | |||||
| <allowedValueRange> | |||||
| <minimum>0</minimum> | |||||
| <step>1</step> | |||||
| </allowedValueRange> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>VolumeDB</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>i2</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>Loudness</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>boolean</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>A_ARG_TYPE_Channel</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| <allowedValueList> | |||||
| <allowedValue>Master</allowedValue> | |||||
| </allowedValueList> | |||||
| </stateVariable> | |||||
| <stateVariable><Optional/> | |||||
| <name>A_ARG_TYPE_InstanceID</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>ui4</dataType> | |||||
| </stateVariable> | |||||
| <stateVariable> | |||||
| <name>A_ARG_TYPE_PresetName</name> <sendEventsAttribute>no</sendEventsAttribute> | |||||
| <dataType>string</dataType> | |||||
| <allowedValueList> | |||||
| <allowedValue>FactoryDefaults</allowedValue> | |||||
| </allowedValueList> | |||||
| </stateVariable> | |||||
| </serviceStateTable> | |||||
| <actionList> | |||||
| <action> | |||||
| <name>ListPresets</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentPresetNameList</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>PresetNameList</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action> | |||||
| <name>SelectPreset</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>PresetName</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_PresetName</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetBrightness</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentBrightness</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>Brightness</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetBrightness</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredBrightness</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>Brightness</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetContrast</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentContrast</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>Contrast</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetContrast</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredContrast</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>Contrast</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetSharpness</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentSharpness</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>Sharpness</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetSharpness</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredSharpness</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>Sharpness</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetRedVideoGain</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentRedVideoGain</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>RedVideoGain</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetRedVideoGain</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredRedVideoGain</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>RedVideoGain</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetGreenVideoGain</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentGreenVideoGain</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>GreenVideoGain</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetGreenVideoGain</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredGreenVideoGain</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>GreenVideoGain</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetBlueVideoGain</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentBlueVideoGain</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>BlueVideoGain</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetBlueVideoGain</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredBlueVideoGain</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>BlueVideoGain</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetRedVideoBlackLevel</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentRedVideoBlackLevel</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>RedVideoBlackLevel</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetRedVideoBlackLevel</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredRedVideoBlackLevel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>RedVideoBlackLevel</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetGreenVideoBlackLevel</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentGreenVideoBlackLevel</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetGreenVideoBlackLevel</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredGreenVideoBlackLevel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetBlueVideoBlackLevel</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentBlueVideoBlackLevel</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetBlueVideoBlackLevel</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredBlueVideoBlackLevel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetColorTemperature </name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentColorTemperature</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>ColorTemperature</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetColorTemperature</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredColorTemperature</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>ColorTemperature</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetHorizontalKeystone</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentHorizontalKeystone</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>HorizontalKeystone</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetHorizontalKeystone</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredHorizontalKeystone</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>HorizontalKeystone</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetVerticalKeystone</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentVerticalKeystone</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>VerticalKeystone</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetVerticalKeystone</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredVerticalKeystone</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>VerticalKeystone</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetMute</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Channel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentMute</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>Mute</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetMute</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Channel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredMute</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>Mute</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetVolume</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Channel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentVolume</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>Volume</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetVolume</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Channel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredVolume</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>Volume</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetVolumeDB</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Channel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentVolume</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>VolumeDB</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetVolumeDB</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Channel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredVolume</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>VolumeDB</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetVolumeDBRange</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Channel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>MinValue</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>VolumeDB</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>MaxValue</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>VolumeDB</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>GetLoudness</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Channel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>CurrentLoudness</name> | |||||
| <direction>out</direction> | |||||
| <relatedStateVariable>Loudness</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| <action><Optional/> | |||||
| <name>SetLoudness</name> | |||||
| <argumentList> | |||||
| <argument> | |||||
| <name>InstanceID</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>Channel</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable> | |||||
| </argument> | |||||
| <argument> | |||||
| <name>DesiredLoudness</name> | |||||
| <direction>in</direction> | |||||
| <relatedStateVariable>Loudness</relatedStateVariable> | |||||
| </argument> | |||||
| </argumentList> | |||||
| </action> | |||||
| </actionList> | |||||
| </scpd> | |||||
| @@ -1,168 +0,0 @@ | |||||
| <ServiceControlSyntaxTestCases> | |||||
| <ServiceType>AVTransport</ServiceType> | |||||
| <ServiceVersion>1</ServiceVersion> | |||||
| <TestCaseList> | |||||
| <TestCase> | |||||
| <Id>1</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetAVTransportURI</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <CurrentURI>any-string</CurrentURI> | |||||
| <CurrentURIMetaData>any-string</CurrentURIMetaData> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>2</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetNextAVTransportURI</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <NextURI>any-string</NextURI> | |||||
| <NextURIMetaData>any-string</NextURIMetaData> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>3</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetMediaInfo</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>4</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetTransportInfo</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>5</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetPositionInfo</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>6</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetDeviceCapabilities</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>7</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetTransportSettings</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>8</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>Stop</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>9</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>Play</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <Speed>1</Speed> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>10</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>Pause</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>11</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>Record</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>12</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>Seek</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <Unit>TRACK_NR</Unit> | |||||
| <Target>1</Target> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>13</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>Next</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>14</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>Previous</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>15</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetPlayMode</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <NewPlayMode>NORMAL</NewPlayMode> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>16</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetRecordQualityMode</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <NewRecordQualityMode>any-string</NewRecordQualityMode> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>17</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetCurrentTransportActions</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| </TestCaseList> | |||||
| </ServiceControlSyntaxTestCases> | |||||
| @@ -1,48 +0,0 @@ | |||||
| <ServiceControlSyntaxTestCases> | |||||
| <ServiceType>ConnectionManager</ServiceType> | |||||
| <ServiceVersion>1</ServiceVersion> | |||||
| <TestCaseList> | |||||
| <TestCase> | |||||
| <Id>1</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetProtocolInfo</ActionName> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>2</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>PrepareForConnection</ActionName> | |||||
| <InArgs> | |||||
| <RemoteProtocolInfo>any-string</RemoteProtocolInfo> | |||||
| <PeerConnectionManager>any-string</PeerConnectionManager> | |||||
| <PeerConnectionID>-1</PeerConnectionID> | |||||
| <Direction>Input</Direction> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>3</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>ConnectionComplete</ActionName> | |||||
| <InArgs> | |||||
| <ConnectionID>0</ConnectionID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>4</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetCurrentConnectionIDs</ActionName> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>5</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetCurrentConnectionInfo</ActionName> | |||||
| <InArgs> | |||||
| <ConnectionID>0</ConnectionID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| </TestCaseList> | |||||
| </ServiceControlSyntaxTestCases> | |||||
| @@ -1,146 +0,0 @@ | |||||
| <ServiceControlSyntaxTestCases> | |||||
| <ServiceType>ContentDirectory</ServiceType> | |||||
| <ServiceVersion>1</ServiceVersion> | |||||
| <TestCaseList> | |||||
| <TestCase> | |||||
| <Id>1</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetSearchCapabilities</ActionName> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>2</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetSortCapabilities</ActionName> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>3</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetSystemUpdateID</ActionName> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>4</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>Browse</ActionName> | |||||
| <InArgs> | |||||
| <ObjectID>0</ObjectID> | |||||
| <BrowseFlag>BrowseMetadata</BrowseFlag> | |||||
| <Filter>dc:title</Filter> | |||||
| <StartingIndex>0</StartingIndex> | |||||
| <RequestedCount>0</RequestedCount> | |||||
| <SortCriteria></SortCriteria> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>5</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>Search</ActionName> | |||||
| <InArgs> | |||||
| <ContainerID>0</ContainerID> | |||||
| <SearchCriteria>dc:title contains "Rock"</SearchCriteria> | |||||
| <Filter>dc:title</Filter> | |||||
| <StartingIndex>0</StartingIndex> | |||||
| <RequestedCount>0</RequestedCount> | |||||
| <SortCriteria></SortCriteria> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>6</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>CreateObject</ActionName> | |||||
| <InArgs> | |||||
| <ContainerID>0</ContainerID> | |||||
| <Elements> | |||||
| <DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"> | |||||
| <item id="" parentID="0" restricted="false"> | |||||
| <dc:title>Test Object - CDS Syntax Text Case #6</dc:title> | |||||
| <upnp:class>object.item</upnp:class> | |||||
| </item> | |||||
| </DIDL-Lite> | |||||
| </Elements> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>7</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>DestroyObject</ActionName> | |||||
| <InArgs> | |||||
| <ObjectID>0</ObjectID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>8</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>UpdateObject</ActionName> | |||||
| <InArgs> | |||||
| <ObjectID>0</ObjectID> | |||||
| <CurrentTagValue>any-string</CurrentTagValue> | |||||
| <NewTagValue>any-string</NewTagValue> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>9</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>ImportResource</ActionName> | |||||
| <InArgs> | |||||
| <SourceURI>http://host/path/file</SourceURI> | |||||
| <DestinationURI>http://host/path/file</DestinationURI> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>10</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>ExportResource</ActionName> | |||||
| <InArgs> | |||||
| <SourceURI>http://host/path/file</SourceURI> | |||||
| <DestinationURI>http://host/path/file</DestinationURI> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>11</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>StopTransferResource</ActionName> | |||||
| <InArgs> | |||||
| <TransferID>0</TransferID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>12</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetTransferProgress</ActionName> | |||||
| <InArgs> | |||||
| <TransferID>0</TransferID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>13</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>DeleteResource</ActionName> | |||||
| <InArgs> | |||||
| <ResourceURI>http://host/path/file</ResourceURI> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>14</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>CreateReference</ActionName> | |||||
| <InArgs> | |||||
| <ContainerID>0</ContainerID> | |||||
| <ObjectID>0</ObjectID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| </TestCaseList> | |||||
| </ServiceControlSyntaxTestCases> | |||||
| @@ -1,347 +0,0 @@ | |||||
| <ServiceControlSyntaxTestCases> | |||||
| <ServiceType>RenderingControl</ServiceType> | |||||
| <ServiceVersion>1</ServiceVersion> | |||||
| <TestCaseList> | |||||
| <TestCase> | |||||
| <Id>1</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>ListPresets</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>2</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SelectPreset</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <PresetName>FactoryDefaults</PresetName> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>3</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetBrightness</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>4</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetBrightness</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredBrightness>0</DesiredBrightness> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>5</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetContrast</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>6</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetContrast</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredContrast>0</DesiredContrast> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>7</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetRedVideoGain</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>8</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetRedVideoGain</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredRedVideoGain>0</DesiredRedVideoGain> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>9</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetGreenVideoGain</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>10</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetGreenVideoGain</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredGreenVideoGain>0</DesiredGreenVideoGain> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>11</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetBlueVideoGain</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>12</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetBlueVideoGain</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredBlueVideoGain>0</DesiredBlueVideoGain> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>13</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetRedVideoBlackLevel</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>14</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetRedVideoBlackLevel</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredRedVideoBlackLevel>0</DesiredRedVideoBlackLevel> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>15</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetGreenVideoBlackLevel</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>16</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetGreenVideoBlackLevel</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredGreenVideoBlackLevel>0</DesiredGreenVideoBlackLevel> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>17</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetBlueVideoBlackLevel</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>18</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetBlueVideoBlackLevel</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredBlueVideoBlackLevel>0</DesiredBlueVideoBlackLevel> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>19</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetColorTemperature</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>20</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetColorTemperature</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredColorTemperature>0</DesiredColorTemperature> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>21</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetHorizontalKeystone</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>22</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetHorizontalKeystone</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredHorizontalKeystone>0</DesiredHorizontalKeystone> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>23</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetVerticalKeystone</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>24</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetVerticalKeystone</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredVerticalKeystone>0</DesiredVerticalKeystone> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>25</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetSharpness</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>26</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetSharpness</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <DesiredSharpness>0</DesiredSharpness> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>27</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetMute</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <Channel>Master</Channel> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>28</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetMute</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <Channel>Master</Channel> | |||||
| <DesiredMute>1</DesiredMute> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>29</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetVolume</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <Channel>Master</Channel> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>30</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetVolume</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <Channel>Master</Channel> | |||||
| <DesiredVolume>0</DesiredVolume> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>31</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetVolumeDB</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <Channel>Master</Channel> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>32</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetVolumeDB</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <Channel>Master</Channel> | |||||
| <DesiredVolume>0</DesiredVolume> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>33</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetVolumeDBRange</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <Channel>Master</Channel> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>34</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>GetLoudness</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <Channel>Master</Channel> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| <TestCase> | |||||
| <Id>35</Id> | |||||
| <Category>Valid Action And Valid InArgs</Category> | |||||
| <ActionName>SetLoudness</ActionName> | |||||
| <InArgs> | |||||
| <InstanceID>0</InstanceID> | |||||
| <Channel>Master</Channel> | |||||
| <DesiredLoudness>1</DesiredLoudness> | |||||
| </InArgs> | |||||
| <ExpectedReturnCode>ACTION_AND_INARGS_ARE_VALID</ExpectedReturnCode> | |||||
| </TestCase> | |||||
| </TestCaseList> | |||||
| </ServiceControlSyntaxTestCases> | |||||
| @@ -1,183 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2006 John-Mark Gurney <jmg@funkthat.com> | |||||
| '''DVD Handling''' | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| default_audio_lang = 'en' | |||||
| import itertools | |||||
| import os | |||||
| import sys | |||||
| sys.path.append('mpegts') | |||||
| try: | |||||
| import mpegts | |||||
| audiofilter = lambda x, y: mpegts.DVDAudioFilter(x, y) | |||||
| except ImportError: | |||||
| print >>sys.stderr, 'module mpegts could not be loaded, not filtering audio' | |||||
| audiofilter = lambda x, y: x | |||||
| from pydvdread import * | |||||
| from DIDLLite import StorageFolder, Movie, VideoItem, Resource | |||||
| from FSStorage import FSObject, registerklassfun | |||||
| from twisted.python import log, threadable | |||||
| from twisted.spread import pb | |||||
| from twisted.web import resource, server | |||||
| def gennameindexes(pref, item): | |||||
| d = {} | |||||
| for i, title in enumerate(item): | |||||
| t = '%s %d (%s)' % (pref, i + 1, title.time) | |||||
| d[t] = i | |||||
| return d | |||||
| class IterTransfer(pb.Viewable): | |||||
| def __init__(self, iterable, request): | |||||
| self.iter = iter(iterable) | |||||
| self.request = request | |||||
| request.registerProducer(self, 0) | |||||
| def resumeProducing(self): | |||||
| if not self.request: | |||||
| return | |||||
| # get data and write to request. | |||||
| try: | |||||
| data = self.iter.next() | |||||
| if 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) | |||||
| except StopIteration: | |||||
| if self.request: | |||||
| self.request.unregisterProducer() | |||||
| self.request.finish() | |||||
| self.request = None | |||||
| def pauseProducing(self): | |||||
| pass | |||||
| def stopProducing(self): | |||||
| # close zipfile | |||||
| self.request = None | |||||
| # Remotely relay producer interface. | |||||
| def view_resumeProducing(self, issuer): | |||||
| self.resumeProducing() | |||||
| def view_pauseProducing(self, issuer): | |||||
| self.pauseProducing() | |||||
| def view_stopProducing(self, issuer): | |||||
| self.stopProducing() | |||||
| synchronized = ['resumeProducing', 'stopProducing'] | |||||
| threadable.synchronize(IterTransfer) | |||||
| class IterGenResource(resource.Resource): | |||||
| isLeaf = True | |||||
| def __init__(self, itergen): | |||||
| resource.Resource.__init__(self) | |||||
| self.itergen = itergen | |||||
| def render(self, request): | |||||
| request.setHeader('content-type', 'video/mpeg') | |||||
| if request.method == 'HEAD': | |||||
| return '' | |||||
| # return data | |||||
| IterTransfer(self.itergen(), request) | |||||
| # and make sure the connection doesn't get closed | |||||
| return server.NOT_DONE_YET | |||||
| class DVDChapter(VideoItem): | |||||
| def __init__(self, *args, **kwargs): | |||||
| self.dvdtitle = kwargs['dvdtitle'] | |||||
| self.chapter = kwargs['chapter'] | |||||
| del kwargs['dvdtitle'], kwargs['chapter'] | |||||
| audio = self.dvdtitle.selectaudio(default_audio_lang) | |||||
| kwargs['content'] = IterGenResource(lambda i = self.chapter, | |||||
| p = audio.pos: audiofilter(i, 0x80 + p)) | |||||
| VideoItem.__init__(self, *args, **kwargs) | |||||
| self.url = '%s/%s' % (self.cd.urlbase, self.id) | |||||
| self.res = Resource(self.url, 'http-get:*:video/mpeg:*') | |||||
| #self.res.size = self.chapter.size | |||||
| def doUpdate(self): | |||||
| pass | |||||
| class DVDTitle(StorageFolder): | |||||
| def __init__(self, *args, **kwargs): | |||||
| self.dvdtitle = kwargs['dvdtitle'] | |||||
| self.dvddisc = kwargs['dvddisc'] | |||||
| del kwargs['dvdtitle'], kwargs['dvddisc'] | |||||
| audio = self.dvdtitle.selectaudio(default_audio_lang) | |||||
| kwargs['content'] = IterGenResource(lambda dt = self.dvdtitle, | |||||
| p = audio.pos: audiofilter(itertools.chain(*dt), 0x80 + p)) | |||||
| StorageFolder.__init__(self, *args, **kwargs) | |||||
| self.url = '%s/%s' % (self.cd.urlbase, self.id) | |||||
| self.res = Resource(self.url, 'http-get:*:video/mpeg:*') | |||||
| # mapping from path to objectID | |||||
| self.pathObjmap = {} | |||||
| def checkUpdate(self): | |||||
| self.doUpdate() | |||||
| #return self.dvddisc.checkUpdate() | |||||
| def genChildren(self): | |||||
| return gennameindexes('Chapter', self.dvdtitle) | |||||
| def createObject(self, i, arg): | |||||
| return DVDChapter, i, (), { 'dvdtitle': self.dvdtitle, | |||||
| 'chapter': self.dvdtitle[arg] } | |||||
| class DVDDisc(FSObject, StorageFolder): | |||||
| def __init__(self, *args, **kwargs): | |||||
| path = kwargs['path'] | |||||
| del kwargs['path'] | |||||
| StorageFolder.__init__(self, *args, **kwargs) | |||||
| FSObject.__init__(self, path) | |||||
| def genChildren(self): | |||||
| self.dvd = DVD(self.FSpath) | |||||
| return gennameindexes('Title', self.dvd) | |||||
| def createObject(self, i, arg): | |||||
| return DVDTitle, i, (), { 'dvdtitle': self.dvd[arg], | |||||
| 'dvddisc': self } | |||||
| def detectdvd(path, fobj): | |||||
| if os.path.isdir(path): | |||||
| # Make sure we there is only a VIDEO_TS in there, even | |||||
| # if there is a VIDEO_TS w/ other files, we will open | |||||
| # the VIDEO_TS as a DVD (if it is one) | |||||
| ld = os.listdir(path) | |||||
| if ld == ['VIDEO_TS' ]: | |||||
| pass | |||||
| elif not filter(lambda x: x[:4] != 'VTS_' and | |||||
| x[:9] != 'VIDEO_TS.', ld): | |||||
| pass | |||||
| else: | |||||
| return None, None | |||||
| d = DVD(path) | |||||
| return DVDDisc, { 'path': path } | |||||
| registerklassfun(detectdvd) | |||||
| @@ -1,145 +0,0 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| # Copyright 2006,2007 Frank Scholz <coherence@beebits.net> | |||||
| # | |||||
| # a little helper to get the proper ElementTree package | |||||
| import re | |||||
| try: | |||||
| import cElementTree as ET | |||||
| import elementtree | |||||
| #print "we are on CET" | |||||
| except ImportError: | |||||
| try: | |||||
| from elementtree import ElementTree as ET | |||||
| import elementtree | |||||
| #print "simply using ET" | |||||
| except ImportError: | |||||
| """ this seems to be necessary with the python2.5 on the Maemo platform """ | |||||
| try: | |||||
| from xml.etree import ElementTree as ET | |||||
| from xml import etree as elementtree | |||||
| except ImportError: | |||||
| try: | |||||
| from xml.etree import ElementTree as ET | |||||
| from xml import etree as elementtree | |||||
| except ImportError: | |||||
| #print "no ElementTree module found, critical error" | |||||
| raise ImportError("no ElementTree module found, critical error") | |||||
| utf8_escape = re.compile(eval(r'u"[&<>\"]+"')) | |||||
| escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"')) | |||||
| def encode_entity(text, pattern=escape): | |||||
| # map reserved and non-ascii characters to numerical entities | |||||
| def escape_entities(m, map=elementtree.ElementTree._escape_map): | |||||
| out = [] | |||||
| append = out.append | |||||
| for char in m.group(): | |||||
| t = map.get(char) | |||||
| if t is None: | |||||
| t = "&#%d;" % ord(char) | |||||
| append(t) | |||||
| return ''.join(out) | |||||
| try: | |||||
| return elementtree.ElementTree._encode(pattern.sub(escape_entities, text), 'ascii') | |||||
| except TypeError: | |||||
| elementtree.ElementTree._raise_serialization_error(text) | |||||
| def new_encode_entity(text, pattern=utf8_escape): | |||||
| # map reserved and non-ascii characters to numerical entities | |||||
| def escape_entities(m, map=elementtree.ElementTree._escape_map): | |||||
| out = [] | |||||
| append = out.append | |||||
| for char in m.group(): | |||||
| t = map.get(char) | |||||
| if t is None: | |||||
| t = "&#%d;" % ord(char) | |||||
| append(t) | |||||
| if isinstance(text, str): | |||||
| return ''.join(out) | |||||
| else: | |||||
| return ''.encode('utf-8').join(out) | |||||
| try: | |||||
| if isinstance(text, str): | |||||
| return elementtree.ElementTree._encode(escape.sub(escape_entities, text), 'ascii') | |||||
| else: | |||||
| return elementtree.ElementTree._encode(utf8_escape.sub(escape_entities, text.decode('utf-8')), 'utf-8') | |||||
| except TypeError: | |||||
| elementtree.ElementTree._raise_serialization_error(text) | |||||
| elementtree.ElementTree._encode_entity = new_encode_entity | |||||
| # it seems there are some ElementTree libs out there | |||||
| # which have the alias XMLParser and some that haven't. | |||||
| # | |||||
| # So we just use the XMLTreeBuilder method for now | |||||
| # if XMLParser isn't available. | |||||
| if not hasattr(ET, 'XMLParser'): | |||||
| def XMLParser(encoding='utf-8'): | |||||
| return ET.XMLTreeBuilder() | |||||
| ET.XMLParser = XMLParser | |||||
| def namespace_map_update(namespaces): | |||||
| #try: | |||||
| # from xml.etree import ElementTree | |||||
| #except ImportError: | |||||
| # from elementtree import ElementTree | |||||
| elementtree.ElementTree._namespace_map.update(namespaces) | |||||
| class ElementInterface(elementtree.ElementTree.Element): | |||||
| """ helper class """ | |||||
| def indent(elem, level=0): | |||||
| """ generate pretty looking XML, based upon: | |||||
| http://effbot.org/zone/element-lib.htm#prettyprint | |||||
| """ | |||||
| i = "\n" + level*" " | |||||
| if len(elem): | |||||
| if not elem.text or not elem.text.strip(): | |||||
| elem.text = i + " " | |||||
| for elem in elem: | |||||
| indent(elem, level+1) | |||||
| if not elem.tail or not elem.tail.strip(): | |||||
| elem.tail = i | |||||
| if not elem.tail or not elem.tail.strip(): | |||||
| elem.tail = i | |||||
| else: | |||||
| if level and (not elem.tail or not elem.tail.strip()): | |||||
| elem.tail = i | |||||
| def parse_xml(data, encoding="utf-8"): | |||||
| p = ET.XMLParser(encoding=encoding) | |||||
| # my version of twisted.web returns page_infos as a dictionary in | |||||
| # the second item of the data list | |||||
| if isinstance(data, (list, tuple)): | |||||
| data, _ = data | |||||
| try: | |||||
| data = data.encode(encoding) | |||||
| except UnicodeDecodeError: | |||||
| pass | |||||
| except Exception as error: | |||||
| print("parse_xml encode Exception", error) | |||||
| import traceback | |||||
| traceback.print_exc() | |||||
| # Guess from who we're getting this? | |||||
| data = data.replace('\x00','') | |||||
| try: | |||||
| p.feed(data) | |||||
| except Exception as error: | |||||
| print("parse_xml feed Exception", error) | |||||
| print(error, repr(data)) | |||||
| return None | |||||
| else: | |||||
| return ET.ElementTree(p.close()) | |||||
| @@ -1,33 +0,0 @@ | |||||
| import fcntl | |||||
| import shelve | |||||
| class FileLock(object): | |||||
| def __init__(self, fname = None): | |||||
| self.f = open(fname, 'w+') | |||||
| self.islocked = False | |||||
| def close(self): | |||||
| self.f.close() | |||||
| self.islocked = None | |||||
| def exclusivelock(self): | |||||
| fcntl.flock(self.f.fileno(), fcntl.LOCK_EX) | |||||
| self.islocked = True | |||||
| def sharedlock(self): | |||||
| fcntl.flock(self.f.fileno(), fcntl.LOCK_SH) | |||||
| self.islocked = True | |||||
| def unlock(self): | |||||
| fcntl.flock(self.f.fileno(), fcntl.LOCK_UN) | |||||
| self.islocked = False | |||||
| class LockShelve(FileLock, shelve.DbfilenameShelf): | |||||
| def __init__(self, fname, *args, **kwargs): | |||||
| FileLock.__init__(self, fname + ".lock") | |||||
| self.exclusivelock() | |||||
| try: | |||||
| shelve.DbfilenameShelf.__init__(self, fname, *args, **kwargs) | |||||
| except: | |||||
| self.close() | |||||
| raise | |||||
| @@ -1,772 +0,0 @@ | |||||
| import array | |||||
| import os.path | |||||
| import string | |||||
| import UserDict | |||||
| from ctypes import * | |||||
| __all__ = [ 'FLACDec' ] | |||||
| # Find out if we need to endian swap the buffer | |||||
| def _islittleendian(): | |||||
| '''Run a check to see if the box is little endian or not.''' | |||||
| t = array.array('H', '\x00\x01') | |||||
| if t[0] == 1: | |||||
| return False | |||||
| else: | |||||
| return True | |||||
| is_little_endian = _islittleendian() | |||||
| inter = {} | |||||
| try: | |||||
| dname = os.path.dirname(__file__) | |||||
| if not dname: | |||||
| dname = '.' | |||||
| path = os.path.join(dname, '_interleave.so') | |||||
| interleavelib = CDLL(path) | |||||
| for i in (16, ): | |||||
| f = getattr(interleavelib, 'interleave%d' % i) | |||||
| f.restype = None | |||||
| # bigendian, nchan, chanmap, chansamps, data, out | |||||
| outtype = locals()['c_int%d' % i] | |||||
| f.argtypes = [ c_int, POINTER(c_int), c_int, | |||||
| POINTER(POINTER(c_int32)), POINTER(outtype), ] | |||||
| inter[i] = outtype, f | |||||
| except OSError: | |||||
| import warnings | |||||
| warnings.warn('Using slow interleaver, compile the _interleave.so module to improve performance.') | |||||
| inter[16] = None, lambda nchan, chanmap, blksize, inbuf, ignore: \ | |||||
| array.array('h', [ inbuf[chanmap[x % nchan]][x / nchan] for x in | |||||
| xrange(blksize * nchan)]) | |||||
| try: | |||||
| flaclib = CDLL('libFLAC.so') | |||||
| except OSError, e: | |||||
| import warnings | |||||
| warnings.warn('libFLAC.so not installed. FLAC decoding not available.') | |||||
| raise ImportError(str(e)) | |||||
| # Defines | |||||
| FLAC__METADATA_TYPE_STREAMINFO = 0 | |||||
| FLAC__METADATA_TYPE_PADDING = 1 | |||||
| FLAC__METADATA_TYPE_APPLICATION = 2 | |||||
| FLAC__METADATA_TYPE_SEEKTABLE = 3 | |||||
| FLAC__METADATA_TYPE_VORBIS_COMMENT = 4 | |||||
| FLAC__METADATA_TYPE_CUESHEET = 5 | |||||
| FLAC__METADATA_TYPE_PICTURE = 6 | |||||
| FLAC__METADATA_TYPE_UNDEFINED = 7 | |||||
| FLAC__MAX_CHANNELS = 8 | |||||
| FLAC__MAX_LPC_ORDER = 32 | |||||
| FLAC__MAX_FIXED_ORDER = 4 | |||||
| FLAC__byte = c_uint8 | |||||
| FLAC__byte_p = POINTER(FLAC__byte) | |||||
| # Data | |||||
| FLAC__StreamDecoderStateString = (c_char_p * 10).in_dll(flaclib, | |||||
| 'FLAC__StreamDecoderStateString') | |||||
| FLAC__StreamDecoderInitStatusString = (c_char_p * 6).in_dll(flaclib, | |||||
| 'FLAC__StreamDecoderInitStatusString') | |||||
| FLAC__StreamDecoderErrorStatusString = (c_char_p * 4).in_dll(flaclib, | |||||
| 'FLAC__StreamDecoderErrorStatusString') | |||||
| FLAC__StreamMetadata_Picture_TypeString = (c_char_p * 21).in_dll(flaclib, | |||||
| 'FLAC__StreamMetadata_Picture_TypeString') | |||||
| # Enums | |||||
| FLAC__STREAM_DECODER_SEARCH_FOR_METADATA = 0 | |||||
| FLAC__STREAM_DECODER_READ_METADATA = 1 | |||||
| FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC = 2 | |||||
| FLAC__STREAM_DECODER_READ_FRAME = 3 | |||||
| FLAC__STREAM_DECODER_END_OF_STREAM = 4 | |||||
| FLAC__STREAM_DECODER_OGG_ERROR = 5 | |||||
| FLAC__STREAM_DECODER_SEEK_ERROR = 6 | |||||
| FLAC__STREAM_DECODER_ABORTED = 7 | |||||
| FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR = 8 | |||||
| FLAC__STREAM_DECODER_UNINITIALIZED = 9 | |||||
| FLAC__STREAM_DECODER_INIT_STATUS_OK = 0 | |||||
| FLAC__FRAME_NUMBER_TYPE_FRAME_NUMBER = 0 | |||||
| FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER = 1 | |||||
| FLAC__SUBFRAME_TYPE_CONSTANT = 0 | |||||
| FLAC__SUBFRAME_TYPE_VERBATIM = 1 | |||||
| FLAC__SUBFRAME_TYPE_FIXED = 2 | |||||
| FLAC__SUBFRAME_TYPE_LPC = 3 | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_OTHER = 0 # < Other */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD = 1 # < 32x32 pixels 'file icon' (PNG only) */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON = 2 # < Other file icon */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER = 3 # < Cover (front) */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_BACK_COVER = 4 # < Cover (back) */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_LEAFLET_PAGE = 5 # < Leaflet page */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_MEDIA = 6 # < Media (e.g. label side of CD) */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_LEAD_ARTIST = 7 # < Lead artist/lead performer/soloist */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_ARTIST = 8 # < Artist/performer */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_CONDUCTOR = 9 # < Conductor */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_BAND = 10 # < Band/Orchestra */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_COMPOSER = 11 # < Composer */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_LYRICIST = 12 # < Lyricist/text writer */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_RECORDING_LOCATION = 13 # < Recording Location */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_DURING_RECORDING = 14 # < During recording */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_DURING_PERFORMANCE = 15 # < During performance */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_VIDEO_SCREEN_CAPTURE = 16 # < Movie/video screen capture */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_FISH = 17 # < A bright coloured fish */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_ILLUSTRATION = 18 # < Illustration */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_BAND_LOGOTYPE = 19 # < Band/artist logotype */ | |||||
| FLAC__STREAM_METADATA_PICTURE_TYPE_PUBLISHER_LOGOTYPE = 20 # < Publisher/Studio logotype */ | |||||
| pictnamemap = dict(enumerate(('other', 'icon', 'icon', 'cover', 'backcover', | |||||
| 'leaflet', 'media', 'lead', 'artist', 'conductor', 'band', 'composer', | |||||
| 'lyricist', 'location', 'recording', 'performance', 'video', 'fish', | |||||
| 'illustration', 'bandlogo', 'publogo'))) | |||||
| OrigStructure = Structure | |||||
| class Structure(Structure): | |||||
| def getarray(self, base): | |||||
| base_array = getattr(self, base) | |||||
| return [ base_array[x] for x in xrange(getattr(self, | |||||
| 'num_' + base)) ] | |||||
| def __getattr__(self, k): | |||||
| if k[-6:] == '_array': | |||||
| return self.getarray(k[:-6]) | |||||
| raise AttributeError(k) | |||||
| def asobj(self): | |||||
| r = {} | |||||
| #print 'asobj:', `self` | |||||
| if hasattr(self, '_material_'): | |||||
| flds = self._material_ | |||||
| else: | |||||
| flds = self._fields_ | |||||
| for i in flds: | |||||
| attr = i[0] | |||||
| obj = getattr(self, attr) | |||||
| if attr[-6:] == '_array': | |||||
| #print 'asarraycnt' | |||||
| v = [ x.asobj() for x in | |||||
| self.getarray(attr[:-6]) ] | |||||
| elif isinstance(obj, Structure): | |||||
| #print 'asstruct' | |||||
| v = obj.asobj() | |||||
| elif isinstance(obj, Array) and obj._type_ != c_char: | |||||
| #print 'asarray' | |||||
| v = obj[:] | |||||
| else: | |||||
| #print 'asobj' | |||||
| v = obj | |||||
| r[attr] = v | |||||
| return r | |||||
| def __repr__(self, fields=None): | |||||
| cls = self.__class__ | |||||
| if fields is None: | |||||
| if hasattr(self, '_material_'): | |||||
| fields = self._material_ | |||||
| else: | |||||
| fields = self._fields_ | |||||
| return '<%s.%s: %s>' % (cls.__module__, cls.__name__, | |||||
| ', '.join([ '%s: %s' % (x[0], `getattr(self, x[0])`) | |||||
| for x in fields ])) | |||||
| class FLAC__StreamMetadata_StreamInfo(Structure): | |||||
| _fields_ = [ ('min_blocksize', c_uint), | |||||
| ('max_blocksize', c_uint), | |||||
| ('min_framesize', c_uint), | |||||
| ('max_framesize', c_uint), | |||||
| ('sample_rate', c_uint), | |||||
| ('channels', c_uint), | |||||
| ('bits_per_sample', c_uint), | |||||
| ('total_samples', c_uint64), | |||||
| ('md5sum', c_ubyte * 16), | |||||
| ] | |||||
| class FLAC__StreamMetadata_VorbisComment_Entry(Structure): | |||||
| _fields_ = [ ('length', c_uint32), | |||||
| ('entry', POINTER(c_char)), # string bounded by length | |||||
| ] | |||||
| def asstring(self): | |||||
| return self.entry[:self.length].decode('utf-8') | |||||
| def __repr__(self): | |||||
| return '<FLAC__StreamMetadata_VorbisComment_Entry: %s>' % \ | |||||
| `self.asstring()` | |||||
| def makestrrange(a, b): | |||||
| '''Make a string w/ characters from a through b (inclusive).''' | |||||
| return ''.join(chr(x) for x in xrange(a, b + 1)) | |||||
| class VorbisComments(UserDict.DictMixin): | |||||
| def __init__(self, vc=()): | |||||
| d = self._dict = {} | |||||
| for i in vc: | |||||
| k, v = i.split('=', 1) | |||||
| try: | |||||
| self[k].append(v) | |||||
| except KeyError: | |||||
| d[self.makevalidkey(k)] = [ v ] | |||||
| transtable = string.maketrans(makestrrange(0x41, 0x5a), | |||||
| makestrrange(0x61, 0x7a)) | |||||
| delchars = makestrrange(0, 0x1f) + '=' + makestrrange(0x7e, 0x7f) | |||||
| @staticmethod | |||||
| def makevalidkey(k): | |||||
| k = str(k) | |||||
| origlen = len(k) | |||||
| k = k.translate(VorbisComments.transtable, | |||||
| VorbisComments.delchars) | |||||
| if len(k) != origlen: | |||||
| raise ValueError('Invalid key') | |||||
| return k | |||||
| def __getitem__(self, k): | |||||
| return self._dict[self.makevalidkey(k)] | |||||
| def __setitem__(self, k, v): | |||||
| # XXX - allow? check v? | |||||
| return self._dict.__setitem__(self.makevalidkey(k), v) | |||||
| def __delitem__(self, k): | |||||
| return self._dict.__delitem__(self.makevalidkey(k)) | |||||
| def keys(self): | |||||
| return self._dict.keys() | |||||
| class FLAC__StreamMetadata_VorbisComment(Structure): | |||||
| _fields_ = [ ('vendor_string', | |||||
| FLAC__StreamMetadata_VorbisComment_Entry), | |||||
| ('num_comments', c_uint32), | |||||
| ('comments', POINTER(FLAC__StreamMetadata_VorbisComment_Entry)), | |||||
| ] | |||||
| _material_ = (('vendor_string', ), ('comments_array', )) | |||||
| def asobj(self): | |||||
| return self.vendor_string.asstring(), \ | |||||
| VorbisComments(x.asstring() for x in | |||||
| self.getarray('comments')) | |||||
| class FLAC__StreamMetadata_CueSheet_Index(Structure): | |||||
| _fields_ = [ ('offset', c_uint64), | |||||
| ('number', c_ubyte), | |||||
| ] | |||||
| class FLAC__StreamMetadata_CueSheet_Track(Structure): | |||||
| _fields_ = [ ('offset', c_uint64), | |||||
| ('number', c_ubyte), | |||||
| ('isrc', c_char * 13), # string + NUL | |||||
| ('type', c_ubyte, 1), | |||||
| ('pre_emphasis', c_ubyte, 1), | |||||
| ('num_indices', c_ubyte), | |||||
| ('indices', POINTER(FLAC__StreamMetadata_CueSheet_Index)), | |||||
| ] | |||||
| _material_ = (('offset', ), ('number', ), ('isrc', ), ('type', ), | |||||
| ('pre_emphasis', ), ('indices_array', )) | |||||
| class FLAC__StreamMetadata_CueSheet(Structure): | |||||
| _fields_ = [ ('media_catalog_number', c_char * 129), # string + nul | |||||
| ('lead_in', c_uint64), | |||||
| ('is_cd', c_int), | |||||
| ('num_tracks', c_uint), | |||||
| ('tracks', POINTER(FLAC__StreamMetadata_CueSheet_Track)), | |||||
| ] | |||||
| _material_ = (('media_catalog_number', ), ('lead_in', ), ('is_cd', ), | |||||
| ('tracks_array', )) | |||||
| class FLAC__StreamMetadata_Picture(Structure): | |||||
| _fields_ = [ ('type', c_int), | |||||
| ('mime_type', c_char_p), | |||||
| # description is listed as FLAC__byte_p, but | |||||
| # documented as NUL terminated UTF-8 string | |||||
| ('description', c_char_p), | |||||
| ('width', c_uint32), | |||||
| ('height', c_uint32), | |||||
| ('depth', c_uint32), | |||||
| ('colors', c_uint32), | |||||
| ('data_length', c_uint32), | |||||
| ('data', FLAC__byte_p), | |||||
| ] | |||||
| _material_ = (('type', ), ('mime_type', ), ('width', ), ('height', ), ('depth', ), ('colors', )) | |||||
| def asobj(self): | |||||
| return (self.type, self.mime_type, | |||||
| self.description.decode('utf-8'), self.width, self.height, | |||||
| self.depth, self.colors, | |||||
| ''.join(chr(x) for x in self.data[:self.data_length])) | |||||
| class FLAC__StreamMetadataData(Union): | |||||
| _fields_ = [ ('stream_info', FLAC__StreamMetadata_StreamInfo), | |||||
| ('vorbis_comment', FLAC__StreamMetadata_VorbisComment), | |||||
| ('cue_sheet', FLAC__StreamMetadata_CueSheet), | |||||
| ('picture', FLAC__StreamMetadata_Picture), | |||||
| ] | |||||
| class FLAC__StreamMetadata(Structure): | |||||
| _fields_ = [ ('type', c_int), | |||||
| ('is_last', c_int), | |||||
| ('length', c_uint), | |||||
| ('data', FLAC__StreamMetadataData), | |||||
| ] | |||||
| class FLAC__EntropyCodingMethod_PartitionedRiceContents(Structure): | |||||
| pass | |||||
| class FLAC__EntropyCodingMethod_PartitionedRice(Structure): | |||||
| _fields_ = [ ('order', c_uint), | |||||
| ('contents', | |||||
| POINTER(FLAC__EntropyCodingMethod_PartitionedRiceContents)), | |||||
| ] | |||||
| class FLAC__EntropyCodingMethod(Structure): | |||||
| _fields_ = [ ('type', c_int), | |||||
| ('partitioned_rice', FLAC__EntropyCodingMethod_PartitionedRice), | |||||
| ] | |||||
| class FLAC__Subframe_Constant(Structure): | |||||
| _fields_ = [ ('value', c_int32), ] | |||||
| class FLAC__Subframe_Verbatim(Structure): | |||||
| _fields_ = [ ('data', POINTER(c_int32)), ] | |||||
| class FLAC__Subframe_Fixed(Structure): | |||||
| _fields_ = [ ('entropy_coding_method', FLAC__EntropyCodingMethod), | |||||
| ('order', c_uint), | |||||
| ('warmup', c_int32 * FLAC__MAX_FIXED_ORDER), | |||||
| ('residual', POINTER(c_int32)), | |||||
| ] | |||||
| class FLAC__Subframe_LPC(Structure): | |||||
| _fields_ = [ ('entropy_coding_method', FLAC__EntropyCodingMethod), | |||||
| ('order', c_uint), | |||||
| ('qlp_coeff_precision', c_uint), | |||||
| ('quantization_level', c_int), | |||||
| ('qlp_coeff', c_int32 * FLAC__MAX_LPC_ORDER), | |||||
| ('warmup', c_int32 * FLAC__MAX_LPC_ORDER), | |||||
| ('residual', POINTER(c_int32)), | |||||
| ] | |||||
| class FLAC__SubframeUnion(Union): | |||||
| _fields_ = [ ('constant', FLAC__Subframe_Constant), | |||||
| ('fixed', FLAC__Subframe_Fixed), | |||||
| ('lpc', FLAC__Subframe_LPC), | |||||
| ('verbatim', FLAC__Subframe_Verbatim), | |||||
| ] | |||||
| class FLAC__Subframe(Structure): | |||||
| _fields_ = [ ('type', c_int), | |||||
| ('data', FLAC__SubframeUnion), | |||||
| ('wasted_bits', c_uint), | |||||
| ] | |||||
| class number_union(Union): | |||||
| _fields_ = [ ('frame_number', c_uint32), | |||||
| ('sample_number', c_uint64), | |||||
| ] | |||||
| class FLAC__FrameHeader(Structure): | |||||
| _fields_ = [ ('blocksize', c_uint), | |||||
| ('sample_rate', c_uint), | |||||
| ('channels', c_uint), | |||||
| ('channel_assignment', c_int), | |||||
| ('bits_per_sample', c_uint), | |||||
| ('number_type', c_int), | |||||
| ('number', number_union), | |||||
| ('crc', c_uint8), | |||||
| ] | |||||
| class FLAC__FrameFooter(Structure): | |||||
| _fields_ = [ ('crc', c_uint16), ] | |||||
| class FLAC__Frame(Structure): | |||||
| _fields_ = [ ('header', FLAC__FrameHeader), | |||||
| ('subframes', FLAC__Subframe * FLAC__MAX_CHANNELS), | |||||
| ('footer', FLAC__FrameFooter), | |||||
| ] | |||||
| class FLAC__StreamDecoder(Structure): | |||||
| pass | |||||
| # Types | |||||
| FLAC__StreamMetadata_p = POINTER(FLAC__StreamMetadata) | |||||
| FLAC__Frame_p = POINTER(FLAC__Frame) | |||||
| FLAC__StreamDecoder_p = POINTER(FLAC__StreamDecoder) | |||||
| # Function typedefs | |||||
| FLAC__StreamDecoderReadCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p, | |||||
| POINTER(c_ubyte), POINTER(c_size_t), c_void_p) | |||||
| FLAC__StreamDecoderSeekCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p, | |||||
| c_uint64, c_void_p) | |||||
| FLAC__StreamDecoderTellCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p, | |||||
| POINTER(c_uint64), c_void_p) | |||||
| FLAC__StreamDecoderLengthCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p, | |||||
| POINTER(c_uint64), c_void_p) | |||||
| FLAC__StreamDecoderEofCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p, | |||||
| c_void_p) | |||||
| FLAC__StreamDecoderWriteCallback = CFUNCTYPE(c_int, FLAC__StreamDecoder_p, | |||||
| FLAC__Frame_p, POINTER(POINTER(c_int32)), c_void_p) | |||||
| FLAC__StreamDecoderMetadataCallback = CFUNCTYPE(None, FLAC__StreamDecoder_p, | |||||
| FLAC__StreamMetadata_p, c_void_p) | |||||
| FLAC__StreamDecoderErrorCallback = CFUNCTYPE(None, FLAC__StreamDecoder_p, | |||||
| c_int, c_void_p) | |||||
| funs = { | |||||
| 'FLAC__stream_decoder_new': (FLAC__StreamDecoder_p, []), | |||||
| 'FLAC__stream_decoder_delete': (None, [FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_set_md5_checking': (c_int, | |||||
| [ FLAC__StreamDecoder_p, c_int, ]), | |||||
| 'FLAC__stream_decoder_set_metadata_respond': (c_int, | |||||
| [ FLAC__StreamDecoder_p, c_int, ]), | |||||
| 'FLAC__stream_decoder_set_metadata_respond_all': (c_int, | |||||
| [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_get_state': (c_int, | |||||
| [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_get_total_samples': (c_uint64, | |||||
| [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_get_channels': (c_uint, | |||||
| [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_get_channel_assignment': (c_int, | |||||
| [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_get_bits_per_sample': (c_uint, | |||||
| [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_get_sample_rate': (c_uint, | |||||
| [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_get_blocksize': (c_uint, | |||||
| [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_init_stream': (c_int, [ FLAC__StreamDecoder_p, | |||||
| FLAC__StreamDecoderReadCallback, FLAC__StreamDecoderSeekCallback, | |||||
| FLAC__StreamDecoderTellCallback, FLAC__StreamDecoderLengthCallback, | |||||
| FLAC__StreamDecoderEofCallback, FLAC__StreamDecoderWriteCallback, | |||||
| FLAC__StreamDecoderMetadataCallback, | |||||
| FLAC__StreamDecoderErrorCallback, ]), | |||||
| 'FLAC__stream_decoder_init_file': (c_int, [ FLAC__StreamDecoder_p, | |||||
| c_char_p, FLAC__StreamDecoderWriteCallback, | |||||
| FLAC__StreamDecoderMetadataCallback, | |||||
| FLAC__StreamDecoderErrorCallback, c_void_p, ]), | |||||
| 'FLAC__stream_decoder_finish': (c_int, [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_flush': (c_int, [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_reset': (c_int, [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_process_single': (c_int, | |||||
| [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_process_until_end_of_metadata': (c_int, | |||||
| [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_process_until_end_of_stream': (c_int, | |||||
| [ FLAC__StreamDecoder_p, ]), | |||||
| 'FLAC__stream_decoder_seek_absolute': (c_int, | |||||
| [ FLAC__StreamDecoder_p, c_uint64, ]), | |||||
| } | |||||
| for i in funs: | |||||
| f = getattr(flaclib, i) | |||||
| f.restype, f.argtypes = funs[i] | |||||
| def clientdatawrapper(fun): | |||||
| def newfun(*args): | |||||
| #print 'nf:', `args` | |||||
| return fun(*args[1:-1]) | |||||
| return newfun | |||||
| def getindex(ar, idx): | |||||
| for i in ar: | |||||
| if idx == i['number']: | |||||
| return i | |||||
| raise ValueError('index %d not present: %s' % (idx, `ar`)) | |||||
| def _checksum(n): | |||||
| ret = 0 | |||||
| while n > 0: | |||||
| n, m = divmod(n, 10) | |||||
| ret += m | |||||
| return ret | |||||
| def _cuetotrackinfo(cuesheet): | |||||
| '''XXX - do not use. This will not work because the cuesheet does | |||||
| not include data tracks!''' | |||||
| if not cuesheet['is_cd']: | |||||
| raise ValueError('cuesheet isn\'t for a cd') | |||||
| tracks = [] | |||||
| ta = cuesheet['tracks_array'] | |||||
| tot = 0 | |||||
| cksum = 0 | |||||
| for i in xrange(1, len(ta)): | |||||
| offstart = ta[i - 1]['offset'] | |||||
| offend = ta[i]['offset'] | |||||
| secs = offend / 44100 - offstart / 44100 | |||||
| #print ta[i - 1]['number'], secs, offstart / (2352 / 4), (offend - offstart) / (2352 / 4) | |||||
| tot += secs | |||||
| cksum += _checksum(offstart / 44100 + 2) | |||||
| #print tot | |||||
| tracks.append(divmod(tot, 60)) | |||||
| #print `tracks` | |||||
| return (long(cksum) % 0xff) << 24 | tot << 8 | (len(ta) - 1), 0 | |||||
| class FLACDec(object): | |||||
| '''Class to support flac decoding.''' | |||||
| channels = property(lambda x: x._channels) | |||||
| samplerate = property(lambda x: x._samplerate) | |||||
| bitspersample = property(lambda x: x._bitspersample) | |||||
| totalsamples = property(lambda x: x._totalsamples) | |||||
| bytespersample = property(lambda x: x._bytespersample) | |||||
| tags = property(lambda x: x._tags) | |||||
| vorbis = property(lambda x: x._vorbis) | |||||
| cuesheet = property(lambda x: x._cuesheet) | |||||
| pictures = property(lambda x: x._pictures) | |||||
| def __len__(self): | |||||
| return self._totalsamples | |||||
| def __init__(self, file): | |||||
| '''Pass in the file name. We currently do not support file objects.''' | |||||
| self.flacdec = None | |||||
| self._lasterror = None | |||||
| # We need to keep references to the callback functions | |||||
| # around so they won't be garbage collected. | |||||
| self.write_wrap = FLAC__StreamDecoderWriteCallback( | |||||
| clientdatawrapper(self._cb_write)) | |||||
| self.metadata_wrap = FLAC__StreamDecoderMetadataCallback( | |||||
| clientdatawrapper(self._cb_metadata)) | |||||
| self.error_wrap = FLAC__StreamDecoderErrorCallback( | |||||
| clientdatawrapper(self._cb_error)) | |||||
| self.flacdec = flaclib.FLAC__stream_decoder_new() | |||||
| if self.flacdec == 0: | |||||
| raise RuntimeError('allocating decoded') | |||||
| flaclib.FLAC__stream_decoder_set_md5_checking(self.flacdec, | |||||
| True) | |||||
| for i in (FLAC__METADATA_TYPE_VORBIS_COMMENT, | |||||
| FLAC__METADATA_TYPE_CUESHEET, FLAC__METADATA_TYPE_PICTURE): | |||||
| flaclib.FLAC__stream_decoder_set_metadata_respond( | |||||
| self.flacdec, i) | |||||
| status = flaclib.FLAC__stream_decoder_init_file(self.flacdec, | |||||
| file, self.write_wrap, self.metadata_wrap, | |||||
| self.error_wrap, None) | |||||
| if status != FLAC__STREAM_DECODER_INIT_STATUS_OK: | |||||
| raise ValueError( | |||||
| FLAC__StreamDecoderInitStatusString[status]) | |||||
| self._tags = {} | |||||
| self._vorbis = None | |||||
| self._cuesheet = None | |||||
| self._pictures = {} | |||||
| flaclib.FLAC__stream_decoder_process_until_end_of_metadata( | |||||
| self.flacdec) | |||||
| if self._lasterror is not None: | |||||
| raise ValueError( | |||||
| FLAC__StreamDecoderErrorStatusString[ | |||||
| self._lasterror]) | |||||
| self.curcnt = 0 | |||||
| self.cursamp = 0 | |||||
| if self.vorbis is None: | |||||
| self.vorbis = '', VorbisComments() | |||||
| def close(self): | |||||
| '''Finish decoding and close all the objects.''' | |||||
| if self.flacdec is None: | |||||
| return | |||||
| #print 'close called' | |||||
| if not flaclib.FLAC__stream_decoder_finish(self.flacdec): | |||||
| md5invalid = True | |||||
| else: | |||||
| md5invalid = False | |||||
| flaclib.FLAC__stream_decoder_delete(self.flacdec) | |||||
| self.flacdec = None | |||||
| self.write_wrap = None | |||||
| self.metadata_wrap = None | |||||
| self.error_wrap = None | |||||
| if md5invalid: | |||||
| pass | |||||
| #raise ValueError('invalid md5') | |||||
| def _cb_write(self, frame_p, buffer_pp): | |||||
| frame = frame_p[0] | |||||
| #print 'write:', `frame` | |||||
| #for i in xrange(frame.header.channels): | |||||
| # print '%d:' % i, `frame.subframes[i]` | |||||
| nchan = frame.header.channels | |||||
| #print 'sample number:', frame.header.number.sample_number | |||||
| self.cursamp = frame.header.number.sample_number | |||||
| self.curcnt = frame.header.blocksize | |||||
| outtype, fun = inter[frame.header.bits_per_sample] | |||||
| if outtype is None: | |||||
| outbuf = None | |||||
| else: | |||||
| outbuf = (outtype * (nchan * frame.header.blocksize))() | |||||
| # nchan, chanmap, chansamps, data, out | |||||
| assert nchan == 2 | |||||
| r = fun(nchan, (c_int * 2)(0, 1), frame.header.blocksize, | |||||
| buffer_pp, outbuf) | |||||
| if outtype is None: | |||||
| self.curdata = r | |||||
| else: | |||||
| self.curdata = array.array('h', outbuf) | |||||
| if is_little_endian: | |||||
| self.curdata.byteswap() | |||||
| return 0 | |||||
| def _cb_metadata(self, metadata_p): | |||||
| md = metadata_p[0] | |||||
| #print 'metadata:', `md` | |||||
| if md.type == FLAC__METADATA_TYPE_STREAMINFO: | |||||
| si = md.data.stream_info | |||||
| self._channels = si.channels | |||||
| self._samplerate = si.sample_rate | |||||
| self._bitspersample = si.bits_per_sample | |||||
| self._totalsamples = si.total_samples | |||||
| self._bytespersample = si.channels * \ | |||||
| si.bits_per_sample / 8 | |||||
| #print `si` | |||||
| elif md.type == FLAC__METADATA_TYPE_VORBIS_COMMENT: | |||||
| self._vorbis = md.data.vorbis_comment.asobj() | |||||
| self._tags = self.vorbis[1] | |||||
| #print 'vc:', `md.data.vorbis_comment` | |||||
| #print 'v:', `self.vorbis` | |||||
| elif md.type == FLAC__METADATA_TYPE_CUESHEET: | |||||
| self._cuesheet = md.data.cue_sheet.asobj() | |||||
| #print 'cs:', `md.data.cue_sheet` | |||||
| #print 'c:', `self.cuesheet` | |||||
| elif md.type == FLAC__METADATA_TYPE_PICTURE: | |||||
| pict = md.data.picture | |||||
| #print 'p:', `md.data.picture` | |||||
| #print 'po:', `pict.asobj()` | |||||
| self._pictures.setdefault(pictnamemap[pict.type], | |||||
| []).append(pict.asobj()) | |||||
| #print 'pd:', `self._pictures` | |||||
| else: | |||||
| print 'unknown metatype:', md.type | |||||
| def _cb_error(self, errstatus): | |||||
| #print 'error:', `errstatus` | |||||
| self._lasterror = errstatus | |||||
| def getstate(self): | |||||
| state = flaclib.FLAC__stream_decoder_get_state(self.flacdec) | |||||
| return state | |||||
| state = property(getstate) | |||||
| def goto(self, pos): | |||||
| '''Go to sample possition.''' | |||||
| if self.flacdec is None: | |||||
| raise ValueError('closed') | |||||
| pos = min(self.totalsamples - 1, pos) | |||||
| if flaclib.FLAC__stream_decoder_seek_absolute(self.flacdec, | |||||
| pos): | |||||
| return | |||||
| # the slow way | |||||
| state = self.state | |||||
| if state == FLAC__STREAM_DECODER_SEEK_ERROR: | |||||
| print 'WARNING: possibly invalid file!' | |||||
| if self.cursamp > pos or \ | |||||
| state == FLAC__STREAM_DECODER_SEEK_ERROR: | |||||
| if not flaclib.FLAC__stream_decoder_reset(self.flacdec): | |||||
| raise RuntimeError('unable to seek to beginin') | |||||
| flaclib.FLAC__stream_decoder_process_until_end_of_metadata(self.flacdec) | |||||
| flaclib.FLAC__stream_decoder_process_single( | |||||
| self.flacdec) | |||||
| read = pos - self.cursamp | |||||
| while read: | |||||
| tread = min(read, 512*1024) | |||||
| self.read(tread) | |||||
| read -= tread | |||||
| def read(self, nsamp=16*1024, oneblk=False): | |||||
| '''If oneblk is True, we will only process data once.''' | |||||
| if self.flacdec is None: | |||||
| raise ValueError('closed') | |||||
| r = [] | |||||
| nsamp = min(nsamp, self.totalsamples - self.cursamp) | |||||
| nchan = self.channels | |||||
| while nsamp: | |||||
| if self.curcnt == 0: | |||||
| flaclib.FLAC__stream_decoder_process_single( | |||||
| self.flacdec) | |||||
| continue | |||||
| cnt = min(nsamp, self.curcnt) | |||||
| sampcnt = cnt * nchan | |||||
| r.append(self.curdata[:sampcnt].tostring()) | |||||
| self.cursamp += cnt | |||||
| self.curcnt -= cnt | |||||
| self.curdata = self.curdata[sampcnt:] | |||||
| nsamp -= cnt | |||||
| if oneblk: | |||||
| break | |||||
| return ''.join(r) | |||||
| def doprofile(d): | |||||
| def readtoend(): | |||||
| while d.read(): | |||||
| pass | |||||
| import hotshot.stats | |||||
| prof = hotshot.Profile('decread.prof') | |||||
| prof.runcall(readtoend) | |||||
| prof.close() | |||||
| stats = hotshot.stats.load('decread.prof') | |||||
| stats.strip_dirs() | |||||
| stats.sort_stats('time', 'calls') | |||||
| stats.print_stats(20) | |||||
| if __name__ == '__main__': | |||||
| import sys | |||||
| if len(sys.argv) == 1: | |||||
| print 'Usage: %s <file>' % sys.argv[0] | |||||
| sys.exit(1) | |||||
| d = FLACDec(sys.argv[1]) | |||||
| print 'total samples:', d.totalsamples | |||||
| print 'channels:', d.channels | |||||
| print 'rate:', d.samplerate | |||||
| print 'bps:', d.bitspersample | |||||
| print 'pict types:', d.pictures.keys() | |||||
| print `d.read(10)` | |||||
| print 'going' | |||||
| d.goto(d.totalsamples - 1000*1000) | |||||
| print 'here' | |||||
| print `d.read(10)` | |||||
| doprofile(d) | |||||
| @@ -1,13 +0,0 @@ | |||||
| #include <sys/types.h> | |||||
| #define INTERLEAVEFUN(nbits) void \ | |||||
| interleave ## nbits (int nchan, int chanmap[], int chansamps, int32_t *data[], int ## nbits ## _t *out) \ | |||||
| { \ | |||||
| int i; \ | |||||
| \ | |||||
| for (i = 0; i < chansamps * nchan; i++) { \ | |||||
| out[i] = (int ## nbits ## _t)data[chanmap[i % nchan]][i / nchan]; \ | |||||
| } \ | |||||
| } | |||||
| INTERLEAVEFUN(16) | |||||
| @@ -1,100 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2009 John-Mark Gurney <jmg@funkthat.com> | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| import FileDIDL | |||||
| import xml.dom.minidom | |||||
| from DIDLLite import StorageFolder, Item, Resource, ResourceList | |||||
| from FSStorage import FSObject, registerklassfun | |||||
| def getElementText(elm, strip=True): | |||||
| s = ''.join(x.nodeValue for x in elm.childNodes) | |||||
| if strip: | |||||
| s = s.strip() | |||||
| return s | |||||
| class ItemObject(FSObject, Item): | |||||
| def __init__(self, *args, **kwargs): | |||||
| path = kwargs.pop('path') | |||||
| dom = kwargs.pop('dom') | |||||
| FSObject.__init__(self, path) | |||||
| Item.__init__(self, *args, **kwargs) | |||||
| self.checkUpdate(dom=dom) | |||||
| _attrconv = [ | |||||
| ('size', 'size', int), | |||||
| ('bitrate', 'bitrate', int), | |||||
| ('sampleFrequency', 'sampleFrequency', int), | |||||
| ('nrAudioChannels', 'nrAudioChannels', int), | |||||
| ] | |||||
| @staticmethod | |||||
| def _addattrib(res, attr, dom, element, fun=lambda x: x): | |||||
| data = dom.getElementsByTagName(element) | |||||
| if data: | |||||
| setattr(res, attr, fun(getElementText(data[0]))) | |||||
| def doUpdate(self, dom=None): | |||||
| if dom is None: | |||||
| dom = xml.dom.minidom.parse(open(self.FSpath)) | |||||
| obj = dom.getElementsByTagName('Object')[0] | |||||
| self.res = ResourceList() | |||||
| for i in obj.getElementsByTagName('res'): | |||||
| mtel = i.getElementsByTagName('mimetype') | |||||
| rtpel = i.getElementsByTagName('rtppayloadtype') | |||||
| if mtel: | |||||
| mt = getElementText(mtel[0]) | |||||
| pi = 'http-get:*:%s:*' % mt | |||||
| elif rtpel: | |||||
| pt = getElementText(rtpel[0]) | |||||
| pi = 'rtsp-rtp-udp:*:%s:*' % pt | |||||
| else: | |||||
| print('missing mimetype or rtppayloadtype element, skipping...') | |||||
| continue | |||||
| url = getElementText(i.getElementsByTagName('url')[0]) | |||||
| r = Resource(url, pi) | |||||
| for j in self._attrconv: | |||||
| self._addattrib(r, j[0], i, j[1], *j[2:]) | |||||
| self.res.append(r) | |||||
| super(ItemObject, self).doUpdate() | |||||
| def canHandle(fobj): | |||||
| # XXX - create schema and validate | |||||
| dom = xml.dom.minidom.parse(fobj) | |||||
| #print 'ch:', `dom` | |||||
| obj = dom.getElementsByTagName('Object')[0] | |||||
| mtel = obj.getElementsByTagName('mimetype') | |||||
| rtpel = obj.getElementsByTagName('rtpmimetype') | |||||
| if mtel: | |||||
| mt = getElementText(mtel[0]) | |||||
| elif rtpel: | |||||
| mt = getElementText(rtpel[0]) | |||||
| else: | |||||
| raise ValueError('failed to find mimetpe or rtppayloadtype') | |||||
| #print 'ch:', `obj`, `mt` | |||||
| return dom, mt | |||||
| def detectitem(path, fobj): | |||||
| #print 'di:', `path`, `fobj` | |||||
| try: | |||||
| dom, mt = canHandle(fobj) | |||||
| except: | |||||
| #import traceback | |||||
| #traceback.print_exc() | |||||
| return None, None | |||||
| klass, mt = FileDIDL.buildClassMT(ItemObject, path, mimetype=mt) | |||||
| #print 'match:', `klass`, `mt` | |||||
| return klass, { 'path': path, 'dom': dom } | |||||
| registerklassfun(detectitem) | |||||
| @@ -1,69 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # | |||||
| # Copyright 2006 John-Mark Gurney. | |||||
| # All rights reserved. | |||||
| # | |||||
| # Redistribution and use in source and binary forms, with or without | |||||
| # modification, are permitted provided that the following conditions | |||||
| # are met: | |||||
| # 1. Redistributions of source code must retain the above copyright | |||||
| # notice, this list of conditions and the following disclaimer. | |||||
| # 2. Redistributions in binary form must reproduce the above copyright | |||||
| # notice, this list of conditions and the following disclaimer in the | |||||
| # documentation and/or other materials provided with the distribution. | |||||
| # | |||||
| # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
| # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
| # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
| # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
| # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
| # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
| # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
| # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
| # SUCH DAMAGE. | |||||
| # | |||||
| # $Id$ | |||||
| # | |||||
| __all__ = [ 'decode_title', 'decode_description' ] | |||||
| try: | |||||
| from ctypes import * | |||||
| import ctypes.util | |||||
| import os.path | |||||
| path = os.path.dirname(__file__) | |||||
| if not path: | |||||
| path = '.' | |||||
| hd = ctypes.cdll.LoadLibrary(os.path.join(path, 'libhuffdecode.so.1')) | |||||
| hd.decodetitle.restype = c_int | |||||
| hd.decodetitle.argtypes = [ c_char_p, c_char_p, c_int ] | |||||
| hd.decodedescription.restype = c_int | |||||
| hd.decodedescription.argtypes = [ c_char_p, c_char_p, c_int ] | |||||
| def docall(fun, s): | |||||
| buflen = 256 | |||||
| while True: | |||||
| buf = ctypes.create_string_buffer(buflen) | |||||
| cnt = fun(s, buf, buflen) | |||||
| if cnt < buflen: | |||||
| break | |||||
| buflen *= 2 | |||||
| return buf.value.decode('iso8859-1') | |||||
| decode_title = lambda x: docall(hd.decodetitle, x) | |||||
| decode_description = lambda x: docall(hd.decodedescription, x) | |||||
| except ImportError: | |||||
| def foo(*args): | |||||
| raise NotImplementedError('Failed to import ctypes') | |||||
| decode_title = decode_description = foo | |||||
| except OSError: | |||||
| def foo(*args): | |||||
| raise NotImplementedError('Failed to find library huffdecode') | |||||
| decode_title = decode_description = foo | |||||
| @@ -1,113 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # | |||||
| # Copyright 2006-2007 John-Mark Gurney. | |||||
| # All rights reserved. | |||||
| # | |||||
| # Redistribution and use in source and binary forms, with or without | |||||
| # modification, are permitted provided that the following conditions | |||||
| # are met: | |||||
| # 1. Redistributions of source code must retain the above copyright | |||||
| # notice, this list of conditions and the following disclaimer. | |||||
| # 2. Redistributions in binary form must reproduce the above copyright | |||||
| # notice, this list of conditions and the following disclaimer in the | |||||
| # documentation and/or other materials provided with the distribution. | |||||
| # | |||||
| # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
| # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
| # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
| # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
| # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
| # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
| # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
| # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
| # SUCH DAMAGE. | |||||
| # | |||||
| # $Id$ | |||||
| # | |||||
| import sys | |||||
| sys.path.append('/Users/jgurney/p4/bktrau/info') | |||||
| import itertools | |||||
| import mpegts | |||||
| import struct | |||||
| import sys | |||||
| def usage(): | |||||
| print >>sys.stderr, 'Usage: %s <file> <pmtpid> <pid> ...' % sys.argv[0] | |||||
| sys.exit(1) | |||||
| def genpats(pmt, prognum): | |||||
| BASEPAT = map(None, "\x47\x40\x00\x10\x00\x00\xb0\x0d\x00\x00\xc1\x00\x00\x00\x00\xe0\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") | |||||
| patidx = 4 + 1 # TS header + pointer table | |||||
| BASEPAT[patidx + 8] = chr(prognum >> 8) | |||||
| BASEPAT[patidx + 9] = chr(prognum & 0xff) | |||||
| BASEPAT[patidx + 10] = chr(0xe0 | ((pmt >> 8) & 0x1f)) | |||||
| BASEPAT[patidx + 11] = chr(pmt & 0xff) | |||||
| newcrc = mpegts.psip_calc_crc32(''.join(BASEPAT[patidx:patidx + 12])) | |||||
| newcrc = map(lambda x, crc = newcrc: chr((crc >> (8 * (3 - x))) & 0xff), range(4)) | |||||
| BASEPAT[patidx + 12:patidx + 16] = newcrc | |||||
| assert len(BASEPAT) == mpegts.TSPKTLEN | |||||
| ret = [] | |||||
| # Generate continuity_counter | |||||
| old = ord(BASEPAT[3]) & 0xf0 | |||||
| for i in range(16): # continuity | |||||
| BASEPAT[3] = chr(old | i) | |||||
| ret.append(''.join(BASEPAT)) | |||||
| return ret | |||||
| def producets(inp, pmtpid, *pids): | |||||
| #print `inp`, `pmtpid`, `pids` | |||||
| # XXX - check if all pids are ints? in range? | |||||
| pids = sets.Set(pids) | |||||
| stream = mpegts.TSPStream(inp) | |||||
| didpmt = False | |||||
| for i in stream: | |||||
| frst = ord(i[1]) | |||||
| # Get first and error bits for testing. | |||||
| pid = (frst & 0x1f) << 8 | ord(i[2]) | |||||
| if frst & 0x80: | |||||
| continue | |||||
| elif pid == 0 and didpmt: | |||||
| yield pats.next() | |||||
| elif pid == pmtpid and frst & 0x40: | |||||
| if not didpmt: | |||||
| startpmt = 4 | |||||
| if ((ord(i[3]) >> 4) & 0x3) == 0x3: | |||||
| # Has adaptation field | |||||
| startpmt += ord(i[startpmt]) + 1 | |||||
| startpmt += ord(i[startpmt]) + 1 | |||||
| assert i[startpmt] == '\x02', (startpmt, i[0:10]) | |||||
| pats = itertools.cycle(genpats(pmtpid, struct.unpack('>H', i[startpmt + 3:startpmt + 5])[0])) | |||||
| yield pats.next() | |||||
| didpmt = True | |||||
| # XXX - we probably want to rewrite the PMT to only | |||||
| # include the pids we are sending. | |||||
| yield i | |||||
| elif pid in pids and didpmt: | |||||
| yield i | |||||
| def main(): | |||||
| if len(sys.argv) < 3: | |||||
| usage() | |||||
| pmtpid = int(sys.argv[2]) | |||||
| pids = map(int, sys.argv[3:]) | |||||
| inp = open(sys.argv[1]) | |||||
| out = sys.stdout | |||||
| producer = producets(inp, pmtpid, *pids) | |||||
| filter(lambda x: out.write(x), producer) | |||||
| if __name__ == '__main__': | |||||
| main() | |||||
| @@ -1,362 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2006-2008 John-Mark Gurney <jmg@funkthat.com> | |||||
| '''MPEG-TS Handling''' | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| tsselpypath = 'mpegts/tssel.py' | |||||
| default_audio_lang = 'eng' | |||||
| import array | |||||
| import io | |||||
| import itertools | |||||
| import os | |||||
| import struct | |||||
| import sys | |||||
| mpegtspath = 'mpegts' | |||||
| if mpegtspath not in sys.path: | |||||
| sys.path.append(mpegtspath) | |||||
| import mpegts | |||||
| import tssel | |||||
| from DIDLLite import StorageFolder, VideoItem, Resource | |||||
| from FSStorage import FSObject, registerklassfun | |||||
| from twisted.python import log, threadable | |||||
| from twisted.spread import pb | |||||
| from twisted.internet import abstract, process, protocol, reactor | |||||
| from twisted.web import error, http, resource, server | |||||
| class _LimitedFile(io.FileIO): | |||||
| def __init__(self, *args, **kwargs): | |||||
| self.__size = kwargs.pop('size') | |||||
| file.__init__(self, *args, **kwargs) | |||||
| def remain(self): | |||||
| pos = self.tell() | |||||
| if pos > self.__size: | |||||
| return 0 | |||||
| return self.__size - pos | |||||
| def read(self, size=-1): | |||||
| if size < 0: | |||||
| return file.read(self, self.remain()) | |||||
| return file.read(self, min(size, self.remain())) | |||||
| def _gennameindexes(chan): | |||||
| d = {} | |||||
| for i in chan: | |||||
| t = '%s %s.%s' % (i['name'], i['major'], i['minor']) | |||||
| d[t] = i | |||||
| return d | |||||
| class MPEGTSTransfer(pb.Viewable): | |||||
| def __init__(self, iterable, request): | |||||
| self.iter = iter(iterable) | |||||
| self.request = request | |||||
| request.registerProducer(self, 0) | |||||
| def resumeProducing(self): | |||||
| if not self.request: | |||||
| return | |||||
| # get data and write to request. | |||||
| try: | |||||
| data = next(self.iter) | |||||
| if 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) | |||||
| except StopIteration: | |||||
| if self.request: | |||||
| self.request.unregisterProducer() | |||||
| self.request.finish() | |||||
| self.request = None | |||||
| def pauseProducing(self): | |||||
| pass | |||||
| def stopProducing(self): | |||||
| # close zipfile | |||||
| self.request = None | |||||
| # Remotely relay producer interface. | |||||
| def view_resumeProducing(self, issuer): | |||||
| self.resumeProducing() | |||||
| def view_pauseProducing(self, issuer): | |||||
| self.pauseProducing() | |||||
| def view_stopProducing(self, issuer): | |||||
| self.stopProducing() | |||||
| synchronized = ['resumeProducing', 'stopProducing'] | |||||
| threadable.synchronize(MPEGTSTransfer) | |||||
| class DynamTSTransfer(pb.Viewable): | |||||
| def __init__(self, path, pmt, *pids): | |||||
| self.path = path | |||||
| #log.msg("DynamTSTransfer: pmt: %s, pids: %s" % (pmt, pids)) | |||||
| self.pmt = pmt | |||||
| self.pids = pids | |||||
| self.didpat = False | |||||
| def resumeProducing(self): | |||||
| if not self.request: | |||||
| return | |||||
| repcnt = 0 | |||||
| data = self.fp.read(min(abstract.FileDescriptor.bufferSize, | |||||
| self.size - self.written) // 188 * 188) | |||||
| dataarray = array.array('B', data) | |||||
| for i in range(0, len(data), 188): | |||||
| if data[i] != 'G': | |||||
| print('bad sync') | |||||
| continue | |||||
| frst = dataarray[i + 1] | |||||
| pid = (frst & 0x1f) << 8 | dataarray[i + 2] | |||||
| if not frst & 0x40: | |||||
| continue | |||||
| elif not self.didpat and pid == 0: | |||||
| startpmt = i + 4 | |||||
| if ((dataarray[i + 3] >> 4) & 0x3) == 0x3: | |||||
| # Adaptation | |||||
| startpmt += dataarray[startpmt] + 1 | |||||
| startpmt += dataarray[startpmt] + 1 | |||||
| assert data[startpmt] =='\x00', (startpmt, | |||||
| data[i:startpmt + 4]) | |||||
| arraysize = ((dataarray[startpmt + 1] & | |||||
| 0xf) << 8) | dataarray[startpmt + 2] | |||||
| startpmt += 3 | |||||
| arraysize -= 4 # CRC | |||||
| # Remaining fields before array | |||||
| startpmt += 5 | |||||
| arraysize -= 5 | |||||
| for startpmt in range(startpmt, | |||||
| min(i + 188 - 3, startpmt + arraysize), 4): | |||||
| prognum, ppid = struct.unpack('>2H', | |||||
| data[startpmt:startpmt + 4]) | |||||
| ppid = ppid & 0x1fff | |||||
| if ppid == self.pmt: | |||||
| break | |||||
| else: | |||||
| raise KeyError('unable to find pmt(%d) in pkt: %s' % (pmt, repr(data[i:i + 188]))) | |||||
| self.pats = itertools.cycle(tssel.genpats( | |||||
| self.pmt, prognum)) | |||||
| self.didpat = True | |||||
| if pid == 0 and self.didpat: | |||||
| assert data[i + 4] =='\x00' and \ | |||||
| data[i + 5] == '\x00', 'error: %s' % repr(data[i:i + 10]) | |||||
| repcnt += 1 | |||||
| pn = next(self.pats) | |||||
| data = data[:i] + pn + data[i + | |||||
| 188:] | |||||
| if repcnt > 1: | |||||
| print('repcnt:', repcnt, 'len(data):', len(data)) | |||||
| if data: | |||||
| self.written += len(data) | |||||
| self.request.write(data) | |||||
| if self.request and self.fp.tell() == self.size: | |||||
| self.request.unregisterProducer() | |||||
| self.request.finish() | |||||
| self.request = None | |||||
| def pauseProducing(self): | |||||
| pass | |||||
| def stopProducing(self): | |||||
| self.fp.close() | |||||
| self.request = None | |||||
| def render(self, request): | |||||
| path = self.path | |||||
| pmt = self.pmt | |||||
| pids = self.pids | |||||
| self.request = request | |||||
| fsize = size = os.path.getsize(path) | |||||
| request.setHeader('accept-ranges','bytes') | |||||
| request.setHeader('content-type', 'video/mpeg') | |||||
| try: | |||||
| self.fp = open(path) | |||||
| except IOError as e: | |||||
| import errno | |||||
| if e[0] == errno.EACCESS: | |||||
| return error.ForbiddenResource().render(request) | |||||
| else: | |||||
| raise | |||||
| if request.setLastModified(os.path.getmtime(path)) is http.CACHED: | |||||
| return '' | |||||
| trans = True | |||||
| # Commented out because it's totally broken. --jknight 11/29/04 | |||||
| # XXX - fixed? jmg 2/17/06 | |||||
| range = request.getHeader('range') | |||||
| tsize = size | |||||
| if range is not None: | |||||
| # This is a request for partial data... | |||||
| bytesrange = range.split('=') | |||||
| assert bytesrange[0] == 'bytes', \ | |||||
| "Syntactically invalid http range header!" | |||||
| start, end = bytesrange[1].split('-', 1) | |||||
| if start: | |||||
| start = int(start) | |||||
| self.fp.seek(start) | |||||
| if end and int(end) < size: | |||||
| end = int(end) | |||||
| else: | |||||
| end = size - 1 | |||||
| else: | |||||
| lastbytes = int(end) | |||||
| if size < lastbytes: | |||||
| lastbytes = size | |||||
| start = size - lastbytes | |||||
| self.fp.seek(start) | |||||
| fsize = lastbytes | |||||
| end = size - 1 | |||||
| start = start // 188 * 188 | |||||
| self.fp.seek(start) | |||||
| size = (end + 1) // 188 * 188 | |||||
| fsize = end - int(start) + 1 | |||||
| # start is the byte offset to begin, and end is the | |||||
| # byte offset to end.. fsize is size to send, tsize | |||||
| # is the real size of the file, and size is the byte | |||||
| # position to stop sending. | |||||
| if fsize <= 0: | |||||
| request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE | |||||
| ) | |||||
| fsize = tsize | |||||
| trans = False | |||||
| else: | |||||
| request.setResponseCode(http.PARTIAL_CONTENT) | |||||
| request.setHeader('content-range',"bytes %s-%s/%s " % ( | |||||
| str(start), str(end), str(tsize))) | |||||
| request.setHeader('content-length', str(fsize)) | |||||
| if request.method == 'HEAD' or trans is False: | |||||
| request.method = 'HEAD' | |||||
| return '' | |||||
| self.size = tsize | |||||
| self.written = 0 | |||||
| request.registerProducer(self, 0) | |||||
| return server.NOT_DONE_YET | |||||
| class MPEGTSResource(resource.Resource): | |||||
| isLeaf = True | |||||
| def __init__(self, *args): | |||||
| resource.Resource.__init__(self) | |||||
| self.args = args | |||||
| def render(self, request): | |||||
| request.setHeader('content-type', 'video/mpeg') | |||||
| # return data | |||||
| return DynamTSTransfer(*self.args).render(request) | |||||
| class MPEGTS(FSObject, VideoItem): | |||||
| def __init__(self, *args, **kwargs): | |||||
| self.path = path = kwargs['path'] | |||||
| del kwargs['path'] | |||||
| self.tvct = tvct = kwargs['tvct'] | |||||
| del kwargs['tvct'] | |||||
| #log.msg('tvct w/ keys:', tvct, tvct.keys()) | |||||
| kwargs['content'] = MPEGTSResource(path, tvct['PMTpid'], | |||||
| *sum(mpegts.getaudiovideopids(tvct['PMT']), [])) | |||||
| VideoItem.__init__(self, *args, **kwargs) | |||||
| FSObject.__init__(self, path) | |||||
| self.url = '%s/%s' % (self.cd.urlbase, self.id) | |||||
| self.res = Resource(self.url, 'http-get:*:video/mpeg:*') | |||||
| def doUpdate(self): | |||||
| pass | |||||
| class MultiMPEGTS(FSObject, StorageFolder): | |||||
| def __init__(self, *args, **kwargs): | |||||
| path = kwargs['path'] | |||||
| del kwargs['path'] | |||||
| StorageFolder.__init__(self, *args, **kwargs) | |||||
| FSObject.__init__(self, path) | |||||
| def genChildren(self): | |||||
| f = mpegts.TSPStream(_LimitedFile(self.FSpath, | |||||
| size=2*1024*1024)) | |||||
| self.tvct = mpegts.GetTVCT(f) | |||||
| #log.msg('MultiMPEGTS genChildren: tvct: %s' % self.tvct) | |||||
| return _gennameindexes(self.tvct['channels']) | |||||
| def createObject(self, i, arg): | |||||
| if arg['prog_num'] == 0: | |||||
| log.msg('bogus:', arg) | |||||
| return None, None, (), {} | |||||
| #log.msg('real tvct:', arg, toindex.keys(), self.tvct) | |||||
| return MPEGTS, i, (), { 'path': self.FSpath, 'tvct': arg } | |||||
| def sort(self): | |||||
| return super(MultiMPEGTS, self).sort(lambda x, y: | |||||
| cmp((x.tvct['major'], x.tvct['minor']), | |||||
| (y.tvct['major'], y.tvct['minor']))) | |||||
| def findtsstream(fp, pktsz=188): | |||||
| d = fp.read(200*pktsz) | |||||
| i = 5 | |||||
| pos = 0 | |||||
| while i and pos < len(d) and pos != -1: | |||||
| if d[pos] == 'G': | |||||
| i -= 1 | |||||
| pos += pktsz | |||||
| else: | |||||
| i = 5 | |||||
| pos = d.find('G', pos + 1) | |||||
| if i or pos == -1: | |||||
| return False | |||||
| return True | |||||
| def detectmpegts(path, fobj): | |||||
| if not findtsstream(fobj): | |||||
| return None, None | |||||
| f = mpegts.TSPStream(_LimitedFile(path, size= 2*1024*1024)) | |||||
| tvct = mpegts.GetTVCT(f) | |||||
| if len(tvct['channels']) == 1: | |||||
| #return None, None | |||||
| # We might reenable this once we have pid filtering working | |||||
| # fast enough. | |||||
| return MPEGTS, { 'path': path, 'tvct': tvct['channels'][0] } | |||||
| elif len(tvct['channels']) > 1: | |||||
| #log.msg('MultiMPEGTS: path: %s' % path) | |||||
| return MultiMPEGTS, { 'path': path } | |||||
| return None, None | |||||
| registerklassfun(detectmpegts) | |||||
| @@ -1,34 +0,0 @@ | |||||
| --- Twisted-2.1.0/twisted/internet/tcp.py Sat Oct 8 21:10:44 2005 | |||||
| +++ /usr/local/lib/python2.4/site-packages/twisted/internet/tcp.py Tue Sep 5 23:33:41 2006 | |||||
| @@ -43,6 +43,7 @@ | |||||
| from errno import WSAEINPROGRESS as EINPROGRESS | |||||
| from errno import WSAEALREADY as EALREADY | |||||
| from errno import WSAECONNRESET as ECONNRESET | |||||
| + from errno import WSAECONNRESET as ECONNABORTED | |||||
| from errno import WSAEISCONN as EISCONN | |||||
| from errno import WSAENOTCONN as ENOTCONN | |||||
| from errno import WSAEINTR as EINTR | |||||
| @@ -55,6 +56,7 @@ | |||||
| from errno import EINPROGRESS | |||||
| from errno import EALREADY | |||||
| from errno import ECONNRESET | |||||
| + from errno import ECONNABORTED | |||||
| from errno import EISCONN | |||||
| from errno import ENOTCONN | |||||
| from errno import EINTR | |||||
| @@ -752,10 +754,13 @@ | |||||
| try: | |||||
| skt, addr = self.socket.accept() | |||||
| except socket.error, e: | |||||
| - if e.args[0] in (EWOULDBLOCK, EAGAIN): | |||||
| + errno = e.args[0] | |||||
| + if not isinstance(errno, type(EAGAIN)): | |||||
| + errno = errno[0] | |||||
| + if errno in (EWOULDBLOCK, EAGAIN, ECONNABORTED): | |||||
| self.numberAccepts = i | |||||
| break | |||||
| - elif e.args[0] == EPERM: | |||||
| + elif errno == EPERM: | |||||
| continue | |||||
| raise | |||||
| @@ -1,98 +0,0 @@ | |||||
| --- TwistedWeb-0.5.0/twisted/web/static.py Sun Jan 2 15:33:41 2005 | |||||
| +++ /usr/local/lib/python2.4/site-packages/twisted/web/static.py Fri Feb 17 23:55:04 2006 | |||||
| @@ -306,7 +306,7 @@ | |||||
| #for content-length | |||||
| fsize = size = self.getFileSize() | |||||
| -# request.setHeader('accept-ranges','bytes') | |||||
| + request.setHeader('accept-ranges','bytes') | |||||
| if self.type: | |||||
| request.setHeader('content-type', self.type) | |||||
| @@ -325,39 +325,59 @@ | |||||
| if request.setLastModified(self.getmtime()) is http.CACHED: | |||||
| return '' | |||||
| + trans = True | |||||
| # Commented out because it's totally broken. --jknight 11/29/04 | |||||
| -# try: | |||||
| -# range = request.getHeader('range') | |||||
| -# | |||||
| -# if range is not None: | |||||
| -# # This is a request for partial data... | |||||
| -# bytesrange = string.split(range, '=') | |||||
| -# assert bytesrange[0] == 'bytes',\ | |||||
| -# "Syntactically invalid http range header!" | |||||
| -# start, end = string.split(bytesrange[1],'-') | |||||
| -# if start: | |||||
| -# f.seek(int(start)) | |||||
| -# if end: | |||||
| -# end = int(end) | |||||
| -# size = end | |||||
| -# else: | |||||
| -# end = size | |||||
| -# request.setResponseCode(http.PARTIAL_CONTENT) | |||||
| -# request.setHeader('content-range',"bytes %s-%s/%s " % ( | |||||
| -# str(start), str(end), str(size))) | |||||
| -# #content-length should be the actual size of the stuff we're | |||||
| -# #sending, not the full size of the on-server entity. | |||||
| -# fsize = end - int(start) | |||||
| -# | |||||
| -# request.setHeader('content-length', str(fsize)) | |||||
| -# except: | |||||
| -# traceback.print_exc(file=log.logfile) | |||||
| +# XXX - fixed? jmg 2/17/06 | |||||
| + try: | |||||
| + range = request.getHeader('range') | |||||
| + | |||||
| + tsize = size | |||||
| + if range is not None: | |||||
| + # This is a request for partial data... | |||||
| + bytesrange = string.split(range, '=') | |||||
| + assert bytesrange[0] == 'bytes',\ | |||||
| + "Syntactically invalid http range header!" | |||||
| + start, end = string.split(bytesrange[1],'-', 1) | |||||
| + if start: | |||||
| + f.seek(int(start)) | |||||
| + if end: | |||||
| + end = int(end) | |||||
| + else: | |||||
| + end = size - 1 | |||||
| + else: | |||||
| + lastbytes = int(end) | |||||
| + if size < lastbytes: | |||||
| + lastbytes = size | |||||
| + start = size - lastbytes | |||||
| + f.seek(start) | |||||
| + fsize = lastbytes | |||||
| + end = size - 1 | |||||
| + size = end + 1 | |||||
| + fsize = end - int(start) + 1 | |||||
| + # start is the byte offset to begin, and end is the byte offset | |||||
| + # to end.. fsize is size to send, tsize is the real size of | |||||
| + # the file, and size is the byte position to stop sending. | |||||
| + | |||||
| + if fsize <= 0: | |||||
| + request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE) | |||||
| + fsize = tsize | |||||
| + trans = False | |||||
| + else: | |||||
| + request.setResponseCode(http.PARTIAL_CONTENT) | |||||
| + request.setHeader('content-range',"bytes %s-%s/%s " % ( | |||||
| + str(start), str(end), str(tsize))) | |||||
| + except: | |||||
| + traceback.print_exc(file=log.logfile) | |||||
| request.setHeader('content-length', str(fsize)) | |||||
| - if request.method == 'HEAD': | |||||
| + if request.method == 'HEAD' or trans == False: | |||||
| + # pretend we're a HEAD request, so content-length | |||||
| + # won't be overwritten. | |||||
| + request.method = 'HEAD' | |||||
| return '' | |||||
| # return data | |||||
| + # size is the byte position to stop sending, not how many bytes to send | |||||
| FileTransfer(f, size, request) | |||||
| # and make sure the connection doesn't get closed | |||||
| return server.NOT_DONE_YET | |||||
| @@ -1,52 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # | |||||
| # $Id$ | |||||
| # | |||||
| from twisted.internet import reactor | |||||
| from twisted.application import service | |||||
| from twisted.python import log, usage | |||||
| import configparser | |||||
| import pymeds | |||||
| import os.path | |||||
| import sys | |||||
| #fix for wstools | |||||
| import collections | |||||
| collections.MutableMapping = collections.abc.MutableMapping | |||||
| defconfigfile = 'pymeds.ini' | |||||
| class ConfigFile(pymeds.Options): | |||||
| optParameters = [ [ 'config', 'c', defconfigfile, | |||||
| 'INI style config file', ], ] | |||||
| if __name__ == '__main__': | |||||
| config = ConfigFile() | |||||
| try: | |||||
| config.checkpath = False | |||||
| config.parseOptions() | |||||
| print(repr(config)) | |||||
| if os.path.exists(config['config']): | |||||
| print('foo') | |||||
| scp = configparser.SafeConfigParser() | |||||
| scp.read(config['config']) | |||||
| config.update(scp.items('pymeds')) | |||||
| # Double check config | |||||
| config.checkpath = True | |||||
| config.postOptions() | |||||
| elif config['config'] != defconfigfile: | |||||
| print('bar') | |||||
| raise usage.UsageError( | |||||
| 'config file %s does not exist' % config['config']) | |||||
| except usage.UsageError as errortext: | |||||
| print('%s: %s' % (sys.argv[0], errortext)) | |||||
| print('%s: Try --help for usage details.' % sys.argv[0]) | |||||
| sys.exit(1) | |||||
| log.startLogging(sys.stdout) | |||||
| ser = pymeds.makeService(config) | |||||
| ser.startService() | |||||
| reactor.addSystemEventTrigger('before', 'shutdown', | |||||
| service.IService(ser).stopService) | |||||
| reactor.run() | |||||
| @@ -1,220 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| # Copyright 2005, Tim Potter <tpot@samba.org> | |||||
| # Copyright 2006-2009 John-Mark Gurney <jmg@funkthat.com> | |||||
| __version__ = '$Change: 1740 $' | |||||
| # $Id: //depot/python/pymeds/main/pymeds.py#17 $ | |||||
| # make sure debugging is initalized first, other modules can be pulled in | |||||
| # before the "real" debug stuff is setup. (hmm I could make this a two | |||||
| # stage, where we simulate a namespace to either be thrown away when the | |||||
| # time comes, or merge into the correct one) | |||||
| import debug # my debugging module | |||||
| debug.doDebugging(False) # open up debugging port | |||||
| # Modules to import, maybe config file or something? | |||||
| def tryloadmodule(mod): | |||||
| try: | |||||
| return __import__(mod) | |||||
| except ImportError: | |||||
| import traceback | |||||
| traceback.print_exc() | |||||
| pass | |||||
| # ZipStorage w/ tar support should be last as it will gobble up empty files. | |||||
| # These should be sorted by how much work they do, the least work the earlier. | |||||
| # mpegtsmod can be really expensive. | |||||
| modules = [ | |||||
| 'audio', | |||||
| 'Clip', | |||||
| 'pyvr', | |||||
| 'audioraw', | |||||
| 'item', | |||||
| 'slinkemod', | |||||
| 'dvd', | |||||
| 'ZipStorage', | |||||
| 'mpegtsmod', | |||||
| ] | |||||
| modmap = {} | |||||
| for i in modules: | |||||
| modmap[i] = tryloadmodule(i) | |||||
| for i in modules: | |||||
| debug.insertnamespace(i, modmap[i]) | |||||
| # Check to see which ones didn't get loaded | |||||
| checkmodules = [ x for x in modmap if modmap[x] is None ] | |||||
| if checkmodules: | |||||
| checkmodules.sort() | |||||
| print('The following modules were not loaded:', ', '.join(checkmodules)) | |||||
| from FSStorage import FSDirectory | |||||
| import os | |||||
| import os.path | |||||
| import random | |||||
| import socket | |||||
| import string | |||||
| import urllib.parse | |||||
| from twisted.application import internet, service | |||||
| from twisted.python import usage | |||||
| def generateuuid(): | |||||
| return ''.join([ 'uuid:'] + [random.choice(string.ascii_letters) for x in range(20)]) | |||||
| class Options(usage.Options): | |||||
| checkpath = True | |||||
| optParameters = [ | |||||
| [ 'title', 't', 'My Media Server', 'Title of the server.', ], | |||||
| [ 'path', 'p', 'media', 'Root path of the media to be served.', ], | |||||
| ] | |||||
| def postOptions(self): | |||||
| p = self['path'] | |||||
| if self.checkpath and not os.path.isdir(p): | |||||
| raise usage.UsageError('path %s does not exist' % repr(p)) | |||||
| def parseArgs(self, *args): | |||||
| # XXX - twisted doesn't let you provide a message on what | |||||
| # arguments are required, so we will do our own work in here. | |||||
| if len(args) not in (1, 2): | |||||
| raise usage.UsageError('Arguments: addr [ port ]') | |||||
| self['addr'] = args[0] | |||||
| if len(args) == 1: | |||||
| port = random.randint(10000, 65000) | |||||
| else: | |||||
| port = int(args[1]) | |||||
| if port < 1024 or port > 65535: | |||||
| raise ValueError( | |||||
| 'port must be between 1024 and 65535') | |||||
| self['port'] = port | |||||
| def fixupmimetypes(): | |||||
| # Purely to ensure some sane mime-types. On MacOSX I need these. | |||||
| # XXX - There isn't any easier way to get to the mime-type dict | |||||
| # that I know of. | |||||
| from twisted.web import static | |||||
| medianode = static.File('pymediaserv') | |||||
| medianode.contentTypes.update( { | |||||
| # From: http://support.microsoft.com/kb/288102 | |||||
| '.asf': 'video/x-ms-asf', | |||||
| '.asx': 'video/x-ms-asf', | |||||
| '.wma': 'audio/x-ms-wma', | |||||
| '.wax': 'audio/x-ms-wax', | |||||
| '.wmv': 'video/x-ms-wmv', | |||||
| '.wvx': 'video/x-ms-wvx', | |||||
| '.wm': 'video/x-ms-wm', | |||||
| '.wmx': 'video/x-ms-wmx', | |||||
| # From: http://www.matroska.org/technical/specs/notes.html | |||||
| '.mkv': 'video/x-matroska', | |||||
| '.mka': 'audio/x-matroska', | |||||
| '.flv': 'video/x-flv', | |||||
| #'.ts': 'video/mp2t', | |||||
| '.ts': 'video/mpeg', # we may want this instead of mp2t | |||||
| '.m2t': 'video/mpeg', | |||||
| '.m2ts': 'video/mpeg', | |||||
| '.mp4': 'video/mp4', | |||||
| #'.mp4': 'video/mpeg', | |||||
| '.dat': 'video/mpeg', # VCD tracks | |||||
| '.ogm': 'application/ogg', | |||||
| '.vob': 'video/mpeg', | |||||
| #'.m4a': 'audio/mp4', # D-Link can't seem to play AAC files. | |||||
| }) | |||||
| def makeService(config): | |||||
| listenAddr = config['addr'] | |||||
| listenPort = config['port'] | |||||
| uuid = config.get('uuid', None) | |||||
| if uuid is None: | |||||
| uuid = generateuuid() | |||||
| urlbase = 'http://%s:%d/' % (listenAddr, listenPort) | |||||
| # Create SOAP server and content server | |||||
| from twisted.web import server, resource, static | |||||
| from ContentDirectory import ContentDirectoryServer | |||||
| from ConnectionManager import ConnectionManagerServer | |||||
| class WebServer(resource.Resource): | |||||
| def __init__(self): | |||||
| resource.Resource.__init__(self) | |||||
| class RootDevice(static.Data): | |||||
| def __init__(self): | |||||
| r = { | |||||
| 'hostname': socket.gethostname(), | |||||
| 'uuid': uuid, | |||||
| 'urlbase': urlbase, | |||||
| } | |||||
| d = open('root-device.xml').read() % r | |||||
| static.Data.__init__(self, bytes(d, 'ascii'), 'text/xml') | |||||
| root = WebServer() | |||||
| debug.insertnamespace('root', root) | |||||
| content = resource.Resource() | |||||
| # This sets up the root to be the media dir so we don't have to | |||||
| # enumerate the directory. | |||||
| cds = ContentDirectoryServer(config['title'], klass=FSDirectory, | |||||
| path=config['path'], urlbase=urllib.parse.urljoin(urlbase, 'content'), | |||||
| webbase=content) | |||||
| debug.insertnamespace('cds', cds) | |||||
| root.putChild(b'ContentDirectory', cds) | |||||
| cds = cds.control | |||||
| root.putChild(b'ConnectionManager', ConnectionManagerServer()) | |||||
| root.putChild(b'root-device.xml', RootDevice()) | |||||
| root.putChild(b'content', content) | |||||
| fixupmimetypes() | |||||
| site = server.Site(root) | |||||
| # Create SSDP server | |||||
| from SSDP import SSDPServer, SSDP_PORT | |||||
| s = SSDPServer() | |||||
| debug.insertnamespace('s', s) | |||||
| class PyMedS(service.MultiService): | |||||
| def startService(self): | |||||
| service.MultiService.startService(self) | |||||
| rdxml = urllib.parse.urljoin(urlbase, 'root-device.xml') | |||||
| s.register('%s::upnp:rootdevice' % uuid, | |||||
| 'upnp:rootdevice', rdxml) | |||||
| s.register(uuid, | |||||
| uuid, | |||||
| rdxml) | |||||
| s.register('%s::urn:schemas-upnp-org:device:MediaServer:1' % uuid, | |||||
| 'urn:schemas-upnp-org:device:MediaServer:1', rdxml) | |||||
| s.register('%s::urn:schemas-upnp-org:service:ConnectionManager:1' % uuid, | |||||
| 'urn:schemas-upnp-org:service:ConnectionManager:1', rdxml) | |||||
| s.register('%s::urn:schemas-upnp-org:service:ContentDirectory:1' % uuid, | |||||
| 'urn:schemas-upnp-org:service:ContentDirectory:1', rdxml) | |||||
| def stopService(self): | |||||
| # Some reason stopProtocol isn't called | |||||
| s.doStop() | |||||
| service.MultiService.stopService(self) | |||||
| import pickle | |||||
| pickle.dump(cds, open('test.pickle', 'wb'), -1) | |||||
| serv = PyMedS() | |||||
| internet.TCPServer(listenPort, site, interface=listenAddr).setServiceParent(serv) | |||||
| internet.MulticastServer(SSDP_PORT, s, | |||||
| listenMultiple=True).setServiceParent(serv) | |||||
| return serv | |||||
| @@ -1,176 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| # Copyright 2005, Tim Potter <tpot@samba.org> | |||||
| # Copyright 2006 John-Mark Gurney <jmg@funkthat.com> | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| # make sure debugging is initalized first, other modules can be pulled in | |||||
| # before the "real" debug stuff is setup. (hmm I could make this a two | |||||
| # stage, where we simulate a namespace to either be thrown away when the | |||||
| # time comes, or merge into the correct one) | |||||
| import debug # my debugging module | |||||
| debug.doDebugging(True) # open up debugging port | |||||
| # Modules to import, maybe config file or something? | |||||
| def tryloadmodule(mod): | |||||
| try: | |||||
| return __import__(mod) | |||||
| except ImportError: | |||||
| #import traceback | |||||
| #traceback.print_exc() | |||||
| pass | |||||
| # ZipStorage w/ tar support should be last as it will gobble up empty files. | |||||
| # These should be sorted by how much work they do, the least work the earlier. | |||||
| # mpegtsmod can be really expensive. | |||||
| modules = [ | |||||
| 'pyvr', | |||||
| 'dvd', | |||||
| 'ZipStorage', | |||||
| 'mpegtsmod', | |||||
| ] | |||||
| modmap = {} | |||||
| for i in modules: | |||||
| modmap[i] = tryloadmodule(i) | |||||
| for i in modules: | |||||
| debug.insertnamespace(i, modmap[i]) | |||||
| from DIDLLite import TextItem, AudioItem, VideoItem, ImageItem, Resource, StorageFolder | |||||
| from FSStorage import FSDirectory | |||||
| import os | |||||
| import os.path | |||||
| import random | |||||
| import socket | |||||
| import string | |||||
| import sys | |||||
| from twisted.python import log | |||||
| from twisted.internet import reactor | |||||
| def generateuuid(): | |||||
| if False: | |||||
| return 'uuid:asdflkjewoifjslkdfj' | |||||
| return ''.join([ 'uuid:'] + map(lambda x: random.choice(string.letters), xrange(20))) | |||||
| listenAddr = sys.argv[1] | |||||
| if len(sys.argv) > 2: | |||||
| listenPort = int(sys.argv[2]) | |||||
| if listenPort < 1024 or listenPort > 65535: | |||||
| raise ValueError, 'port out of range' | |||||
| else: | |||||
| listenPort = random.randint(10000, 65000) | |||||
| log.startLogging(sys.stdout) | |||||
| # Create SSDP server | |||||
| from SSDP import SSDPServer, SSDP_PORT, SSDP_ADDR | |||||
| s = SSDPServer() | |||||
| debug.insertnamespace('s', s) | |||||
| port = reactor.listenMulticast(SSDP_PORT, s, listenMultiple=True) | |||||
| port.joinGroup(SSDP_ADDR) | |||||
| port.setLoopbackMode(0) # don't get our own sends | |||||
| uuid = generateuuid() | |||||
| urlbase = 'http://%s:%d/' % (listenAddr, listenPort) | |||||
| # Create SOAP server | |||||
| from twisted.web import server, resource, static | |||||
| from ContentDirectory import ContentDirectoryServer | |||||
| from ConnectionManager import ConnectionManagerServer | |||||
| class WebServer(resource.Resource): | |||||
| def __init__(self): | |||||
| resource.Resource.__init__(self) | |||||
| class RootDevice(static.Data): | |||||
| def __init__(self): | |||||
| r = { | |||||
| 'hostname': socket.gethostname(), | |||||
| 'uuid': uuid, | |||||
| 'urlbase': urlbase, | |||||
| } | |||||
| d = file('root-device.xml').read() % r | |||||
| static.Data.__init__(self, d, 'text/xml') | |||||
| root = WebServer() | |||||
| debug.insertnamespace('root', root) | |||||
| content = resource.Resource() | |||||
| mediapath = 'media' | |||||
| if not os.path.isdir(mediapath): | |||||
| print >>sys.stderr, \ | |||||
| 'Sorry, %s is not a directory, no content to serve.' % `mediapath` | |||||
| sys.exit(1) | |||||
| # This sets up the root to be the media dir so we don't have to | |||||
| # enumerate the directory | |||||
| cds = ContentDirectoryServer('My Media Server', klass=FSDirectory, | |||||
| path=mediapath, urlbase=os.path.join(urlbase, 'content'), webbase=content) | |||||
| debug.insertnamespace('cds', cds) | |||||
| root.putChild('ContentDirectory', cds) | |||||
| cds = cds.control | |||||
| root.putChild('ConnectionManager', ConnectionManagerServer()) | |||||
| root.putChild('root-device.xml', RootDevice()) | |||||
| root.putChild('content', content) | |||||
| # Purely to ensure some sane mime-types. On MacOSX I need these. | |||||
| medianode = static.File('pymediaserv') | |||||
| medianode.contentTypes.update( { | |||||
| # From: http://support.microsoft.com/kb/288102 | |||||
| '.asf': 'video/x-ms-asf', | |||||
| '.asx': 'video/x-ms-asf', | |||||
| '.wma': 'audio/x-ms-wma', | |||||
| '.wax': 'audio/x-ms-wax', | |||||
| '.wmv': 'video/x-ms-wmv', | |||||
| '.wvx': 'video/x-ms-wvx', | |||||
| '.wm': 'video/x-ms-wm', | |||||
| '.wmx': 'video/x-ms-wmx', | |||||
| #'.ts': 'video/mp2t', | |||||
| '.ts': 'video/mpeg', # we may want this instead of mp2t | |||||
| '.m2t': 'video/mpeg', | |||||
| '.m2ts': 'video/mpeg', | |||||
| '.mp4': 'video/mp4', | |||||
| #'.mp4': 'video/mpeg', | |||||
| '.dat': 'video/mpeg', # VCD tracks | |||||
| '.ogm': 'application/ogg', | |||||
| '.vob': 'video/mpeg', | |||||
| #'.m4a': 'audio/mp4', # D-Link can't seem to play AAC files. | |||||
| }) | |||||
| del medianode | |||||
| site = server.Site(root) | |||||
| reactor.listenTCP(listenPort, site) | |||||
| # we need to do this after the children are there, since we send notifies | |||||
| s.register('%s::upnp:rootdevice' % uuid, | |||||
| 'upnp:rootdevice', | |||||
| urlbase + 'root-device.xml') | |||||
| s.register(uuid, | |||||
| uuid, | |||||
| urlbase + 'root-device.xml') | |||||
| s.register('%s::urn:schemas-upnp-org:device:MediaServer:1' % uuid, | |||||
| 'urn:schemas-upnp-org:device:MediaServer:1', | |||||
| urlbase + 'root-device.xml') | |||||
| s.register('%s::urn:schemas-upnp-org:service:ConnectionManager:1' % uuid, | |||||
| 'urn:schemas-upnp-org:device:ConnectionManager:1', | |||||
| urlbase + 'root-device.xml') | |||||
| s.register('%s::urn:schemas-upnp-org:service:ContentDirectory:1' % uuid, | |||||
| 'urn:schemas-upnp-org:device:ContentDirectory:1', | |||||
| urlbase + 'root-device.xml') | |||||
| # Main loop | |||||
| reactor.run() | |||||
| @@ -1,256 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2008 John-Mark Gurney <jmg@funktaht.com> | |||||
| '''PVR Interface''' | |||||
| __version__ = '$Change: 1743 $' | |||||
| # $Id: //depot/python/pymeds/main/pyvr.py#10 $ | |||||
| from DIDLLite import Container, Item, VideoItem, Resource | |||||
| from FSStorage import registerklassfun, FSObject | |||||
| import os.path | |||||
| import time | |||||
| from twisted.internet import reactor | |||||
| from twisted.python import log | |||||
| import twisted.web | |||||
| import urllib.parse | |||||
| import urllib.request, urllib.error, urllib.parse | |||||
| def getPage(url, contextFactory=None, *args, **kwargs): | |||||
| """Download a web page as a string. | |||||
| Download a page. Return the HTTPClientFactory, which will | |||||
| callback with a page (as a string) or errback with a | |||||
| description of the error. | |||||
| See HTTPClientFactory to see what extra args can be passed. | |||||
| """ | |||||
| from twisted.web.client import _parse, HTTPClientFactory | |||||
| scheme, host, port, path = _parse(url) | |||||
| factory = HTTPClientFactory(url, *args, **kwargs) | |||||
| if scheme == 'https': | |||||
| from twisted.internet import ssl | |||||
| if contextFactory is None: | |||||
| contextFactory = ssl.ClientContextFactory() | |||||
| reactor.connectSSL(host, port, factory, contextFactory) | |||||
| else: | |||||
| reactor.connectTCP(host, port, factory) | |||||
| return factory | |||||
| class PYVRShow(VideoItem): | |||||
| def __init__(self, *args, **kwargs): | |||||
| baseurl = kwargs['url'] | |||||
| self.info = kwargs['info'] | |||||
| del kwargs['info'], kwargs['url'] | |||||
| VideoItem.__init__(self, *args, **kwargs) | |||||
| url = self.info['link'] | |||||
| sc = urllib.parse.urlparse(url)[0] | |||||
| if not sc: | |||||
| # need to combine w/ base url | |||||
| url = urllib.parse.urljoin(baseurl, url) | |||||
| self.res = Resource(url, | |||||
| 'http-get:*:%s:*' % self.info['mimetype']) | |||||
| self.res.duration = self.info['duration'] | |||||
| try: | |||||
| self.date = self.info['startdateiso'] | |||||
| except KeyError: | |||||
| pass | |||||
| def doUpdate(self): | |||||
| pass | |||||
| import xml.sax | |||||
| import xml.sax.handler | |||||
| from xml.sax.saxutils import unescape | |||||
| class RecordingXML(xml.sax.handler.ContentHandler): | |||||
| dataels = ('title', 'startdateiso', 'subtitle', 'duration', | |||||
| 'mimetype', 'link', 'delete', ) | |||||
| def __init__(self): | |||||
| self.shows = {} | |||||
| self.data = None | |||||
| def characters(self, chars): | |||||
| if self.data is not None: | |||||
| self.data.append(chars) | |||||
| def startElement(self, name, attrs): | |||||
| if name in self.dataels: | |||||
| self.data = [] | |||||
| self.curel = name | |||||
| elif name == 'record': | |||||
| self.currec = {} | |||||
| def endElement(self, name): | |||||
| if name in self.dataels: | |||||
| data = unescape(''.join(self.data)) | |||||
| self.currec[self.curel] = data | |||||
| elif name == 'record': | |||||
| rec = self.currec | |||||
| try: | |||||
| self.shows[rec['title']].append(rec) | |||||
| except KeyError: | |||||
| self.shows[rec['title']] = [ rec ] | |||||
| self.data = None | |||||
| def recxmltoobj(page): | |||||
| obj = RecordingXML() | |||||
| xml.sax.parseString(page, obj) | |||||
| return obj.shows | |||||
| class PYVRShows(Container): | |||||
| def __init__(self, *args, **kwargs): | |||||
| self.pyvr = kwargs['pyvr'] | |||||
| del kwargs['pyvr'] | |||||
| self.show = kwargs['show'] | |||||
| del kwargs['show'] | |||||
| Container.__init__(self, *args, **kwargs) | |||||
| self.shows = {} | |||||
| self.lastmodified = None | |||||
| def checkUpdate(self): | |||||
| self.pyvr.checkUpdate() | |||||
| if self.pyvr.lastmodified != self.lastmodified: | |||||
| self.doUpdate() | |||||
| @staticmethod | |||||
| def getunique(eps, ep): | |||||
| i = 1 | |||||
| while True: | |||||
| title = '%s Copy %d' % (ep['subtitle'], i) | |||||
| if title not in eps: | |||||
| return title | |||||
| i += 1 | |||||
| @staticmethod | |||||
| def eplisttodict(eps): | |||||
| ret = {} | |||||
| for pos, i in enumerate(eps): | |||||
| title = i['subtitle'] | |||||
| if title in ret: | |||||
| print('WARNING: dup:', repr(i), repr(ret[title])) | |||||
| title = PYVRShows.getunique(ret, i) | |||||
| i['pos'] = pos | |||||
| ret[title] = i | |||||
| return ret | |||||
| def genChildren(self): | |||||
| return self.eplisttodict(self.pyvr.shows[self.show]) | |||||
| def createObject(self, i, arg): | |||||
| return PYVRShow, i, (), { 'url': self.pyvr.url, 'info': arg } | |||||
| def sort(self): | |||||
| Container.sort(self, lambda x, y: cmp(x.info['pos'], | |||||
| y.info['pos'])) | |||||
| def doUpdate(self): | |||||
| Container.doUpdate(self) | |||||
| self.lastmodified = self.pyvr.lastmodified | |||||
| class PYVR(FSObject, Container): | |||||
| def __init__(self, *args, **kwargs): | |||||
| self.url = kwargs.pop('url') | |||||
| FSObject.__init__(self, kwargs.pop('path')) | |||||
| Container.__init__(self, *args, **kwargs) | |||||
| #self.pend = None | |||||
| self.lastmodified = None | |||||
| self.newobjs = None | |||||
| self.objs = {} | |||||
| self.lastcheck = 0 | |||||
| def checkUpdate(self): | |||||
| #if self.pend is not None: | |||||
| # raise self.pend | |||||
| if time.time() - self.lastcheck < 10: | |||||
| return | |||||
| # Check to see if any changes have been made | |||||
| self.runCheck() | |||||
| def runCheck(self): | |||||
| while True: | |||||
| try: | |||||
| self.page = urllib.request.urlopen(self.url) | |||||
| break | |||||
| except urllib.error.HTTPError: | |||||
| time.sleep(.1) | |||||
| #self.page = getPage(self.url, method='HEAD') | |||||
| #self.page.deferred.addErrback(self.errCheck).addCallback( | |||||
| # self.doCheck) | |||||
| #self.pend = self.page.deferred | |||||
| return self.doCheck(self.page) | |||||
| def errCheck(self, x): | |||||
| print('errCheck:', repr(x)) | |||||
| self.runCheck() | |||||
| def doCheck(self, x): | |||||
| #print 'doCheck:', self.page.status | |||||
| #if self.page.status != '200': | |||||
| # print 'foo' | |||||
| # return reactor.callLater(.01, self.runCheck) | |||||
| self.lastcheck = time.time() | |||||
| slm = self.page.info()['last-modified'] | |||||
| if slm == self.lastmodified: | |||||
| # Page the same, don't do anything | |||||
| #self.pend = None | |||||
| return | |||||
| return self.parsePage(self.page) | |||||
| #self.page = getPage(self.url) | |||||
| #self.page.deferred.addCallback(self.parsePage) | |||||
| #self.pend = self.page.deferred | |||||
| #return self.pend | |||||
| def parsePage(self, page): | |||||
| slm = self.page.info()['last-modified'] | |||||
| self.lastmodified = slm | |||||
| del self.page | |||||
| #self.pend = None | |||||
| self.newobjs = recxmltoobj(page.read()) | |||||
| print('pp:', repr(self.newobjs)) | |||||
| self.doUpdate() | |||||
| def genChildren(self): | |||||
| return list(self.newobjs.keys()) | |||||
| def createObject(self, i, arg=None): | |||||
| print('co:', repr(i), repr(arg)) | |||||
| return PYVRShows, i, (), { 'show': i, 'pyvr': self } | |||||
| def doUpdate(self): | |||||
| if self.newobjs is None: | |||||
| import traceback | |||||
| traceback.print_stack(file=log.logfile) | |||||
| raise ValueError('did not get shows') | |||||
| self.shows = self.newobjs | |||||
| Container.doUpdate(self) | |||||
| self.newobjs = None | |||||
| def detectpyvrfile(path, fobj): | |||||
| bn = os.path.basename(path) | |||||
| if bn == 'PYVR': | |||||
| return PYVR, { 'url': fobj.readline().strip(), 'path': path } | |||||
| return None, None | |||||
| registerklassfun(detectpyvrfile) | |||||
| @@ -1,3 +0,0 @@ | |||||
| twisted | |||||
| -e git+https://www.funkthat.com/gitea/jmg/SOAPpy@main#egg=SOAPpy-py3 | |||||
| -e git+https://www.funkthat.com/gitea/jmg/wstools-py3@main#egg=wstools-py3 | |||||
| @@ -1,38 +0,0 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <root xmlns="urn:schemas-upnp-org:device-1-0"> | |||||
| <specVersion> | |||||
| <major>1</major> | |||||
| <minor>0</minor> | |||||
| </specVersion> | |||||
| <URLBase>%(urlbase)s</URLBase> | |||||
| <device> | |||||
| <deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType> | |||||
| <friendlyName>PyMedS (%(hostname)s)</friendlyName> | |||||
| <manufacturer>JMGDIE</manufacturer> | |||||
| <manufacturerURL>http://www.funkthat.com/</manufacturerURL> | |||||
| <modelDescription>hopefully a UPnP/AV 1.0 Compliant Media Server</modelDescription> | |||||
| <modelName>PyMedS</modelName> | |||||
| <modelNumber>1</modelNumber> | |||||
| <modelURL>https://www.funkthat.com/~jmg/jmpc/pymeds.html</modelURL> | |||||
| <serialNumber>0</serialNumber> | |||||
| <UDN>%(uuid)s</UDN> | |||||
| <UPC/> | |||||
| <serviceList> | |||||
| <service> | |||||
| <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType> | |||||
| <serviceId>urn:upnp-org:serviceId:urn:schemas-upnp-org:service:ConnectionManager</serviceId> | |||||
| <SCPDURL>/ConnectionManager/scpd.xml</SCPDURL> | |||||
| <controlURL>/ConnectionManager/control</controlURL> | |||||
| <eventSubURL>/ConnectionManager/event</eventSubURL> | |||||
| </service> | |||||
| <service> | |||||
| <serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType> | |||||
| <serviceId>urn:upnp-org:serviceId:urn:schemas-upnp-org:service:ContenDirectory</serviceId> | |||||
| <SCPDURL>/ContentDirectory/scpd.xml</SCPDURL> | |||||
| <controlURL>/ContentDirectory/control</controlURL> | |||||
| <eventSubURL>/ContentDirectory/event</eventSubURL> | |||||
| </service> | |||||
| </serviceList> | |||||
| <deviceList/> | |||||
| </device> | |||||
| </root> | |||||
| @@ -1,558 +0,0 @@ | |||||
| #!/usr/bin/env python | |||||
| # Copyright 2009 John-Mark Gurney <jmg@funkthat.com> | |||||
| '''CD Changer Module''' | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| import CDDB | |||||
| import audio | |||||
| import filelock | |||||
| import os.path | |||||
| import shelve | |||||
| import slinke | |||||
| from DIDLLite import Container, StorageSystem, StorageVolume | |||||
| from DIDLLite import MusicGenre, MusicTrack, MusicAlbum, Resource, ResourceList | |||||
| from FSStorage import registerklassfun | |||||
| from twisted.python import log, threadable, failure | |||||
| from twisted.internet import defer, protocol, reactor, threads | |||||
| from twisted.internet.interfaces import IPushProducer, IPullProducer, IConsumer | |||||
| from twisted.internet.serialport import SerialPort | |||||
| from twisted.web import error, http, resource, server | |||||
| from zope.interface import implements | |||||
| def isint(s): | |||||
| try: | |||||
| i = int(s) | |||||
| return True | |||||
| except ValueError: | |||||
| return False | |||||
| class LimitedAudioProducer(object): | |||||
| implements(IPushProducer, IPullProducer, IConsumer) | |||||
| # request, args | |||||
| def __init__(self, consumer, *args, **kwargs): | |||||
| self.remain = self.setlength(consumer) | |||||
| self.consumer = consumer | |||||
| audio.AudioPlayer(self, *args, **kwargs) | |||||
| @staticmethod | |||||
| def setlength(request): | |||||
| if request.responseHeaders.hasHeader('content-length'): | |||||
| secs = int( | |||||
| request.responseHeaders.getRawHeaders( | |||||
| 'content-length')[0].strip()) | |||||
| r = secs * audio.bytespersecmt( | |||||
| request.responseHeaders.getRawHeaders( | |||||
| 'content-type')[0]) | |||||
| request.responseHeaders.setRawHeaders( | |||||
| 'content-length', [ str(r) ]) | |||||
| else: | |||||
| r = None | |||||
| return r | |||||
| def shutdown(self): | |||||
| print('lap: shutdown', repr(self.consumer)) | |||||
| # XXX - I still get writes after I've asked my producer to stop | |||||
| self.write = lambda x: None | |||||
| self.producer.stopProducing() | |||||
| self.producer = None | |||||
| self.consumer.unregisterProducer() | |||||
| self.consumer = None | |||||
| #self.remain = 0 # No more writes | |||||
| # IPushProducer | |||||
| def pauseProducing(self): | |||||
| return self.producer.pauseProducing() | |||||
| def resumeProducing(self): | |||||
| return self.producer.resumeProducing() | |||||
| def stopProducing(self): | |||||
| print('lap: sp') | |||||
| self.shutdown() | |||||
| # IConsumer | |||||
| def registerProducer(self, producer, streaming): | |||||
| print('lap: regp') | |||||
| self.producer = producer | |||||
| self.streamingProducer = streaming | |||||
| self.consumer.registerProducer(self, streaming) | |||||
| def unregisterProducer(): | |||||
| print('lap: unregp') | |||||
| self.shutdown() | |||||
| def write(self, data): | |||||
| if self.remain is not None: | |||||
| data = data[:self.remain] | |||||
| self.remain -= len(data) | |||||
| self.consumer.write(data) | |||||
| if self.remain is not None and not self.remain: | |||||
| # Done producing | |||||
| self.shutdown() | |||||
| class LimitedAudioRes(audio.AudioResource): | |||||
| producerFactory = LimitedAudioProducer | |||||
| def render(self, request): | |||||
| r = audio.AudioResource.render(self, request) | |||||
| if request.method == 'HEAD': | |||||
| LimitedAudioProducer.setlength(request) | |||||
| return r | |||||
| class ChangerTrack(resource.Resource): | |||||
| isLeaf = True | |||||
| def __init__(self, obj, res): | |||||
| resource.Resource.__init__(self) | |||||
| self._obj = obj | |||||
| self._res = res | |||||
| def getmimetype(self): | |||||
| return self._res.getmimetype() | |||||
| def render(self, request): | |||||
| print('CTrender:', repr(request.postpath), repr(request.method), repr(request)) | |||||
| self.setlength(request) | |||||
| if request.method == 'HEAD': | |||||
| return self._res.render(request) | |||||
| # Needs to be created here so that it will fire. | |||||
| # If not, the list may be walked before we add it. | |||||
| nf = request.notifyFinish() | |||||
| d = self._obj.trylock(20, request) | |||||
| d.addCallback(lambda x: self._obj.cue()) | |||||
| # XXX - how to stop playback when track is done? | |||||
| #d.addCallback(lambda x: self.setlength(request)) | |||||
| d.addCallback(lambda x: self.printarg('before render, after cue')) | |||||
| d.addCallback(lambda x: self._res.render(request)) | |||||
| d.addCallback(lambda x: self.docleanup(nf, request)) | |||||
| d.addErrback(lambda exc: self.failed(exc, request)) | |||||
| # XXX - add errBack? | |||||
| d.addCallback(lambda x: self.printarg('after render, before play')) | |||||
| d.addCallback(lambda x: self._obj.play()) | |||||
| d.addErrback(self.logerr) | |||||
| return server.NOT_DONE_YET | |||||
| def setlength(self, request): | |||||
| r = self._obj.getlength() | |||||
| request.responseHeaders.setRawHeaders( | |||||
| 'content-length', [ str(r) ]) | |||||
| def docleanup(self, nf, request): | |||||
| print('docleanup') | |||||
| nf.addBoth(self.dostop) | |||||
| nf.addBoth(lambda x: self.dounlock(request)) | |||||
| nf.addErrback(self.logerr, 'nf') | |||||
| def dounlock(self, request): | |||||
| self._obj.unlock(request) | |||||
| # Cancel the error back so we don't get a warning. | |||||
| return None | |||||
| def dostop(self, arg): | |||||
| print('dostop') | |||||
| d = self._obj.stop() | |||||
| # Make sure we have stopped before we continue | |||||
| d.addBoth(lambda x: arg) | |||||
| return d | |||||
| def logerr(self, exc, *args): | |||||
| print('logerr:', repr(args)) | |||||
| exc.printTraceback() | |||||
| #exc.printDetailedTraceback() | |||||
| def failed(self, exc, request): | |||||
| print('in this failed case', self._obj.haslock(request), repr(request)) | |||||
| if self._obj.haslock(request): | |||||
| self.dounlock(request) | |||||
| # XXX - look at queue and decide | |||||
| #request.responseHeaders.addRawHeader('Retry-After') | |||||
| res = error.ErrorPage(http.SERVICE_UNAVAILABLE, | |||||
| 'failed w/ Exception', exc).render(request) | |||||
| request.write(res) | |||||
| request.finish() | |||||
| return exc | |||||
| def printarg(self, args): | |||||
| print('pa:', repr(self), repr(args)) | |||||
| def unregisterProducer(self): | |||||
| resource.Resource.unregisterProducer(self) | |||||
| raise NotImplementedError | |||||
| class SLinkEChangerDiscTrack(MusicTrack): | |||||
| def __init__(self, *args, **kwargs): | |||||
| discobj = kwargs.pop('discobj') | |||||
| track = kwargs.pop('track') | |||||
| kwargs['content'] = ChangerTrack(self, discobj.getResource()) | |||||
| MusicTrack.__init__(self, *args, **kwargs) | |||||
| self._discobj = discobj | |||||
| self._track = track | |||||
| self.originalTrackNumber = str(track) | |||||
| if False: | |||||
| self.bitrate = bitrate | |||||
| self.url = '%s/%s' % (self.cd.urlbase, self.id) | |||||
| self.res = ResourceList() | |||||
| res = Resource(self.url, 'http-get:*:%s:*' % | |||||
| kwargs['content'].getmimetype()) | |||||
| l = self.getlength() | |||||
| if l is not None: | |||||
| res.duration = l | |||||
| self.res.append(res) | |||||
| def getlength(self): | |||||
| '''Returns None if length in seconds is unavailable.''' | |||||
| track = self._track | |||||
| cddbid = self._discobj.getID()[2:] | |||||
| cddbid[-1] = cddbid[-1] * 75 # frames/sec | |||||
| return (cddbid[track] - cddbid[track - 1]) // 75 | |||||
| def trylock(self, t, obj): | |||||
| return self._discobj.trylock(t, obj) | |||||
| def haslock(self, obj): | |||||
| return self._discobj.haslock(obj) | |||||
| def unlock(self, obj): | |||||
| self._discobj.unlock(obj) | |||||
| def cue(self): | |||||
| return self._discobj.cueTrack(self._track) | |||||
| def play(self): | |||||
| return self._discobj.play() | |||||
| def stop(self): | |||||
| return self._discobj.stop() | |||||
| class SLinkEChangerDisc(MusicAlbum): | |||||
| def __init__(self, *args, **kwargs): | |||||
| changer = kwargs.pop('changer') | |||||
| self._changer = changer | |||||
| disc = kwargs.pop('disc') | |||||
| self._disc = disc | |||||
| self._discnum = int(disc) | |||||
| self._lastid = None | |||||
| kwargs['content'] = ChangerTrack(self, self.getResource()) | |||||
| MusicAlbum.__init__(self, *args, **kwargs) | |||||
| self.url = '%s/%s' % (self.cd.urlbase, self.id) | |||||
| self.res = ResourceList() | |||||
| res = Resource(self.url, 'http-get:*:%s:*' % | |||||
| kwargs['content'].getmimetype()) | |||||
| l = self.getlength() | |||||
| if l is not None: | |||||
| res.duration = l | |||||
| self.res.append(res) | |||||
| def getlength(self): | |||||
| return self.getID()[-1] | |||||
| def trylock(self, t, obj): | |||||
| return self._changer.trylock(t, obj) | |||||
| def haslock(self, obj): | |||||
| return self._changer.haslock(obj) | |||||
| def unlock(self, obj): | |||||
| return self._changer.unlock(obj) | |||||
| def getResource(self): | |||||
| return self._changer.getResource() | |||||
| def getID(self): | |||||
| return self._changer.getID(self._disc) | |||||
| def cue(self): | |||||
| return self._changer.cueDisc(self._discnum, 1) | |||||
| def cueTrack(self, track): | |||||
| return self._changer.cueDisc(self._discnum, track) | |||||
| def play(self): | |||||
| return self._changer.play() | |||||
| def stop(self): | |||||
| return self._changer.stop() | |||||
| # ContentDirectory calls this | |||||
| def checkUpdate(self): | |||||
| print('cU') | |||||
| curid = self.getID() | |||||
| if self._lastid != curid: | |||||
| print('dU') | |||||
| self.doUpdate() | |||||
| self._lastid = curid | |||||
| def genChildren(self): | |||||
| return dict(('%02d' % i, i) for i in range(1, self.getID()[1] + 1)) | |||||
| def createObject(self, i, arg): | |||||
| return SLinkEChangerDiscTrack, i, (), { 'discobj': self, 'track': arg } | |||||
| # This is not a child of FSObject, since we will change the shelve on our own. | |||||
| class SLinkEChanger(StorageSystem, slinke.CDPlayerProtocol): | |||||
| def __init__(self, *args, **kwargs): | |||||
| s = filelock.LockShelve(kwargs.pop('file'), flag='rw') | |||||
| StorageSystem.__init__(self, *args, **kwargs) | |||||
| #slinke.CDPlayerProtocol.__init__(self) # XXX - none exists!?! | |||||
| self._changed = True | |||||
| self._lock = None | |||||
| self._poweroff = None | |||||
| self._pendinglocks = [] | |||||
| self._s = s | |||||
| reactor.addSystemEventTrigger('after', 'shutdown', | |||||
| self.aftershutdown) | |||||
| sl = slinke.SLinkE() | |||||
| config = s['config'] | |||||
| s = SerialPort(sl, config['serialport'], reactor, | |||||
| **config['serialkwargs']) | |||||
| th = slinke.SLinkProtocol() | |||||
| sl.openPort(config['port'], th) | |||||
| th.connectCDPlayer(config['cdnum'], self) | |||||
| self._audiores = LimitedAudioRes(config['audiodev'], | |||||
| config['audiodefault']) | |||||
| def aftershutdown(self): | |||||
| print('in SLinkEChanger after shutdown') | |||||
| self._s.close() | |||||
| self._s = None | |||||
| def trylock(self, t, obj): | |||||
| '''Try to lock the Changer, timeout in t seconds. Associate | |||||
| lock w/ obj.''' | |||||
| # XXX - figure out how background tasks can be aborted. | |||||
| # (such as getting new CDDB ids) | |||||
| assert obj is not None, 'cannot use None as lock object' | |||||
| if self._lock is None: | |||||
| if self._poweroff is not None: | |||||
| self._poweroff.cancel() | |||||
| self._poweroff = None | |||||
| self._lock = obj | |||||
| print('tl: locked:', repr(self._lock)) | |||||
| return defer.succeed(True) | |||||
| d = defer.Deferred() | |||||
| d.addErrback(self.droppendinglockobj, obj) | |||||
| cl = reactor.callLater(t, d.errback, | |||||
| failure.Failure(RuntimeError('timed out waiting for lock'))) | |||||
| self._pendinglocks.append((d, obj, cl)) | |||||
| return d | |||||
| def droppendinglockobj(self, failure, obj): | |||||
| pobj = [ x for x in self._pendinglocks if x[1] is obj ][0] | |||||
| self._pendinglocks.remove(pobj) | |||||
| return failure | |||||
| def haslock(self, obj): | |||||
| if self._lock is obj: | |||||
| return True | |||||
| return False | |||||
| def unlock(self, obj): | |||||
| print('unlock:', repr(obj)) | |||||
| if self._lock is None: | |||||
| print('ul: not locked') | |||||
| raise RuntimeError('unlocked when not locked') | |||||
| if obj is not self._lock: | |||||
| print('ul: wrong obj') | |||||
| raise ValueError('unlocking when not locked by: %s, was locked by: %s' % (repr(obj), self._lock)) | |||||
| if not self._pendinglocks: | |||||
| print('really unlocked') | |||||
| self._lock = None | |||||
| self._poweroff = reactor.callLater(300, self.turnoff) | |||||
| return | |||||
| pobj = self._pendinglocks.pop(0) | |||||
| self._lock = pobj[1] | |||||
| print('passing lock:', repr(self._lock)) | |||||
| pobj[2].cancel() | |||||
| pobj[0].callback(True) | |||||
| @defer.deferredGenerator | |||||
| def turnoff(self): | |||||
| # needs to be first as checkids may reschedule | |||||
| # XXX - This really should only be done at start up, or | |||||
| # door open events. | |||||
| self._poweroff = None | |||||
| a = defer.waitForDeferred(self.checkids()) | |||||
| yield a | |||||
| a.getResult() | |||||
| print('powering cd changer off') | |||||
| # checkids may have rescheduled us. If we don't cancel it, | |||||
| # we'd wake up every five minutes just to turn off again. | |||||
| if self._poweroff is not None: | |||||
| self._poweroff.cancel() | |||||
| self._poweroff = None | |||||
| a = defer.waitForDeferred(self.transport.poweroff()) | |||||
| yield a | |||||
| a.getResult() | |||||
| @defer.deferredGenerator | |||||
| def checkids(self): | |||||
| print('starting checkids') | |||||
| a = defer.waitForDeferred(self.transport.poweron()) | |||||
| yield a | |||||
| print('power should be on:', repr(a.getResult())) | |||||
| discs = sorted(self.transport.discs()) | |||||
| print(discs) | |||||
| for i in self.transport: | |||||
| discnum = i | |||||
| i = str(i) | |||||
| try: | |||||
| id = self.getID(i) | |||||
| #print `i`, `id`, `self._s[i]` | |||||
| continue | |||||
| except KeyError: | |||||
| pass | |||||
| print('missing:', repr(i)) | |||||
| # No ID, fetch it. | |||||
| a = defer.waitForDeferred(self.trylock(5, self)) | |||||
| yield a | |||||
| a.getResult() | |||||
| try: | |||||
| a = defer.waitForDeferred(self.transport.getcddbid(discnum)) | |||||
| yield a | |||||
| cddbid = a.getResult() | |||||
| a = defer.waitForDeferred(threads.deferToThread(CDDB.query, cddbid)) | |||||
| yield a | |||||
| queryres = a.getResult() | |||||
| print('res:', repr(i), repr(queryres)) | |||||
| self._s[i] = { 'query': queryres, | |||||
| 'cddbid': cddbid, } | |||||
| self._changed = True | |||||
| except slinke.NoDisc: | |||||
| print('Disc not present: %d' % discnum) | |||||
| continue | |||||
| finally: | |||||
| self.unlock(self) | |||||
| def cueDisc(self, disc, track): | |||||
| # int is here since we talk about discs as strings | |||||
| return self.transport.cuedisc(int(disc), track) | |||||
| def play(self): | |||||
| return self.transport.play() | |||||
| def stop(self): | |||||
| return self.transport.stop() | |||||
| def getResource(self): | |||||
| return self._audiores | |||||
| def getID(self, disc): | |||||
| try: | |||||
| return self._s[disc]['cddbid'] | |||||
| except KeyError: | |||||
| if int(disc) == self.transport.curdisc: | |||||
| pass | |||||
| raise | |||||
| def getMatch(self, disc, wait=False): | |||||
| q = self._s[disc]['query'] | |||||
| if q[0] == 200: | |||||
| return q[1] | |||||
| elif q[0] == 202: | |||||
| return | |||||
| elif q[0] in (211, 210): | |||||
| # 210 multiple matches | |||||
| return q[1][0] | |||||
| else: | |||||
| raise ValueError('what to do? %s' % repr(self._s[disc])) | |||||
| def getDiscTitle(self, disc, wait=False): | |||||
| m = self.getMatch(disc, wait) | |||||
| if m is None: | |||||
| return '%03d' % int(disc) | |||||
| t = m['title'] | |||||
| try: | |||||
| t = t.decode('utf8') | |||||
| except UnicodeDecodeError: | |||||
| t = t.decode('iso8859-1') | |||||
| if t.count('/') > 1: | |||||
| print('tcount:', repr(t)) | |||||
| try: | |||||
| return t.split('/', 1)[1] | |||||
| except IndexError: | |||||
| print('ie:', repr(t)) | |||||
| return t | |||||
| # CDPlayerProtocol interface | |||||
| def connectionMade(self): | |||||
| super(SLinkEChanger, self).connectionMade() | |||||
| print('attached to cdplayer') | |||||
| self._changed = True | |||||
| # Make sure we start out off, or if we are missing CDDB id's, | |||||
| # that we get them. | |||||
| self.turnoff() | |||||
| def stateChange(self): | |||||
| print('sC') | |||||
| # ContentDirectory calls this | |||||
| def checkUpdate(self): | |||||
| if self._changed: | |||||
| self._changed = False | |||||
| self.doUpdate() | |||||
| # StorageSystem interface | |||||
| def genChildren(self): | |||||
| return dict((self.getDiscTitle(x), | |||||
| x) for x in self._s if isint(x) and self.transport and | |||||
| int(x) in self.transport) | |||||
| #def genCurrent(self): | |||||
| def createObject(self, i, arg): | |||||
| return SLinkEChangerDisc, i, (), { 'changer': self, 'disc': arg } | |||||
| def detectslinkemod(origpath, fobj): | |||||
| # XXX - shelve adds the extension back | |||||
| origpath, ext = os.path.splitext(origpath) | |||||
| if ext == '.lock': | |||||
| return None, None | |||||
| s = shelve.open(origpath, 'r') | |||||
| if 'config' in s: | |||||
| # XXX - expand detection | |||||
| return SLinkEChanger, { 'file': origpath } | |||||
| return None, None | |||||
| if __name__ != '__main__': | |||||
| registerklassfun(detectslinkemod, debug=False) | |||||
| else: | |||||
| # do create work | |||||
| pass | |||||
| @@ -1,110 +0,0 @@ | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| # Copyright 2007 - Frank Scholz <coherence@beebits.net> | |||||
| """ SOAP-lite | |||||
| some simple functions to implement the SOAP msgs | |||||
| needed by UPnP with ElementTree | |||||
| inspired by ElementSOAP.py | |||||
| """ | |||||
| from et import ET | |||||
| NS_SOAP_ENV = "{http://schemas.xmlsoap.org/soap/envelope/}" | |||||
| NS_SOAP_ENC = "{http://schemas.xmlsoap.org/soap/encoding/}" | |||||
| NS_XSI = "{http://www.w3.org/1999/XMLSchema-instance}" | |||||
| NS_XSD = "{http://www.w3.org/1999/XMLSchema}" | |||||
| SOAP_ENCODING = "http://schemas.xmlsoap.org/soap/encoding/" | |||||
| UPNPERRORS = {401:'Invalid Action', | |||||
| 402:'Invalid Args', | |||||
| 501:'Action Failed', | |||||
| 600:'Argument Value Invalid', | |||||
| 601:'Argument Value Out of Range', | |||||
| 602:'Optional Action Not Implemented', | |||||
| 603:'Out Of Memory', | |||||
| 604:'Human Intervention Required', | |||||
| 605:'String Argument Too Long', | |||||
| 606:'Action Not Authorized', | |||||
| 607:'Signature Failure', | |||||
| 608:'Signature Missing', | |||||
| 609:'Not Encrypted', | |||||
| 610:'Invalid Sequence', | |||||
| 611:'Invalid Control URL', | |||||
| 612:'No Such Session',} | |||||
| def build_soap_error(status,description='without words'): | |||||
| """ builds an UPnP SOAP error msg | |||||
| """ | |||||
| root = ET.Element('s:Fault') | |||||
| ET.SubElement(root,'faultcode').text='s:Client' | |||||
| ET.SubElement(root,'faultstring').text='UPnPError' | |||||
| e = ET.SubElement(root,'detail') | |||||
| e = ET.SubElement(e, 'UPnPError') | |||||
| e.attrib['xmlns']='urn:schemas-upnp-org:control-1-0' | |||||
| ET.SubElement(e,'errorCode').text=str(status) | |||||
| ET.SubElement(e,'errorDescription').text=UPNPERRORS.get(status,description) | |||||
| return build_soap_call(None, root, encoding=None) | |||||
| def build_soap_call(method, arguments, is_response=False, | |||||
| encoding=SOAP_ENCODING, | |||||
| envelope_attrib=None, | |||||
| typed=None): | |||||
| """ create a shell for a SOAP request or response element | |||||
| - set method to none to omitt the method element and | |||||
| add the arguments directly to the body (for an error msg) | |||||
| - arguments can be a dict or an ET.Element | |||||
| """ | |||||
| envelope = ET.Element("s:Envelope") | |||||
| if envelope_attrib: | |||||
| for n in envelope_attrib: | |||||
| envelope.attrib.update({n[0] : n[1]}) | |||||
| else: | |||||
| envelope.attrib.update({'s:encodingStyle' : "http://schemas.xmlsoap.org/soap/encoding/"}) | |||||
| envelope.attrib.update({'xmlns:s' :"http://schemas.xmlsoap.org/soap/envelope/"}) | |||||
| body = ET.SubElement(envelope, "s:Body") | |||||
| if method: | |||||
| # append the method call | |||||
| if is_response is True: | |||||
| method += "Response" | |||||
| re = ET.SubElement(body,method) | |||||
| if encoding: | |||||
| re.set(NS_SOAP_ENV + "encodingStyle", encoding) | |||||
| else: | |||||
| re = body | |||||
| # append the arguments | |||||
| if isinstance(arguments,dict): | |||||
| type_map = {str: 'xsd:string', | |||||
| bytes: 'xsd:string', | |||||
| int: 'xsd:int', | |||||
| float: 'xsd:float', | |||||
| bool: 'xsd:boolean'} | |||||
| for arg_name, arg_val in arguments.items(): | |||||
| arg_type = type_map[type(arg_val)] | |||||
| if isinstance(arg_val, bytes): | |||||
| arg_val = arg_val.decode('utf-8') | |||||
| arg_val = str(arg_val) | |||||
| if arg_type == 'xsd:boolean': | |||||
| arg_val = arg_val.lower() | |||||
| e = ET.SubElement(re, arg_name) | |||||
| if typed and arg_type: | |||||
| if not isinstance(type, ET.QName): | |||||
| arg_type = ET.QName("http://www.w3.org/1999/XMLSchema", arg_type) | |||||
| e.set(NS_XSI + "type", arg_type) | |||||
| e.text = arg_val | |||||
| else: | |||||
| re.append(arguments) | |||||
| preamble = """<?xml version="1.0" encoding="utf-8"?>""" | |||||
| return bytes(preamble + ET.tostring(envelope, 'unicode'), 'ascii') | |||||
| @@ -1,45 +0,0 @@ | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| # Copyright 2005, Tim Potter <tpot@samba.org> | |||||
| # Copyright 2006-2007 John-Mark Gurney <jmg@funkthat.com> | |||||
| __version__ = '$Change$' | |||||
| # $Id$ | |||||
| from twisted.web import soap | |||||
| from twisted.python import log | |||||
| from types import * | |||||
| import soap_lite | |||||
| class errorCode(Exception): | |||||
| def __init__(self, status): | |||||
| self.status = status | |||||
| class UPnPPublisher(soap.SOAPPublisher): | |||||
| """UPnP requires OUT parameters to be returned in a slightly | |||||
| different way than the SOAPPublisher class does.""" | |||||
| namespace = None | |||||
| def _gotResult(self, result, request, methodName): | |||||
| ns = self.namespace | |||||
| if ns: | |||||
| meth = "{%s}%s" % (ns, methodName) | |||||
| else: | |||||
| meth = methodName | |||||
| response = soap_lite.build_soap_call(meth, result, | |||||
| is_response=True, encoding=None) | |||||
| self._sendResponse(request, response) | |||||
| def _gotError(self, failure, request, methodName): | |||||
| e = failure.value | |||||
| status = 500 | |||||
| if isinstance(e, errorCode): | |||||
| status = e.status | |||||
| else: | |||||
| failure.printTraceback(file = log.logfile) | |||||
| response = soap_lite.build_soap_error(status) | |||||
| self._sendResponse(request, response, status=status) | |||||
| @@ -1,32 +0,0 @@ | |||||
| <DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp"> | |||||
| <container id="0\Music\All%20Tracks\" parentID="0\Music\" restricted="0" searchable="0" childCount="1"> | |||||
| <dc:title>All Tracks</dc:title> | |||||
| <upnp:class>object.container.storageFolder</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| </container> | |||||
| <container id="0\Music\Playlists\" parentID="0\Music\" restricted="0" searchable="0" childCount="0"> | |||||
| <dc:title>Playlists</dc:title> | |||||
| <upnp:class>object.container</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| </container> | |||||
| <container id="0\Music\Genres\" parentID="0\Music\" restricted="0" searchable="0" childCount="1"> | |||||
| <dc:title>Genres</dc:title> | |||||
| <upnp:class>object.container</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| </container> | |||||
| <container id="0\Music\Artists\" parentID="0\Music\" restricted="0" searchable="0" childCount="1"> | |||||
| <dc:title>Artists</dc:title> | |||||
| <upnp:class>object.container</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| </container> | |||||
| <container id="0\Music\Albums\" parentID="0\Music\" restricted="0" searchable="0" childCount="1"> | |||||
| <dc:title>Albums</dc:title> | |||||
| <upnp:class>object.container</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| </container> | |||||
| <container id="0\Music\Folders\" parentID="0\Music\" restricted="0" searchable="0" childCount="1"> | |||||
| <dc:title>Folders</dc:title> | |||||
| <upnp:class>object.container</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| </container> | |||||
| </DIDL-Lite> | |||||
| @@ -1,11 +0,0 @@ | |||||
| <DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp"> | |||||
| <item id="0\Music\Genres\Others\chimes.wav" parentID="0\Music\Genres\Others\" restricted="0"> | |||||
| <dc:title>chimes.wav</dc:title> | |||||
| <upnp:class>object.item.audioItem.musicTrack</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| <upnp:genre>Others</upnp:genre> | |||||
| <upnp:artist>Others</upnp:artist> | |||||
| <upnp:album>Others</upnp:album> | |||||
| <res protocolInfo="http-get:*:audio/x-wav:*" bitrate="176" size="15920">http://16.176.65.48:5643/web/C:\temp\Resource%20Kit\kixtart\chimes.wav</res> | |||||
| </item> | |||||
| </DIDL-Lite> | |||||
| @@ -1,14 +0,0 @@ | |||||
| <DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp"> | |||||
| <container id="0\Photo\All%20Photos\" parentID="0\Photo\" restricted="0" searchable="0" childCount="1"> | |||||
| <dc:title>All Photos</dc:title> | |||||
| <upnp:class>object.container.album.photoAlbum</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| <upnp:icon>http://16.176.65.48:5643/web/C:\temp\foo.jpg.thumb</upnp:icon> | |||||
| </container> | |||||
| <container id="0\Photo\Folders\" parentID="0\Photo\" restricted="0" searchable="0" childCount="1"> | |||||
| <dc:title>Folders</dc:title> | |||||
| <upnp:class>object.container.album.photoAlbum</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| <upnp:icon>http://16.176.65.48:5643/web/C:\temp\foo.jpg.thumb</upnp:icon> | |||||
| </container> | |||||
| </DIDL-Lite> | |||||
| @@ -1,22 +0,0 @@ | |||||
| <DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp"> | |||||
| <container id="0\Movie\" parentID="0\" restricted="0" searchable="0" childCount="0"> | |||||
| <dc:title>Movie</dc:title> | |||||
| <upnp:class>object.container</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| </container> | |||||
| <container id="0\Music\" parentID="0\" restricted="0" searchable="0" childCount="0"> | |||||
| <dc:title>Music</dc:title> | |||||
| <upnp:class>object.container</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| </container> | |||||
| <container id="0\Photo\" parentID="0\" restricted="0" searchable="0" childCount="0"> | |||||
| <dc:title>Photo</dc:title> | |||||
| <upnp:class>object.container</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| </container> | |||||
| <container id="0\OnlineMedia\" parentID="0\" restricted="0" searchable="0" childCount="0"> | |||||
| <dc:title>OnlineMedia</dc:title> | |||||
| <upnp:class>object.container</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| </container> | |||||
| </DIDL-Lite> | |||||
| @@ -1,8 +0,0 @@ | |||||
| <DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp"> | |||||
| <item id="0\\Music\\Genres\\Others\\chimes.wav" parentID="0\\Music\\Genres\\Others\\" restricted="0"> | |||||
| <dc:title>chimes.wav</dc:title> | |||||
| <upnp:class>object.item.audioItem.musicTrack</upnp:class> | |||||
| <dc:creator></dc:creator> | |||||
| </item> | |||||
| </DIDL-Lite> | |||||
| @@ -1,18 +0,0 @@ | |||||
| #!/usr/bin/python | |||||
| # Copyright 2005, Tim Potter <tpot@samba.org> | |||||
| # Licensed under the MIT license | |||||
| # http://opensource.org/licenses/mit-license.php | |||||
| """Take XML on stdin and produce pretty-printed XML on stdout.""" | |||||
| import sys | |||||
| from xml.dom import minidom | |||||
| str = "" | |||||
| for s in sys.stdin.readlines(): | |||||
| str = str + s[:-1] # Eat trailing \n | |||||
| doc = minidom.parseString(str) | |||||
| print doc.toprettyxml(indent = " ") | |||||