# Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2005, Tim Potter # Copyright 2006-2009 John-Mark Gurney __version__ = '$Change$' # $Id$ from elementtree.ElementTree import Element, SubElement, tostring, _ElementInterface 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: root.attrib[self.validattrs[i]] = str(self.attrs[i]) return root 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 self._mt.has_key(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' creator = None res = None writeStatus = 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 kwargs.has_key('content'): self._content = kwargs['content'] 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): 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 if self.creator is not None: SubElement(root, 'dc:creator').text = self.creator if self.res is not None: try: for res in iter(self.res): root.append(res.toElement()) except TypeError: root.append(self.res.toElement()) if self.writeStatus is not None: SubElement(root, 'upnp:writeStatus').text = self.writeStatus 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): # Update parent container Container.doUpdate(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' genre = None description = None longDescription = None publisher = None language = None relation = None rights = None def toElement(self): root = Item.toElement(self) 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.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.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.rights is not None: SubElement(root, 'dc:rights').text = self.rights return root class MusicTrack(AudioItem): """A discrete piece of audio that should be interpreted as music.""" klass = AudioItem.klass + '.musicTrack' artist = None album = None originalTrackNumber = None playlist = None storageMedium = None contributor = None date = None def toElement(self): root = AudioItem.toElement(self) 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.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.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.date is not None: SubElement(root, 'dc:date').text = self.date return root 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)) createClass = None searchClass = None searchable = None updateID = 0 needupdate = False def __init__(self, cd, id, parentID, title, **kwargs): Object.__init__(self, cd, id, parentID, title, **kwargs) list.__init__(self) self.doingUpdate = 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 for new 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 None: return list.sort(self, lambda x, y: cmp(x.title, y.title)) return list.sort(self, fun) def doUpdate(self): if self.doingUpdate: return self.doingUpdate = True didupdate = False children = self.genChildren() if isinstance(children, dict): oldchildren = self.oldchildren self.oldchildren = children isdict = True else: children = set(children) isdict = False names = {} #print 'i:', `self`, `self.genCurrent`, `self.__class__` for id, i in tuple(self.genCurrent()): if i not in children: didupdate = True # delete self.cd.delItem(id) else: names[i] = id for i in children: if i in names: if isdict: if oldchildren[i] == children[i]: continue self.cd.delItem(names[i]) else: # XXX - some sort of comparision? continue # new object if isdict: args = (children[i], ) else: args = () #print 'i:', `i`, `isdict`, `args`, `self` klass, name, args, kwargs = self.createObject(i, *args) if klass is not None: self.cd.addItem(self.id, klass, name, *args, **kwargs) didupdate = True # sort our children self.sort() if didupdate: self.didUpdate() self.doingUpdate = False def didUpdate(self): if self.id == '0': self.updateID = (self.updateID + 1) else: self.updateID = (self.updateID + 1) % (1l << 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) self._addSet('upnp:createclass', self.createClass) self._addSet('upnp:searchclass', self.searchClass) 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 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' total = -1 used = -1 free = -1 maxpartition = -1 medium = 'UNKNOWN' 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:storageMaxPartition').text = str(self.maxpartition) SubElement(root, 'upnp:storageMedium').text = self.medium return root class StorageVolume(Container): klass = Container.klass + '.storageVolume' total = -1 used = -1 free = -1 medium = 'UNKNOWN' 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 return root class StorageFolder(Container): klass = Container.klass + '.storageFolder' used = -1 def toElement(self): root = Container.toElement(self) if self.used is not None: SubElement(root, 'upnp:storageUsed').text = str(self.used) 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 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)