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.

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