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.

367 lines
8.5 KiB

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