Browse Source

switch to using tabs instead of spaces...

[git-p4: depot-paths = "//depot/": change = 763]
replace/4e84fdb41ea781c7a8f872baa423e8b3be4045a7
John-Mark Gurney 20 years ago
parent
commit
312d124260
8 changed files with 561 additions and 575 deletions
  1. +5
    -5
      ConnectionManager.py
  2. +152
    -152
      ContentDirectory.py
  3. +213
    -213
      DIDLLite.py
  4. +148
    -160
      SSDP.py
  5. +9
    -9
      browse-zms.py
  6. +28
    -28
      cdsclient
  7. +5
    -7
      upnp.py
  8. +1
    -1
      xmlpretty

+ 5
- 5
ConnectionManager.py View File

@@ -10,10 +10,10 @@ from twisted.web import resource, static, soap
from upnp import UPnPPublisher from upnp import UPnPPublisher


class ConnectionManagerControl(UPnPPublisher): class ConnectionManagerControl(UPnPPublisher):
pass
pass


class ConnectionManagerServer(resource.Resource): class ConnectionManagerServer(resource.Resource):
def __init__(self):
resource.Resource.__init__(self)
self.putChild('scpd.xml', static.File('connection-manager-scpd.xml'))
self.putChild('control', ConnectionManagerControl())
def __init__(self):
resource.Resource.__init__(self)
self.putChild('scpd.xml', static.File('connection-manager-scpd.xml'))
self.putChild('control', ConnectionManagerControl())

+ 152
- 152
ContentDirectory.py View File

@@ -14,9 +14,9 @@
# twisted swallows the tracebacks. At the moment I'm going: # twisted swallows the tracebacks. At the moment I'm going:
# #
# try: # try:
# ....
# ....
# except: # except:
# traceback.print_exc(file = log.logfile)
# traceback.print_exc(file = log.logfile)
# #


from twisted.python import log from twisted.python import log
@@ -30,208 +30,208 @@ import traceback
from urllib import quote from urllib import quote


class ContentDirectoryControl(UPnPPublisher, dict): class ContentDirectoryControl(UPnPPublisher, dict):
"""This class implements the CDS actions over SOAP."""
def getnextID(self):
ret = str(self.nextID)
self.nextID += 1
return ret
def addContainer(self, parent, title, klass = Container, **kwargs):
ret = self.addItem(parent, klass, title, **kwargs)
self.children[ret] = self[ret]
return ret
def addItem(self, parent, klass, *args, **kwargs):
assert isinstance(self[parent], Container)
nid = self.getnextID()
i = klass(self, nid, parent, *args, **kwargs)
self.children[parent].append(i)
self[i.id] = i
return i.id
def delItem(self, id):
"""This class implements the CDS actions over SOAP."""
def getnextID(self):
ret = str(self.nextID)
self.nextID += 1
return ret
def addContainer(self, parent, title, klass = Container, **kwargs):
ret = self.addItem(parent, klass, title, **kwargs)
self.children[ret] = self[ret]
return ret
def addItem(self, parent, klass, *args, **kwargs):
assert isinstance(self[parent], Container)
nid = self.getnextID()
i = klass(self, nid, parent, *args, **kwargs)
self.children[parent].append(i)
self[i.id] = i
return i.id
def delItem(self, id):
if isinstance(self[id], Container): if isinstance(self[id], Container):
for i in self.children[id]: for i in self.children[id]:
self.delItem(i) self.delItem(i)
assert len(self.children[id]) == 0 assert len(self.children[id]) == 0
del self.children[id] del self.children[id]
del self[id]
del self[id]


def getchildren(self, item):
assert isinstance(self[item], Container)
return self.children[item][:]
def getchildren(self, item):
assert isinstance(self[item], Container)
return self.children[item][:]


def __init__(self, title, *args):
super(ContentDirectoryControl, self).__init__(*args)
fakeparent = '-1'
self.nextID = 0
self.children = { fakeparent: []}
self.needupdate = False
self.updateId = 0
self[fakeparent] = Container(None, '-1', 'fake')
root = self.addContainer(fakeparent, title)
assert root == '0'
del self[fakeparent]
del self.children[fakeparent]
def __init__(self, title, *args):
super(ContentDirectoryControl, self).__init__(*args)
fakeparent = '-1'
self.nextID = 0
self.children = { fakeparent: []}
self.needupdate = False
self.updateId = 0
self[fakeparent] = Container(None, '-1', 'fake')
root = self.addContainer(fakeparent, title)
assert root == '0'
del self[fakeparent]
del self.children[fakeparent]


def doupdate(self):
if self.needupdate:
self.needupdate += 1
self.needupdate = False
def doupdate(self):
if self.needupdate:
self.needupdate += 1
self.needupdate = False


# Required actions
# Required actions


def soap_GetSearchCapabilities(self, *args, **kwargs):
"""Required: Return the searching capabilities supported by the device."""
def soap_GetSearchCapabilities(self, *args, **kwargs):
"""Required: Return the searching capabilities supported by the device."""


log.msg('GetSearchCapabilities()')
return { 'SearchCapabilitiesResponse': { 'SearchCaps': '' }}
log.msg('GetSearchCapabilities()')
return { 'SearchCapabilitiesResponse': { 'SearchCaps': '' }}


def soap_GetSortCapabilities(self, *args, **kwargs):
"""Required: Return the CSV list of meta-data tags that can be used in
sortCriteria."""
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 { 'SortCapabilitiesResponse': { 'SortCaps': '' }}
def soap_GetSystemUpdateID(self, *args, **kwargs):
"""Required: Return the current value of state variable SystemUpdateID."""
log.msg('GetSortCapabilities()')
return { 'SortCapabilitiesResponse': { 'SortCaps': '' }}


log.msg('GetSystemUpdateID()')
self.needupdate = True
return { 'SystemUpdateIdResponse': { 'Id': self.updateId }}
def soap_GetSystemUpdateID(self, *args, **kwargs):
"""Required: Return the current value of state variable SystemUpdateID."""


BrowseFlags = ('BrowseMetaData', 'BrowseDirectChildren')
log.msg('GetSystemUpdateID()')
self.needupdate = True
return { 'SystemUpdateIdResponse': { 'Id': self.updateId }}


def soap_Browse(self, *args):
"""Required: Incrementally browse the native heirachy of the Content
Directory objects exposed by the Content Directory Service."""
BrowseFlags = ('BrowseMetaData', 'BrowseDirectChildren')


(ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount,
SortCriteria) = args
StartingIndex = int(StartingIndex)
RequestedCount = int(RequestedCount)
def soap_Browse(self, *args):
"""Required: Incrementally browse the native heirachy of the Content
Directory objects exposed by the Content Directory Service."""


log.msg('Browse(ObjectID=%s, BrowseFlags=%s, Filter=%s, '
'StartingIndex=%s RequestedCount=%s SortCriteria=%s)' %
(`ObjectID`, `BrowseFlag`, `Filter`, `StartingIndex`,
`RequestedCount`, `SortCriteria`))
(ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount,
SortCriteria) = args
StartingIndex = int(StartingIndex)
RequestedCount = int(RequestedCount)


didl = DIDLElement()
result = {}
log.msg('Browse(ObjectID=%s, BrowseFlags=%s, Filter=%s, '
'StartingIndex=%s RequestedCount=%s SortCriteria=%s)' %
(`ObjectID`, `BrowseFlag`, `Filter`, `StartingIndex`,
`RequestedCount`, `SortCriteria`))


try:
if BrowseFlag == 'BrowseDirectChildren':
ch = self.getchildren(ObjectID)[StartingIndex: StartingIndex + RequestedCount]
filter(lambda x, s = self, d = didl: d.addItem(s[x.id]) and None, ch)
else:
didl.addItem(self[ObjectID])
didl = DIDLElement()
result = {}


result = {'BrowseResponse': {'Result': didl.toString() ,
'NumberReturned': didl.numItems(),
'TotalMatches': didl.numItems(),
'UpdateID': 0}}
try:
if BrowseFlag == 'BrowseDirectChildren':
ch = self.getchildren(ObjectID)[StartingIndex: StartingIndex + RequestedCount]
filter(lambda x, s = self, d = didl: d.addItem(s[x.id]) and None, ch)
else:
didl.addItem(self[ObjectID])


except:
traceback.print_exc(file=log.logfile)
result = {'BrowseResponse': {'Result': didl.toString() ,
'NumberReturned': didl.numItems(),
'TotalMatches': didl.numItems(),
'UpdateID': 0}}


log.msg('Returning: %s' % result)
except:
traceback.print_exc(file=log.logfile)


return result
log.msg('Returning: %s' % result)


# Optional actions
return result


def soap_Search(self, *args, **kwargs):
"""Search for objects that match some search criteria."""
# Optional actions


(ContainerID, SearchCriteria, Filter, StartingIndex,
RequestedCount, SortCriteria) = args
def soap_Search(self, *args, **kwargs):
"""Search for objects that match some search criteria."""


log.msg('Search(ContainerID=%s, SearchCriteria=%s, Filter=%s, ' \
'StartingIndex=%s, RequestedCount=%s, SortCriteria=%s)' %
(`ContainerID`, `SearchCriteria`, `Filter`,
`StartingIndex`, `RequestedCount`, `SortCriteria`))
def soap_CreateObject(self, *args, **kwargs):
"""Create a new object."""
(ContainerID, SearchCriteria, Filter, StartingIndex,
RequestedCount, SortCriteria) = args


(ContainerID, Elements) = args
log.msg('Search(ContainerID=%s, SearchCriteria=%s, Filter=%s, ' \
'StartingIndex=%s, RequestedCount=%s, SortCriteria=%s)' %
(`ContainerID`, `SearchCriteria`, `Filter`,
`StartingIndex`, `RequestedCount`, `SortCriteria`))


log.msg('CreateObject(ContainerID=%s, Elements=%s)' %
(`ContainerID`, `Elements`))
def soap_CreateObject(self, *args, **kwargs):
"""Create a new object."""


def soap_DestroyObject(self, *args, **kwargs):
"""Destroy the specified object."""
(ContainerID, Elements) = args


(ObjectID) = args
log.msg('CreateObject(ContainerID=%s, Elements=%s)' %
(`ContainerID`, `Elements`))


log.msg('DestroyObject(ObjectID=%s)' % `ObjectID`)
def soap_DestroyObject(self, *args, **kwargs):
"""Destroy the specified object."""


def soap_UpdateObject(self, *args, **kwargs):
"""Modify, delete or insert object metadata."""
(ObjectID) = args


(ObjectID, CurrentTagValue, NewTagValue) = args
log.msg('DestroyObject(ObjectID=%s)' % `ObjectID`)


log.msg('UpdateObject(ObjectID=%s, CurrentTagValue=%s, ' \
'NewTagValue=%s)' % (`ObjectID`, `CurrentTagValue`,
`NewTagValue`))
def soap_UpdateObject(self, *args, **kwargs):
"""Modify, delete or insert object metadata."""


def soap_ImportResource(self, *args, **kwargs):
"""Transfer a file from a remote source to a local
destination in the Content Directory Service."""
(ObjectID, CurrentTagValue, NewTagValue) = args


(SourceURI, DestinationURI) = args
log.msg('UpdateObject(ObjectID=%s, CurrentTagValue=%s, ' \
'NewTagValue=%s)' % (`ObjectID`, `CurrentTagValue`,
`NewTagValue`))


log.msg('ImportResource(SourceURI=%s, DestinationURI=%s)' %
(`SourceURI`, `DestinationURI`))
def soap_ImportResource(self, *args, **kwargs):
"""Transfer a file from a remote source to a local
destination in the Content Directory Service."""


def soap_ExportResource(self, *args, **kwargs):
"""Transfer a file from a local source to a remote
destination."""
(SourceURI, DestinationURI) = args


(SourceURI, DestinationURI) = args
log.msg('ImportResource(SourceURI=%s, DestinationURI=%s)' %
(`SourceURI`, `DestinationURI`))


log.msg('ExportResource(SourceURI=%s, DestinationURI=%s)' %
(`SourceURI`, `DestinationURI`))
def soap_ExportResource(self, *args, **kwargs):
"""Transfer a file from a local source to a remote
destination."""


def soap_StopTransferResource(self, *args, **kwargs):
"""Stop a file transfer initiated by ImportResource or
ExportResource."""
(SourceURI, DestinationURI) = args


(TransferID) = args
log.msg('ExportResource(SourceURI=%s, DestinationURI=%s)' %
(`SourceURI`, `DestinationURI`))


log.msg('StopTransferResource(TransferID=%s)' % TransferID)
def soap_StopTransferResource(self, *args, **kwargs):
"""Stop a file transfer initiated by ImportResource or
ExportResource."""


def soap_GetTransferProgress(self, *args, **kwargs):
"""Query the progress of a file transfer initiated by
an ImportResource or ExportResource action."""
(TransferID) = args


(TransferID, TransferStatus, TransferLength, TransferTotal) = args
log.msg('StopTransferResource(TransferID=%s)' % TransferID)


log.msg('GetTransferProgress(TransferID=%s, TransferStatus=%s, ' \
'TransferLength=%s, TransferTotal=%s)' %
(`TransferId`, `TransferStatus`, `TransferLength`,
`TransferTotal`))
def soap_DeleteResource(self, *args, **kwargs):
"""Delete a specified resource."""
def soap_GetTransferProgress(self, *args, **kwargs):
"""Query the progress of a file transfer initiated by
an ImportResource or ExportResource action."""


(ResourceURI) = args
(TransferID, TransferStatus, TransferLength, TransferTotal) = args


log.msg('DeleteResource(ResourceURI=%s)' % `ResourceURI`)
log.msg('GetTransferProgress(TransferID=%s, TransferStatus=%s, ' \
'TransferLength=%s, TransferTotal=%s)' %
(`TransferId`, `TransferStatus`, `TransferLength`,
`TransferTotal`))


def soap_CreateReference(self, *args, **kwargs):
"""Create a reference to an existing object."""
def soap_DeleteResource(self, *args, **kwargs):
"""Delete a specified resource."""


(ContainerID, ObjectID) = args
(ResourceURI) = args


log.msg('CreateReference(ContainerID=%s, ObjectID=%s)' %
(`ContainerID`, `ObjectID`))
log.msg('DeleteResource(ResourceURI=%s)' % `ResourceURI`)

def soap_CreateReference(self, *args, **kwargs):
"""Create a reference to an existing object."""

(ContainerID, ObjectID) = args

log.msg('CreateReference(ContainerID=%s, ObjectID=%s)' %
(`ContainerID`, `ObjectID`))


class ContentDirectoryServer(resource.Resource): class ContentDirectoryServer(resource.Resource):
def __init__(self, title):
resource.Resource.__init__(self)
self.putChild('scpd.xml', static.File('content-directory-scpd.xml'))
self.control = ContentDirectoryControl(title)
self.putChild('control', self.control)
def __init__(self, title):
resource.Resource.__init__(self)
self.putChild('scpd.xml', static.File('content-directory-scpd.xml'))
self.control = ContentDirectoryControl(title)
self.putChild('control', self.control)

+ 213
- 213
DIDLLite.py View File

@@ -6,346 +6,346 @@
from elementtree.ElementTree import Element, SubElement, tostring, _ElementInterface from elementtree.ElementTree import Element, SubElement, tostring, _ElementInterface


class Resource: class Resource:
"""An object representing a resource."""
"""An object representing a resource."""


def __init__(self, data, protocolInfo):
self.data = data
self.protocolInfo = protocolInfo
self.bitrate = None
self.size = None
def toElement(self):
def __init__(self, data, protocolInfo):
self.data = data
self.protocolInfo = protocolInfo
self.bitrate = None
self.size = None


root = Element('res')
root.attrib['protocolInfo'] = self.protocolInfo
root.text = self.data
def toElement(self):


if self.bitrate is not None:
root.attrib['bitrate'] = str(self.bitrate)
root = Element('res')
root.attrib['protocolInfo'] = self.protocolInfo
root.text = self.data


if self.size is not None:
root.attrib['size'] = str(self.size)
if self.bitrate is not None:
root.attrib['bitrate'] = str(self.bitrate)


return root
if self.size is not None:
root.attrib['size'] = str(self.size)

return root


class Object: class Object:
"""The root class of the entire content directory class heirachy."""
"""The root class of the entire content directory class heirachy."""

klass = 'object'
creator = None
res = None
writeStatus = None


klass = 'object'
creator = None
res = None
writeStatus = None
def __init__(self, cd, id, parentID, title, restricted = False,
creator = None):


def __init__(self, cd, id, parentID, title, restricted = False,
creator = None):
self.cd = cd
self.id = id
self.parentID = parentID
self.title = title
self.creator = creator


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 restricted:
self.restricted = '1'
else:
self.restricted = '0'


def toElement(self):
def toElement(self):


root = Element(self.elementName)
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['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
root.attrib['restricted'] = self.restricted


if self.creator is not None:
SubElement(root, 'dc:creator').text = self.creator
if self.creator is not None:
SubElement(root, 'dc:creator').text = self.creator


if self.res is not None:
root.append(self.res.toElement())
if self.res is not None:
root.append(self.res.toElement())


if self.writeStatus is not None:
SubElement(root, 'upnp:writeStatus').text = self.writeStatus
if self.writeStatus is not None:
SubElement(root, 'upnp:writeStatus').text = self.writeStatus


return root
def toString(self):
return tostring(self.toElement())
return root
def toString(self):
return tostring(self.toElement())


class Item(Object): class Item(Object):
"""A class used to represent atomic (non-container) content
objects."""
"""A class used to represent atomic (non-container) content
objects."""


klass = Object.klass + '.item'
elementName = 'item'
refID = None
klass = Object.klass + '.item'
elementName = 'item'
refID = None


def toElement(self):
def toElement(self):


root = Object.toElement(self)
root = Object.toElement(self)


if self.refID is not None:
SubElement(root, 'refID').text = self.refID
if self.refID is not None:
SubElement(root, 'refID').text = self.refID


return root
return root


class ImageItem(Item): class ImageItem(Item):
klass = Item.klass + '.imageItem'
klass = Item.klass + '.imageItem'


class Photo(ImageItem): class Photo(ImageItem):
klass = ImageItem.klass + '.photo'
klass = ImageItem.klass + '.photo'


class AudioItem(Item): class AudioItem(Item):
"""A piece of content that when rendered generates some audio."""
"""A piece of content that when rendered generates some audio."""

klass = Item.klass + '.audioItem'


klass = Item.klass + '.audioItem'
genre = None
description = None
longDescription = None
publisher = None
language = None
relation = None
rights = None


genre = None
description = None
longDescription = None
publisher = None
language = None
relation = None
rights = None
def toElement(self):


def toElement(self):
root = Item.toElement(self)


root = Item.toElement(self)
if self.genre is not None:
SubElement(root, 'upnp:genre').text = self.genre
if self.genre is not None:
SubElement(root, 'upnp:genre').text = self.genre


if self.description is not None:
SubElement(root, 'dc:description').text = self.description
if self.description is not None:
SubElement(root, 'dc:description').text = self.description


if self.longDescription is not None:
SubElement(root, 'upnp:longDescription').text = \
self.longDescription
if self.longDescription is not None:
SubElement(root, 'upnp:longDescription').text = \
self.longDescription


if self.publisher is not None:
SubElement(root, 'dc:publisher').text = self.publisher
if self.publisher is not None:
SubElement(root, 'dc:publisher').text = self.publisher


if self.language is not None:
SubElement(root, 'dc:language').text = self.language
if self.language is not None:
SubElement(root, 'dc:language').text = self.language


if self.relation is not None:
SubElement(root, 'dc:relation').text = self.relation
if self.relation is not None:
SubElement(root, 'dc:relation').text = self.relation


if self.rights is not None:
SubElement(root, 'dc:rights').text = self.rights
if self.rights is not None:
SubElement(root, 'dc:rights').text = self.rights


return root
return root


class MusicTrack(AudioItem): class MusicTrack(AudioItem):
"""A discrete piece of audio that should be interpreted as music."""
"""A discrete piece of audio that should be interpreted as music."""


klass = AudioItem.klass + '.musicTrack'
klass = AudioItem.klass + '.musicTrack'


artist = None
album = None
originalTrackNumber = None
playlist = None
storageMedium = None
contributor = None
date = None
artist = None
album = None
originalTrackNumber = None
playlist = None
storageMedium = None
contributor = None
date = None


def toElement(self):
def toElement(self):


root = AudioItem.toElement(self)
root = AudioItem.toElement(self)


if self.artist is not None:
SubElement(root, 'upnp:artist').text = self.artist
if self.artist is not None:
SubElement(root, 'upnp:artist').text = self.artist


if self.album is not None:
SubElement(root, 'upnp:album').text = self.album
if self.album is not None:
SubElement(root, 'upnp:album').text = self.album


if self.originalTrackNumber is not None:
SubElement(root, 'upnp:originalTrackNumber').text = \
self.originalTrackNumber
if self.originalTrackNumber is not None:
SubElement(root, 'upnp:originalTrackNumber').text = \
self.originalTrackNumber


if self.playlist is not None:
SubElement(root, 'upnp:playlist').text = self.playlist
if self.playlist is not None:
SubElement(root, 'upnp:playlist').text = self.playlist


if self.storageMedium is not None:
SubElement(root, 'upnp:storageMedium').text = self.storageMedium
if self.storageMedium is not None:
SubElement(root, 'upnp:storageMedium').text = self.storageMedium


if self.contributor is not None:
SubElement(root, 'dc:contributor').text = self.contributor
if self.contributor is not None:
SubElement(root, 'dc:contributor').text = self.contributor


if self.date is not None:
SubElement(root, 'dc:date').text = self.date
if self.date is not None:
SubElement(root, 'dc:date').text = self.date


return root
return root


class AudioBroadcast(AudioItem): class AudioBroadcast(AudioItem):
klass = AudioItem.klass + '.audioBroadcast'
klass = AudioItem.klass + '.audioBroadcast'


class AudioBook(AudioItem): class AudioBook(AudioItem):
klass = AudioItem.klass + '.audioBook'
klass = AudioItem.klass + '.audioBook'


class VideoItem(Item): class VideoItem(Item):
klass = Item.klass + '.videoItem'
klass = Item.klass + '.videoItem'


class Movie(VideoItem): class Movie(VideoItem):
klass = VideoItem.klass + '.movie'
klass = VideoItem.klass + '.movie'


class VideoBroadcast(VideoItem): class VideoBroadcast(VideoItem):
klass = VideoItem.klass + '.videoBroadcast'
klass = VideoItem.klass + '.videoBroadcast'


class MusicVideoClip(VideoItem): class MusicVideoClip(VideoItem):
klass = VideoItem.klass + '.musicVideoClip'
klass = VideoItem.klass + '.musicVideoClip'


class PlaylistItem(Item): class PlaylistItem(Item):
klass = Item.klass + '.playlistItem'
klass = Item.klass + '.playlistItem'


class TextItem(Item): class TextItem(Item):
klass = Item.klass + '.textItem'
klass = Item.klass + '.textItem'


class Container(Object, list): class Container(Object, list):
"""An object that can contain other objects."""
"""An object that can contain other objects."""


klass = Object.klass + '.container'
klass = Object.klass + '.container'


elementName = 'container'
childCount = property(lambda x: len(x))
createClass = None
searchClass = None
searchable = None
elementName = 'container'
childCount = property(lambda x: len(x))
createClass = None
searchClass = None
searchable = None


def __init__(self, cd, id, parentID, title, restricted = 0, creator = None):
Object.__init__(self, cd, id, parentID, title, restricted, creator)
def __init__(self, cd, id, parentID, title, restricted = 0, creator = None):
Object.__init__(self, cd, id, parentID, title, restricted, creator)
list.__init__(self) list.__init__(self)


def toElement(self):
def toElement(self):


root = Object.toElement(self)
root = Object.toElement(self)


root.attrib['childCount'] = str(self.childCount)
root.attrib['childCount'] = str(self.childCount)


if self.createClass is not None:
SubElement(root, 'upnp:createclass').text = self.createClass
if self.searchClass is not None:
if not isinstance(self.searchClass, (list, tuple)):
self.searchClass = ['searchClass']
for i in searchClass:
SubElement(root, 'upnp:searchclass').text = i
if self.createClass is not None:
SubElement(root, 'upnp:createclass').text = self.createClass


if self.searchable is not None:
root.attrib['searchable'] = str(self.searchable)
if self.searchClass is not None:
if not isinstance(self.searchClass, (list, tuple)):
self.searchClass = ['searchClass']
for i in searchClass:
SubElement(root, 'upnp:searchclass').text = i


return root
if self.searchable is not None:
root.attrib['searchable'] = str(self.searchable)

return root


class Person(Container): class Person(Container):
klass = Container.klass + '.person'
klass = Container.klass + '.person'


class MusicArtist(Person): class MusicArtist(Person):
klass = Person.klass + '.musicArtist'
klass = Person.klass + '.musicArtist'


class PlaylistContainer(Container): class PlaylistContainer(Container):
klass = Container.klass + '.playlistContainer'
klass = Container.klass + '.playlistContainer'


class Album(Container): class Album(Container):
klass = Container.klass + '.album'
klass = Container.klass + '.album'


class MusicAlbum(Album): class MusicAlbum(Album):
klass = Album.klass + '.musicAlbum'
klass = Album.klass + '.musicAlbum'


class PhotoAlbum(Album): class PhotoAlbum(Album):
klass = Album.klass + '.photoAlbum'
klass = Album.klass + '.photoAlbum'


class Genre(Container): class Genre(Container):
klass = Container.klass + '.genre'
klass = Container.klass + '.genre'


class MusicGenre(Genre): class MusicGenre(Genre):
klass = Genre.klass + '.musicGenre'
klass = Genre.klass + '.musicGenre'


class MovieGenre(Genre): class MovieGenre(Genre):
klass = Genre.klass + '.movieGenre'
klass = Genre.klass + '.movieGenre'


class StorageSystem(Container): class StorageSystem(Container):
klass = Container.klass + '.storageSystem'
klass = Container.klass + '.storageSystem'

total = -1
used = -1
free = -1
maxpartition = -1
medium = 'UNKNOWN'


total = -1
used = -1
free = -1
maxpartition = -1
medium = 'UNKNOWN'
def toElement(self):


def toElement(self):
root = Container.toElement(self)


root = Container.toElement(self)
SubElement(root, 'upnp:storageTotal').text = str(self.total)
SubElement(root, 'upnp:storageUsed').text = str(self.used)
SubElement(root, 'upnp:storageFree').text = str(self.free)
SubElement(root, 'upnp:storageMaxPartition').text = str(self.maxpartition)
SubElement(root, 'upnp:storageMedium').text = self.medium
SubElement(root, 'upnp:storageTotal').text = str(self.total)
SubElement(root, 'upnp:storageUsed').text = str(self.used)
SubElement(root, 'upnp:storageFree').text = str(self.free)
SubElement(root, 'upnp:storageMaxPartition').text = str(self.maxpartition)
SubElement(root, 'upnp:storageMedium').text = self.medium


return root
return root


class StorageVolume(Container): class StorageVolume(Container):
klass = Container.klass + '.storageVolume'
klass = Container.klass + '.storageVolume'


total = -1
used = -1
free = -1
medium = 'UNKNOWN'
total = -1
used = -1
free = -1
medium = 'UNKNOWN'


def toElement(self):
def toElement(self):


root = Container.toElement(self)
SubElement(root, 'upnp:storageTotal').text = str(self.total)
SubElement(root, 'upnp:storageUsed').text = str(self.used)
SubElement(root, 'upnp:storageFree').text = str(self.free)
SubElement(root, 'upnp:storageMedium').text = self.medium
root = Container.toElement(self)


return root
SubElement(root, 'upnp:storageTotal').text = str(self.total)
SubElement(root, 'upnp:storageUsed').text = str(self.used)
SubElement(root, 'upnp:storageFree').text = str(self.free)
SubElement(root, 'upnp:storageMedium').text = self.medium

return root


class StorageFolder(Container): class StorageFolder(Container):
klass = Container.klass + '.storageFolder'
klass = Container.klass + '.storageFolder'

used = -1


used = -1
def toElement(self):


def toElement(self):
root = Container.toElement(self)


root = Container.toElement(self)
if self.used is not None:
SubElement(root, 'upnp:storageUsed').text = str(self.used)
if self.used is not None:
SubElement(root, 'upnp:storageUsed').text = str(self.used)


return root
return root


class DIDLElement(_ElementInterface): class DIDLElement(_ElementInterface):
def __init__(self):
_ElementInterface.__init__(self, '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 __init__(self):
_ElementInterface.__init__(self, '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 addContainer(self, id, parentID, title, restricted = False):
e = Container(id, parentID, title, restricted, creator = '')
self.append(e.toElement())
def addContainer(self, id, parentID, title, restricted = False):
e = Container(id, parentID, title, restricted, creator = '')
self.append(e.toElement())


def addItem(self, item):
self.append(item.toElement())
def addItem(self, item):
self.append(item.toElement())


def numItems(self):
return len(self)
def numItems(self):
return len(self)


def toString(self):
return tostring(self)
def toString(self):
return tostring(self)


if __name__ == '__main__': if __name__ == '__main__':


root = DIDLElement()
root.addContainer('0\Movie\\', '0\\', 'Movie')
root.addContainer('0\Music\\', '0\\', 'Music')
root.addContainer('0\Photo\\', '0\\', 'Photo')
root.addContainer('0\OnlineMedia\\', '0\\', 'OnlineMedia')
root = DIDLElement()
root.addContainer('0\Movie\\', '0\\', 'Movie')
root.addContainer('0\Music\\', '0\\', 'Music')
root.addContainer('0\Photo\\', '0\\', 'Photo')
root.addContainer('0\OnlineMedia\\', '0\\', 'OnlineMedia')


print tostring(root)
print tostring(root)

+ 148
- 160
SSDP.py View File

@@ -26,167 +26,155 @@ SSDP_ADDR = '239.255.255.250'
# class to handle services etc. # class to handle services etc.


class SSDPServer(DatagramProtocol): 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."""
"""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 = {}
# not used yet
stdheaders = [ ('Server', 'Twisted, UPnP/1.0, python-upnp'), ]
elements = {}
known = {}


def doStop(self):
def doStop(self):
'''Make sure we send out the byebye notifications.''' '''Make sure we send out the byebye notifications.'''


self.transport.write('foobar', (SSDP_ADDR, SSDP_PORT))
for st in self.known:
self.doByebye(st)
DatagramProtocol.doStop(self)

def datagramReceived(self, data, (host, port)):
"""Handle a received multicast datagram."""

# Break up message in to command and headers
# TODO: use the email module after trimming off the request line..
# This gets us much better header support.

header, payload = data.split('\r\n\r\n')
lines = header.split('\r\n')
cmd = string.split(lines[0], ' ')
lines = map(lambda x: x.replace(': ', ':', 1), lines[1:])
lines = filter(lambda x: len(x) > 0, lines)

headers = [string.split(x, ':', 1) for x in lines]
headers = dict(map(lambda x: (x[0].lower(), x[1]), 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, (host, port)):
"""Process a discovery request. The response must be sent to
the address specified by (host, port)."""

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.iteritems())
hcopy['st'] = i
self.discoveryRequest(hcopy, (host, post))
return
if not self.known.has_key(headers['st']):
return

# 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,
'\r\n'.join(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=1800'
self.doNotify(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].iteritems())
stcpy['NT'] = stcpy['ST']
del stcpy['ST']
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems()))
resp.extend(('', ''))
self.transport.write('\r\n'.join(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].iteritems())
stcpy['NT'] = stcpy['ST']
del stcpy['ST']
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems()))
resp.extend(('', ''))
self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT))

def notifyReceived(self, headers, (host, port)):
"""Process a presence announcement. We just remember the
details of the SSDP service announced."""

if headers['nts'] == 'ssdp:alive':

if not self.elements.has_key(headers['nt']):

# 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'])

elif headers['nts'] == 'ssdp:byebye':

if self.elements.has_key(headers['nt']):

# 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.
self.transport.write('foobar', (SSDP_ADDR, SSDP_PORT))
for st in self.known:
self.doByebye(st)
DatagramProtocol.doStop(self)

def datagramReceived(self, data, (host, port)):
"""Handle a received multicast datagram."""

# Break up message in to command and headers
# TODO: use the email module after trimming off the request line..
# This gets us much better header support.

header, payload = data.split('\r\n\r\n')
lines = header.split('\r\n')
cmd = string.split(lines[0], ' ')
lines = map(lambda x: x.replace(': ', ':', 1), lines[1:])
lines = filter(lambda x: len(x) > 0, lines)

headers = [string.split(x, ':', 1) for x in lines]
headers = dict(map(lambda x: (x[0].lower(), x[1]), 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, (host, port)):
"""Process a discovery request. The response must be sent to
the address specified by (host, port)."""

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.iteritems())
hcopy['st'] = i
self.discoveryRequest(hcopy, (host, post))
return
if not self.known.has_key(headers['st']):
return

# 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,
'\r\n'.join(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=1800'
self.doNotify(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].iteritems())
stcpy['NT'] = stcpy['ST']
del stcpy['ST']
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems()))
resp.extend(('', ''))
self.transport.write('\r\n'.join(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].iteritems())
stcpy['NT'] = stcpy['ST']
del stcpy['ST']
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems()))
resp.extend(('', ''))
self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT))

def notifyReceived(self, headers, (host, port)):
"""Process a presence announcement. We just remember the
details of the SSDP service announced."""

if headers['nts'] == 'ssdp:alive':
if not self.elements.has_key(headers['nt']):
# 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'])
elif headers['nts'] == 'ssdp:byebye':
if self.elements.has_key(headers['nt']):
# 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.

+ 9
- 9
browse-zms.py View File

@@ -12,8 +12,8 @@ from twisted.internet import reactor, error
from twisted.internet.protocol import Protocol, ClientFactory from twisted.internet.protocol import Protocol, ClientFactory


class Send(Protocol): class Send(Protocol):
def connectionMade(self):
self.transport.write('''POST /ContentDirectory/control HTTP/1.1\r
def connectionMade(self):
self.transport.write('''POST /ContentDirectory/control HTTP/1.1\r
Host: 192.168.126.1:80\r Host: 192.168.126.1:80\r
User-Agent: POSIX, UPnP/1.0, Intel MicroStack/1.0.1423\r User-Agent: POSIX, UPnP/1.0, Intel MicroStack/1.0.1423\r
SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"\r SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"\r
@@ -35,16 +35,16 @@ Content-Length: 511\r
</s:Body>\r </s:Body>\r
</s:Envelope>\r\n''') </s:Envelope>\r\n''')


def dataReceived(self, data):
print(data)
def dataReceived(self, data):
print(data)


def connectionLost(self, reason):
if reason.type != error.ConnectionDone:
print str(reason)
reactor.stop()
def connectionLost(self, reason):
if reason.type != error.ConnectionDone:
print str(reason)
reactor.stop()


class SendFactory(ClientFactory): class SendFactory(ClientFactory):
protocol = Send
protocol = Send


host = '192.168.126.128' host = '192.168.126.128'
port = 5643 port = 5643


+ 28
- 28
cdsclient View File

@@ -15,38 +15,38 @@ from twisted.python import usage
import sys, string, SOAPpy import sys, string, SOAPpy


class UPnPSOAPProxy: class UPnPSOAPProxy:
"""A proxy for making UPnP SOAP calls."""
"""A proxy for making UPnP SOAP calls."""


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


def _cbGotResult(self, result):
return SOAPpy.parseSOAPRPC(result)
def _cbGotResult(self, result):
return SOAPpy.parseSOAPRPC(result)


def callRemote(self, method, *args, **kwargs):
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})
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)
return page.addCallback(self._cbGotResult)


class Options(usage.Options): class Options(usage.Options):
pass
pass


def printResult(value): def printResult(value):
print value
reactor.stop()
print value
reactor.stop()


def printError(error): def printError(error):
print 'error', error
reactor.stop()
print 'error', error
reactor.stop()


#proxy = UPnPSOAPProxy('http://192.168.126.128:5643/ContentDirectory/control') #proxy = UPnPSOAPProxy('http://192.168.126.128:5643/ContentDirectory/control')
proxy = UPnPSOAPProxy('http://127.0.0.1:8080/ContentDirectory/control') proxy = UPnPSOAPProxy('http://127.0.0.1:8080/ContentDirectory/control')
@@ -56,12 +56,12 @@ proxy = UPnPSOAPProxy('http://127.0.0.1:8080/ContentDirectory/control')
#proxy.callRemote('GetSortCapabilities').addCallbacks(printResult, printError) #proxy.callRemote('GetSortCapabilities').addCallbacks(printResult, printError)


proxy.callRemote('Browse', proxy.callRemote('Browse',
ObjectID = '0\\Music\\Genres\\Others\\chimes.wav',
# BrowseFlag = 'BrowseDirectChildren',
BrowseFlag = 'BrowseMetadata',
Filter = '*',
StartingIndex = 0,
RequestedCount = 700,
SortCriteria = None).addCallbacks(printResult, printError)
ObjectID = '0\\Music\\Genres\\Others\\chimes.wav',
#BrowseFlag = 'BrowseDirectChildren',
BrowseFlag = 'BrowseMetadata',
Filter = '*',
StartingIndex = 0,
RequestedCount = 700,
SortCriteria = None).addCallbacks(printResult, printError)


reactor.run() reactor.run()

+ 5
- 7
upnp.py View File

@@ -8,11 +8,9 @@ from twisted.web import soap
import SOAPpy import SOAPpy


class UPnPPublisher(soap.SOAPPublisher): class UPnPPublisher(soap.SOAPPublisher):
"""UPnP requires OUT parameters to be returned in a slightly
different way than the SOAPPublisher class does."""

def _gotResult(self, result, request, methodName):
response = SOAPpy.buildSOAP(kw=result,
encoding=self.encoding)
self._sendResponse(request, response)
"""UPnP requires OUT parameters to be returned in a slightly
different way than the SOAPPublisher class does."""


def _gotResult(self, result, request, methodName):
response = SOAPpy.buildSOAP(kw=result, encoding=self.encoding)
self._sendResponse(request, response)

+ 1
- 1
xmlpretty View File

@@ -12,7 +12,7 @@ from xml.dom import minidom


str = "" str = ""
for s in sys.stdin.readlines(): for s in sys.stdin.readlines():
str = str + s[:-1] # Eat trailing \n
str = str + s[:-1] # Eat trailing \n


doc = minidom.parseString(str) doc = minidom.parseString(str)
print doc.toprettyxml(indent = " ") print doc.toprettyxml(indent = " ")

Loading…
Cancel
Save