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.

121 lines
3.3 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. #
  7. # $Id$
  8. #
  9. # Modules to import, maybe config file or something?
  10. import dvd
  11. import shoutcast
  12. import ZipStorage # w/ tarfile support, it will gobble up empty files!
  13. import debug # my debugging module
  14. debug.doDebugging(True) # open up debugging port
  15. debug.insertnamespace('dvd', dvd)
  16. debug.insertnamespace('shoutcast', shoutcast)
  17. debug.insertnamespace('ZipStorage', ZipStorage)
  18. from DIDLLite import TextItem, AudioItem, VideoItem, ImageItem, Resource, StorageFolder
  19. from FSStorage import FSDirectory
  20. import os
  21. import os.path
  22. import random
  23. import socket
  24. import string
  25. import sys
  26. from twisted.python import log
  27. from twisted.internet import reactor
  28. def generateuuid():
  29. if False:
  30. return 'uuid:asdflkjewoifjslkdfj'
  31. return ''.join([ 'uuid:'] + map(lambda x: random.choice(string.letters), xrange(20)))
  32. listenAddr = sys.argv[1]
  33. if len(sys.argv) > 2:
  34. listenPort = int(sys.argv[2])
  35. if listenPort < 1024 or listenPort > 65535:
  36. raise ValueError, 'port out of range'
  37. else:
  38. listenPort = random.randint(10000, 65000)
  39. log.startLogging(sys.stdout)
  40. # Create SSDP server
  41. from SSDP import SSDPServer, SSDP_PORT, SSDP_ADDR
  42. s = SSDPServer()
  43. debug.insertnamespace('s', s)
  44. port = reactor.listenMulticast(SSDP_PORT, s)
  45. port.joinGroup(SSDP_ADDR)
  46. port.setLoopbackMode(0) # don't get our own sends
  47. uuid = generateuuid()
  48. urlbase = 'http://%s:%d/' % (listenAddr, listenPort)
  49. # Create SOAP server
  50. from twisted.web import server, resource, static
  51. from ContentDirectory import ContentDirectoryServer
  52. from ConnectionManager import ConnectionManagerServer
  53. class WebServer(resource.Resource):
  54. def __init__(self):
  55. resource.Resource.__init__(self)
  56. class RootDevice(static.Data):
  57. def __init__(self):
  58. r = {
  59. 'hostname': socket.gethostname(),
  60. 'uuid': uuid,
  61. 'urlbase': urlbase,
  62. }
  63. d = file('root-device.xml').read() % r
  64. static.Data.__init__(self, d, 'text/xml')
  65. root = WebServer()
  66. debug.insertnamespace('root', root)
  67. content = resource.Resource()
  68. cds = ContentDirectoryServer('My Media Server', klass = FSDirectory, path = 'media', urlbase = os.path.join(urlbase, 'content'), webbase = content) # This sets up the root to be the media dir so we don't have to enumerate the directory
  69. debug.insertnamespace('cds', cds)
  70. root.putChild('ContentDirectory', cds)
  71. cds = cds.control
  72. root.putChild('ConnectionManager', ConnectionManagerServer())
  73. root.putChild('root-device.xml', RootDevice())
  74. root.putChild('content', content)
  75. site = server.Site(root)
  76. reactor.listenTCP(listenPort, site)
  77. # we need to do this after the children are there, since we send notifies
  78. s.register('%s::upnp:rootdevice' % uuid,
  79. 'upnp:rootdevice',
  80. urlbase + 'root-device.xml')
  81. s.register(uuid,
  82. uuid,
  83. urlbase + 'root-device.xml')
  84. s.register('%s::urn:schemas-upnp-org:device:MediaServer:1' % uuid,
  85. 'urn:schemas-upnp-org:device:MediaServer:1',
  86. urlbase + 'root-device.xml')
  87. s.register('%s::urn:schemas-upnp-org:service:ConnectionManager:1' % uuid,
  88. 'urn:schemas-upnp-org:device:ConnectionManager:1',
  89. urlbase + 'root-device.xml')
  90. s.register('%s::urn:schemas-upnp-org:service:ContentDirectory:1' % uuid,
  91. 'urn:schemas-upnp-org:device:ContentDirectory:1',
  92. urlbase + 'root-device.xml')
  93. # Main loop
  94. reactor.run()