A Python UPnP Media Server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

390 lines
9.2 KiB

  1. # Licensed under the MIT license
  2. # http://opensource.org/licenses/mit-license.php
  3. # Copyright 2005, Tim Potter <tpot@samba.org>
  4. # Copyright 2006 John-Mark Gurney <gurney_j@resnet.uoregon.edu>
  5. __version__ = '$Change$'
  6. # $Id$
  7. from elementtree.ElementTree import Element, SubElement, tostring, _ElementInterface
  8. class Resource:
  9. """An object representing a resource."""
  10. def __init__(self, data, protocolInfo):
  11. self.data = data
  12. self.protocolInfo = protocolInfo
  13. self.bitrate = None
  14. self.size = None
  15. def toElement(self):
  16. root = Element('res')
  17. root.attrib['protocolInfo'] = self.protocolInfo
  18. root.text = self.data
  19. if self.bitrate is not None:
  20. root.attrib['bitrate'] = str(self.bitrate)
  21. if self.size is not None:
  22. root.attrib['size'] = str(self.size)
  23. return root
  24. class Object(object):
  25. """The root class of the entire content directory class heirachy."""
  26. klass = 'object'
  27. creator = None
  28. res = None
  29. writeStatus = None
  30. content = property(lambda x: x._content)
  31. def __init__(self, cd, id, parentID, title, restricted = False,
  32. creator = None, **kwargs):
  33. self.cd = cd
  34. self.id = id
  35. self.parentID = parentID
  36. self.title = title
  37. self.creator = creator
  38. if restricted:
  39. self.restricted = '1'
  40. else:
  41. self.restricted = '0'
  42. if kwargs.has_key('content'):
  43. self._content = kwargs['content']
  44. def __repr__(self):
  45. cls = self.__class__
  46. return '<%s.%s: id: %s, parent: %s, title: %s>' % \
  47. (cls.__module__, cls.__name__, self.id, self.parentID,
  48. self.title)
  49. def checkUpdate(self):
  50. return self
  51. def toElement(self):
  52. root = Element(self.elementName)
  53. root.attrib['id'] = self.id
  54. root.attrib['parentID'] = self.parentID
  55. SubElement(root, 'dc:title').text = self.title
  56. SubElement(root, 'upnp:class').text = self.klass
  57. root.attrib['restricted'] = self.restricted
  58. if self.creator is not None:
  59. SubElement(root, 'dc:creator').text = self.creator
  60. if self.res is not None:
  61. try:
  62. for res in iter(self.res):
  63. root.append(res.toElement())
  64. except TypeError:
  65. root.append(self.res.toElement())
  66. if self.writeStatus is not None:
  67. SubElement(root, 'upnp:writeStatus').text = self.writeStatus
  68. return root
  69. def toString(self):
  70. return tostring(self.toElement())
  71. class Item(Object):
  72. """A class used to represent atomic (non-container) content
  73. objects."""
  74. klass = Object.klass + '.item'
  75. elementName = 'item'
  76. refID = None
  77. def doUpdate(self):
  78. # Update parent container
  79. self.cd[self.parentID].doUpdate()
  80. def toElement(self):
  81. root = Object.toElement(self)
  82. if self.refID is not None:
  83. SubElement(root, 'refID').text = self.refID
  84. return root
  85. class ImageItem(Item):
  86. klass = Item.klass + '.imageItem'
  87. class Photo(ImageItem):
  88. klass = ImageItem.klass + '.photo'
  89. class AudioItem(Item):
  90. """A piece of content that when rendered generates some audio."""
  91. klass = Item.klass + '.audioItem'
  92. genre = None
  93. description = None
  94. longDescription = None
  95. publisher = None
  96. language = None
  97. relation = None
  98. rights = None
  99. def toElement(self):
  100. root = Item.toElement(self)
  101. if self.genre is not None:
  102. SubElement(root, 'upnp:genre').text = self.genre
  103. if self.description is not None:
  104. SubElement(root, 'dc:description').text = self.description
  105. if self.longDescription is not None:
  106. SubElement(root, 'upnp:longDescription').text = \
  107. self.longDescription
  108. if self.publisher is not None:
  109. SubElement(root, 'dc:publisher').text = self.publisher
  110. if self.language is not None:
  111. SubElement(root, 'dc:language').text = self.language
  112. if self.relation is not None:
  113. SubElement(root, 'dc:relation').text = self.relation
  114. if self.rights is not None:
  115. SubElement(root, 'dc:rights').text = self.rights
  116. return root
  117. class MusicTrack(AudioItem):
  118. """A discrete piece of audio that should be interpreted as music."""
  119. klass = AudioItem.klass + '.musicTrack'
  120. artist = None
  121. album = None
  122. originalTrackNumber = None
  123. playlist = None
  124. storageMedium = None
  125. contributor = None
  126. date = None
  127. def toElement(self):
  128. root = AudioItem.toElement(self)
  129. if self.artist is not None:
  130. SubElement(root, 'upnp:artist').text = self.artist
  131. if self.album is not None:
  132. SubElement(root, 'upnp:album').text = self.album
  133. if self.originalTrackNumber is not None:
  134. SubElement(root, 'upnp:originalTrackNumber').text = \
  135. self.originalTrackNumber
  136. if self.playlist is not None:
  137. SubElement(root, 'upnp:playlist').text = self.playlist
  138. if self.storageMedium is not None:
  139. SubElement(root, 'upnp:storageMedium').text = self.storageMedium
  140. if self.contributor is not None:
  141. SubElement(root, 'dc:contributor').text = self.contributor
  142. if self.date is not None:
  143. SubElement(root, 'dc:date').text = self.date
  144. return root
  145. class AudioBroadcast(AudioItem):
  146. klass = AudioItem.klass + '.audioBroadcast'
  147. class AudioBook(AudioItem):
  148. klass = AudioItem.klass + '.audioBook'
  149. class VideoItem(Item):
  150. klass = Item.klass + '.videoItem'
  151. class Movie(VideoItem):
  152. klass = VideoItem.klass + '.movie'
  153. class VideoBroadcast(VideoItem):
  154. klass = VideoItem.klass + '.videoBroadcast'
  155. class MusicVideoClip(VideoItem):
  156. klass = VideoItem.klass + '.musicVideoClip'
  157. class PlaylistItem(Item):
  158. klass = Item.klass + '.playlistItem'
  159. class TextItem(Item):
  160. klass = Item.klass + '.textItem'
  161. class Container(Object, list):
  162. """An object that can contain other objects."""
  163. klass = Object.klass + '.container'
  164. elementName = 'container'
  165. childCount = property(lambda x: len(x))
  166. createClass = None
  167. searchClass = None
  168. searchable = None
  169. updateID = 0
  170. def __init__(self, cd, id, parentID, title, restricted = 0, creator = None):
  171. Object.__init__(self, cd, id, parentID, title, restricted, creator)
  172. list.__init__(self)
  173. def doUpdate(self):
  174. self.updateID = (self.updateID + 1) % (1l << 32)
  175. def toElement(self):
  176. root = Object.toElement(self)
  177. # only include if we have children, it's possible we don't
  178. # have our children yet, and childCount is optional.
  179. if self.childCount:
  180. root.attrib['childCount'] = str(self.childCount)
  181. if self.createClass is not None:
  182. SubElement(root, 'upnp:createclass').text = self.createClass
  183. if self.searchClass is not None:
  184. if not isinstance(self.searchClass, (list, tuple)):
  185. self.searchClass = ['searchClass']
  186. for i in searchClass:
  187. SubElement(root, 'upnp:searchclass').text = i
  188. if self.searchable is not None:
  189. root.attrib['searchable'] = str(self.searchable)
  190. return root
  191. def __repr__(self):
  192. cls = self.__class__
  193. return '<%s.%s: id: %s, parent: %s, title: %s, cnt: %d>' % \
  194. (cls.__module__, cls.__name__, self.id, self.parentID,
  195. self.title, len(self))
  196. class Person(Container):
  197. klass = Container.klass + '.person'
  198. class MusicArtist(Person):
  199. klass = Person.klass + '.musicArtist'
  200. class PlaylistContainer(Container):
  201. klass = Container.klass + '.playlistContainer'
  202. class Album(Container):
  203. klass = Container.klass + '.album'
  204. class MusicAlbum(Album):
  205. klass = Album.klass + '.musicAlbum'
  206. class PhotoAlbum(Album):
  207. klass = Album.klass + '.photoAlbum'
  208. class Genre(Container):
  209. klass = Container.klass + '.genre'
  210. class MusicGenre(Genre):
  211. klass = Genre.klass + '.musicGenre'
  212. class MovieGenre(Genre):
  213. klass = Genre.klass + '.movieGenre'
  214. class StorageSystem(Container):
  215. klass = Container.klass + '.storageSystem'
  216. total = -1
  217. used = -1
  218. free = -1
  219. maxpartition = -1
  220. medium = 'UNKNOWN'
  221. def toElement(self):
  222. root = Container.toElement(self)
  223. SubElement(root, 'upnp:storageTotal').text = str(self.total)
  224. SubElement(root, 'upnp:storageUsed').text = str(self.used)
  225. SubElement(root, 'upnp:storageFree').text = str(self.free)
  226. SubElement(root, 'upnp:storageMaxPartition').text = str(self.maxpartition)
  227. SubElement(root, 'upnp:storageMedium').text = self.medium
  228. return root
  229. class StorageVolume(Container):
  230. klass = Container.klass + '.storageVolume'
  231. total = -1
  232. used = -1
  233. free = -1
  234. medium = 'UNKNOWN'
  235. def toElement(self):
  236. root = Container.toElement(self)
  237. SubElement(root, 'upnp:storageTotal').text = str(self.total)
  238. SubElement(root, 'upnp:storageUsed').text = str(self.used)
  239. SubElement(root, 'upnp:storageFree').text = str(self.free)
  240. SubElement(root, 'upnp:storageMedium').text = self.medium
  241. return root
  242. class StorageFolder(Container):
  243. klass = Container.klass + '.storageFolder'
  244. used = -1
  245. def toElement(self):
  246. root = Container.toElement(self)
  247. if self.used is not None:
  248. SubElement(root, 'upnp:storageUsed').text = str(self.used)
  249. return root
  250. class DIDLElement(_ElementInterface):
  251. def __init__(self):
  252. _ElementInterface.__init__(self, 'DIDL-Lite', {})
  253. self.attrib['xmlns'] = 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite'
  254. self.attrib['xmlns:dc'] = 'http://purl.org/dc/elements/1.1/'
  255. self.attrib['xmlns:upnp'] = 'urn:schemas-upnp-org:metadata-1-0/upnp'
  256. def addContainer(self, id, parentID, title, restricted = False):
  257. e = Container(id, parentID, title, restricted, creator = '')
  258. self.append(e.toElement())
  259. def addItem(self, item):
  260. self.append(item.toElement())
  261. def numItems(self):
  262. return len(self)
  263. def toString(self):
  264. return tostring(self)
  265. if __name__ == '__main__':
  266. root = DIDLElement()
  267. root.addContainer('0\Movie\\', '0\\', 'Movie')
  268. root.addContainer('0\Music\\', '0\\', 'Music')
  269. root.addContainer('0\Photo\\', '0\\', 'Photo')
  270. root.addContainer('0\OnlineMedia\\', '0\\', 'OnlineMedia')
  271. print tostring(root)