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.

419 lines
9.8 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. needupdate = None # do we update before sending? (for res)
  32. def __init__(self, cd, id, parentID, title, restricted = False,
  33. creator = None, **kwargs):
  34. self.cd = cd
  35. self.id = id
  36. self.parentID = parentID
  37. self.title = title
  38. self.creator = creator
  39. if restricted:
  40. self.restricted = '1'
  41. else:
  42. self.restricted = '0'
  43. if kwargs.has_key('content'):
  44. self._content = kwargs['content']
  45. def __lt__(self, other):
  46. return self.__cmp__(other) < 0
  47. def __le__(self, other):
  48. return self.__cmp__(other) <= 0
  49. def __eq__(self, other):
  50. return self.__cmp__(other) == 0
  51. def __ne__(self, other):
  52. return self.__cmp__(other) != 0
  53. def __gt__(self, other):
  54. return self.__cmp__(other) > 0
  55. def __ge__(self, other):
  56. return self.__cmp__(other) >= 0
  57. def __cmp__(self, other):
  58. if not isinstance(other, self.__class__):
  59. return 1
  60. return cmp(self.id, other.id)
  61. def __repr__(self):
  62. cls = self.__class__
  63. return '<%s.%s: id: %s, parent: %s, title: %s>' % \
  64. (cls.__module__, cls.__name__, self.id, self.parentID,
  65. self.title)
  66. def checkUpdate(self):
  67. return self
  68. def toElement(self):
  69. root = Element(self.elementName)
  70. root.attrib['id'] = self.id
  71. root.attrib['parentID'] = self.parentID
  72. SubElement(root, 'dc:title').text = self.title
  73. SubElement(root, 'upnp:class').text = self.klass
  74. root.attrib['restricted'] = self.restricted
  75. if self.creator is not None:
  76. SubElement(root, 'dc:creator').text = self.creator
  77. if self.res is not None:
  78. try:
  79. for res in iter(self.res):
  80. root.append(res.toElement())
  81. except TypeError:
  82. root.append(self.res.toElement())
  83. if self.writeStatus is not None:
  84. SubElement(root, 'upnp:writeStatus').text = self.writeStatus
  85. return root
  86. def toString(self):
  87. return tostring(self.toElement())
  88. class Item(Object):
  89. """A class used to represent atomic (non-container) content
  90. objects."""
  91. klass = Object.klass + '.item'
  92. elementName = 'item'
  93. refID = None
  94. needupdate = True
  95. def doUpdate(self):
  96. # Update parent container
  97. self.cd[self.parentID].doUpdate()
  98. def toElement(self):
  99. root = Object.toElement(self)
  100. if self.refID is not None:
  101. SubElement(root, 'refID').text = self.refID
  102. return root
  103. class ImageItem(Item):
  104. klass = Item.klass + '.imageItem'
  105. class Photo(ImageItem):
  106. klass = ImageItem.klass + '.photo'
  107. class AudioItem(Item):
  108. """A piece of content that when rendered generates some audio."""
  109. klass = Item.klass + '.audioItem'
  110. genre = None
  111. description = None
  112. longDescription = None
  113. publisher = None
  114. language = None
  115. relation = None
  116. rights = None
  117. def toElement(self):
  118. root = Item.toElement(self)
  119. if self.genre is not None:
  120. SubElement(root, 'upnp:genre').text = self.genre
  121. if self.description is not None:
  122. SubElement(root, 'dc:description').text = self.description
  123. if self.longDescription is not None:
  124. SubElement(root, 'upnp:longDescription').text = \
  125. self.longDescription
  126. if self.publisher is not None:
  127. SubElement(root, 'dc:publisher').text = self.publisher
  128. if self.language is not None:
  129. SubElement(root, 'dc:language').text = self.language
  130. if self.relation is not None:
  131. SubElement(root, 'dc:relation').text = self.relation
  132. if self.rights is not None:
  133. SubElement(root, 'dc:rights').text = self.rights
  134. return root
  135. class MusicTrack(AudioItem):
  136. """A discrete piece of audio that should be interpreted as music."""
  137. klass = AudioItem.klass + '.musicTrack'
  138. artist = None
  139. album = None
  140. originalTrackNumber = None
  141. playlist = None
  142. storageMedium = None
  143. contributor = None
  144. date = None
  145. def toElement(self):
  146. root = AudioItem.toElement(self)
  147. if self.artist is not None:
  148. SubElement(root, 'upnp:artist').text = self.artist
  149. if self.album is not None:
  150. SubElement(root, 'upnp:album').text = self.album
  151. if self.originalTrackNumber is not None:
  152. SubElement(root, 'upnp:originalTrackNumber').text = \
  153. self.originalTrackNumber
  154. if self.playlist is not None:
  155. SubElement(root, 'upnp:playlist').text = self.playlist
  156. if self.storageMedium is not None:
  157. SubElement(root, 'upnp:storageMedium').text = self.storageMedium
  158. if self.contributor is not None:
  159. SubElement(root, 'dc:contributor').text = self.contributor
  160. if self.date is not None:
  161. SubElement(root, 'dc:date').text = self.date
  162. return root
  163. class AudioBroadcast(AudioItem):
  164. klass = AudioItem.klass + '.audioBroadcast'
  165. class AudioBook(AudioItem):
  166. klass = AudioItem.klass + '.audioBook'
  167. class VideoItem(Item):
  168. klass = Item.klass + '.videoItem'
  169. class Movie(VideoItem):
  170. klass = VideoItem.klass + '.movie'
  171. class VideoBroadcast(VideoItem):
  172. klass = VideoItem.klass + '.videoBroadcast'
  173. class MusicVideoClip(VideoItem):
  174. klass = VideoItem.klass + '.musicVideoClip'
  175. class PlaylistItem(Item):
  176. klass = Item.klass + '.playlistItem'
  177. class TextItem(Item):
  178. klass = Item.klass + '.textItem'
  179. class Container(Object, list):
  180. """An object that can contain other objects."""
  181. klass = Object.klass + '.container'
  182. elementName = 'container'
  183. childCount = property(lambda x: len(x))
  184. createClass = None
  185. searchClass = None
  186. searchable = None
  187. updateID = 0
  188. needupdate = False
  189. def __init__(self, cd, id, parentID, title, restricted = 0,
  190. creator = None, **kwargs):
  191. Object.__init__(self, cd, id, parentID, title, restricted,
  192. creator, **kwargs)
  193. list.__init__(self)
  194. def doUpdate(self):
  195. self.updateID = (self.updateID + 1) % (1l << 32)
  196. self.cd['0'].doUpdate()
  197. def toElement(self):
  198. root = Object.toElement(self)
  199. # only include if we have children, it's possible we don't
  200. # have our children yet, and childCount is optional.
  201. if self.childCount:
  202. root.attrib['childCount'] = str(self.childCount)
  203. if self.createClass is not None:
  204. SubElement(root, 'upnp:createclass').text = self.createClass
  205. if self.searchClass is not None:
  206. if not isinstance(self.searchClass, (list, tuple)):
  207. self.searchClass = ['searchClass']
  208. for i in searchClass:
  209. SubElement(root, 'upnp:searchclass').text = i
  210. if self.searchable is not None:
  211. root.attrib['searchable'] = str(self.searchable)
  212. return root
  213. def __repr__(self):
  214. cls = self.__class__
  215. return '<%s.%s: id: %s, parent: %s, title: %s, cnt: %d>' % \
  216. (cls.__module__, cls.__name__, self.id, self.parentID,
  217. self.title, len(self))
  218. class Person(Container):
  219. klass = Container.klass + '.person'
  220. class MusicArtist(Person):
  221. klass = Person.klass + '.musicArtist'
  222. class PlaylistContainer(Container):
  223. klass = Container.klass + '.playlistContainer'
  224. class Album(Container):
  225. klass = Container.klass + '.album'
  226. class MusicAlbum(Album):
  227. klass = Album.klass + '.musicAlbum'
  228. class PhotoAlbum(Album):
  229. klass = Album.klass + '.photoAlbum'
  230. class Genre(Container):
  231. klass = Container.klass + '.genre'
  232. class MusicGenre(Genre):
  233. klass = Genre.klass + '.musicGenre'
  234. class MovieGenre(Genre):
  235. klass = Genre.klass + '.movieGenre'
  236. class StorageSystem(Container):
  237. klass = Container.klass + '.storageSystem'
  238. total = -1
  239. used = -1
  240. free = -1
  241. maxpartition = -1
  242. medium = 'UNKNOWN'
  243. def toElement(self):
  244. root = Container.toElement(self)
  245. SubElement(root, 'upnp:storageTotal').text = str(self.total)
  246. SubElement(root, 'upnp:storageUsed').text = str(self.used)
  247. SubElement(root, 'upnp:storageFree').text = str(self.free)
  248. SubElement(root, 'upnp:storageMaxPartition').text = str(self.maxpartition)
  249. SubElement(root, 'upnp:storageMedium').text = self.medium
  250. return root
  251. class StorageVolume(Container):
  252. klass = Container.klass + '.storageVolume'
  253. total = -1
  254. used = -1
  255. free = -1
  256. medium = 'UNKNOWN'
  257. def toElement(self):
  258. root = Container.toElement(self)
  259. SubElement(root, 'upnp:storageTotal').text = str(self.total)
  260. SubElement(root, 'upnp:storageUsed').text = str(self.used)
  261. SubElement(root, 'upnp:storageFree').text = str(self.free)
  262. SubElement(root, 'upnp:storageMedium').text = self.medium
  263. return root
  264. class StorageFolder(Container):
  265. klass = Container.klass + '.storageFolder'
  266. used = -1
  267. def toElement(self):
  268. root = Container.toElement(self)
  269. if self.used is not None:
  270. SubElement(root, 'upnp:storageUsed').text = str(self.used)
  271. return root
  272. class DIDLElement(_ElementInterface):
  273. def __init__(self):
  274. _ElementInterface.__init__(self, 'DIDL-Lite', {})
  275. self.attrib['xmlns'] = 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite'
  276. self.attrib['xmlns:dc'] = 'http://purl.org/dc/elements/1.1/'
  277. self.attrib['xmlns:upnp'] = 'urn:schemas-upnp-org:metadata-1-0/upnp'
  278. def addContainer(self, id, parentID, title, restricted = False):
  279. e = Container(id, parentID, title, restricted, creator = '')
  280. self.append(e.toElement())
  281. def addItem(self, item):
  282. self.append(item.toElement())
  283. def numItems(self):
  284. return len(self)
  285. def toString(self):
  286. return tostring(self)
  287. if __name__ == '__main__':
  288. root = DIDLElement()
  289. root.addContainer('0\Movie\\', '0\\', 'Movie')
  290. root.addContainer('0\Music\\', '0\\', 'Music')
  291. root.addContainer('0\Photo\\', '0\\', 'Photo')
  292. root.addContainer('0\OnlineMedia\\', '0\\', 'OnlineMedia')
  293. print tostring(root)