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.

156 lines
4.0 KiB

  1. #!/usr/bin/env python
  2. # Licensed under the MIT license
  3. # http://opensource.org/licenses/mit-license.php
  4. # Copyright 2005, Tim Potter <tpot@samba.org>
  5. # Copyright 2006 John-Mark Gurney <gurney_j@resnet.uroegon.edu>
  6. from DIDLLite import TextItem, AudioItem, VideoItem, ImageItem, Resource, StorageFolder
  7. from FSStorage import FSDirectory
  8. import os
  9. import os.path
  10. import random
  11. import socket
  12. import string
  13. import sys
  14. from twisted.python import log
  15. from twisted.internet import reactor
  16. def generateuuid():
  17. if False:
  18. return 'uuid:asdflkjewoifjslkdfj'
  19. return ''.join([ 'uuid:'] + map(lambda x: random.choice(string.letters), xrange(20)))
  20. listenAddr = sys.argv[1]
  21. if len(sys.argv) > 2:
  22. listenPort = int(sys.argv[2])
  23. if listenPort < 1024 or listenPort > 65535:
  24. raise ValueError, 'port out of range'
  25. else:
  26. listenPort = random.randint(10000, 65000)
  27. log.startLogging(sys.stdout)
  28. # Create SSDP server
  29. from SSDP import SSDPServer, SSDP_PORT, SSDP_ADDR
  30. s = SSDPServer()
  31. port = reactor.listenMulticast(SSDP_PORT, s)
  32. port.joinGroup(SSDP_ADDR)
  33. port.setLoopbackMode(0) # don't get our own sends
  34. uuid = generateuuid()
  35. urlbase = 'http://%s:%d/' % (listenAddr, listenPort)
  36. # Create SOAP server
  37. from twisted.web import server, resource, static
  38. from ContentDirectory import ContentDirectoryServer
  39. from ConnectionManager import ConnectionManagerServer
  40. class WebServer(resource.Resource):
  41. def __init__(self):
  42. resource.Resource.__init__(self)
  43. class RootDevice(static.Data):
  44. def __init__(self):
  45. r = {
  46. 'hostname': socket.gethostname(),
  47. 'uuid': uuid,
  48. 'urlbase': urlbase,
  49. }
  50. d = file('root-device.xml').read() % r
  51. static.Data.__init__(self, d, 'text/xml')
  52. root = WebServer()
  53. cds = ContentDirectoryServer('My Media Server')
  54. root.putChild('ContentDirectory', cds)
  55. cds = cds.control
  56. root.putChild('ConnectionManager', ConnectionManagerServer())
  57. root.putChild('root-device.xml', RootDevice())
  58. # Area of server to serve media files from
  59. from MediaServer import MediaServer
  60. medianode = static.File('media')
  61. medianode.contentTypes.update( {
  62. '.wmv': 'video/x-ms-wmv',
  63. #'.ts': 'video/mp2t',
  64. '.ts': 'video/mpeg', # we may want this instead of mp2t
  65. #'.mp4': 'video/mp4',
  66. '.mp4': 'video/mpeg',
  67. '.ogm': 'application/ogg',
  68. '.vob': 'video/mpeg',
  69. })
  70. root.putChild('media', medianode)
  71. # Set up media files
  72. def addFSPath(cds, parent, dpath):
  73. dlist = os.listdir(dpath)
  74. dlist.sort()
  75. for i in dlist:
  76. fpath = os.path.join(dpath, i)
  77. try:
  78. fn, ext = os.path.splitext(i)
  79. ext = ext.lower()
  80. if os.path.isdir(fpath):
  81. folder = cds.addContainer(parent, i, klass = StorageFolder)
  82. addFSPath(cds, folder, fpath)
  83. if not os.path.isfile(fpath):
  84. continue
  85. #if ext == '.ts':
  86. # continue
  87. mt = medianode.contentTypes[ext]
  88. ty = mt.split('/')[0]
  89. if ty == 'video':
  90. klass = VideoItem
  91. elif ty == 'audio':
  92. klass = AudioItem
  93. elif ty == 'text':
  94. klass = TextItem
  95. elif ty == 'image':
  96. klass = ImageItem
  97. else:
  98. raise KeyError, 'no item for mt: %s' % mt
  99. item = cds.addItem(parent, klass, i)
  100. cds[item].res = Resource('%s%s' % (urlbase, fpath), 'http-get:*:%s:*' % mt)
  101. cds[item].res.size = os.path.getsize(fpath)
  102. except KeyError:
  103. pass
  104. cds.addContainer('0', 'media', klass = FSDirectory, path = 'media', urlbase = urlbase)
  105. #addFSPath(cds, '0', 'media')
  106. site = server.Site(root)
  107. reactor.listenTCP(listenPort, site)
  108. # we need to do this after the children are there, since we send notifies
  109. s.register('%s::upnp:rootdevice' % uuid,
  110. 'upnp:rootdevice',
  111. urlbase + 'root-device.xml')
  112. s.register(uuid,
  113. uuid,
  114. urlbase + 'root-device.xml')
  115. s.register('%s::urn:schemas-upnp-org:device:MediaServer:1' % uuid,
  116. 'urn:schemas-upnp-org:device:MediaServer:1',
  117. urlbase + 'root-device.xml')
  118. s.register('%s::urn:schemas-upnp-org:service:ConnectionManager:1' % uuid,
  119. 'urn:schemas-upnp-org:device:ConnectionManager:1',
  120. urlbase + 'root-device.xml')
  121. s.register('%s::urn:schemas-upnp-org:service:ContentDirectory:1' % uuid,
  122. 'urn:schemas-upnp-org:device:ContentDirectory:1',
  123. urlbase + 'root-device.xml')
  124. # Main loop
  125. reactor.run()