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.

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