Browse Source

switch to using tabs instead of spaces...

[git-p4: depot-paths = "//depot/": change = 763]
replace/4e84fdb41ea781c7a8f872baa423e8b3be4045a7
John-Mark Gurney 18 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

class ConnectionManagerControl(UPnPPublisher):
pass
pass

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:
#
# try:
# ....
# ....
# except:
# traceback.print_exc(file = log.logfile)
# traceback.print_exc(file = log.logfile)
#

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

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):
for i in self.children[id]:
self.delItem(i)
assert len(self.children[id]) == 0
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):
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

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:
"""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):
"""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):
klass = Item.klass + '.imageItem'
klass = Item.klass + '.imageItem'

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

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):
"""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):
klass = AudioItem.klass + '.audioBroadcast'
klass = AudioItem.klass + '.audioBroadcast'

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

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

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

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

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

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

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

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)

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):
klass = Container.klass + '.person'
klass = Container.klass + '.person'

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

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

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

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

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

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

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

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

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):
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):
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):
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__':

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 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.'''

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

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
User-Agent: POSIX, UPnP/1.0, Intel MicroStack/1.0.1423\r
SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"\r
@@ -35,16 +35,16 @@ Content-Length: 511\r
</s:Body>\r
</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):
protocol = Send
protocol = Send

host = '192.168.126.128'
port = 5643


+ 28
- 28
cdsclient View File

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

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):
pass
pass

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

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://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('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()

+ 5
- 7
upnp.py View File

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

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 = ""
for s in sys.stdin.readlines():
str = str + s[:-1] # Eat trailing \n
str = str + s[:-1] # Eat trailing \n

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

Loading…
Cancel
Save