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.

151 lines
4.2 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. # make sure debugging is initalized first, other modules can be pulled in
  10. # before the "real" debug stuff is setup. (hmm I could make this a two
  11. # stage, where we simulate a namespace to either be thrown away when the
  12. # time comes, or merge into the correct one)
  13. import debug # my debugging module
  14. debug.doDebugging(True) # open up debugging port
  15. # Modules to import, maybe config file or something?
  16. def tryloadmodule(mod):
  17. try:
  18. return __import__(mod)
  19. except ImportError:
  20. #import traceback
  21. #traceback.print_exc()
  22. pass
  23. # ZipStorage w/ tar support should be last as it will gobble up empty files.
  24. modules = [ 'dvd', 'shoutcast', 'ZipStorage' ]
  25. modmap = {}
  26. for i in modules:
  27. modmap[i] = tryloadmodule(i)
  28. for i in modules:
  29. debug.insertnamespace(i, modmap[i])
  30. from DIDLLite import TextItem, AudioItem, VideoItem, ImageItem, Resource, StorageFolder
  31. from FSStorage import FSDirectory
  32. import os
  33. import os.path
  34. import random
  35. import socket
  36. import string
  37. import sys
  38. from twisted.python import log
  39. from twisted.internet import reactor
  40. def generateuuid():
  41. if False:
  42. return 'uuid:asdflkjewoifjslkdfj'
  43. return ''.join([ 'uuid:'] + map(lambda x: random.choice(string.letters), xrange(20)))
  44. listenAddr = sys.argv[1]
  45. if len(sys.argv) > 2:
  46. listenPort = int(sys.argv[2])
  47. if listenPort < 1024 or listenPort > 65535:
  48. raise ValueError, 'port out of range'
  49. else:
  50. listenPort = random.randint(10000, 65000)
  51. log.startLogging(sys.stdout)
  52. # Create SSDP server
  53. from SSDP import SSDPServer, SSDP_PORT, SSDP_ADDR
  54. s = SSDPServer()
  55. debug.insertnamespace('s', s)
  56. port = reactor.listenMulticast(SSDP_PORT, s)
  57. port.joinGroup(SSDP_ADDR)
  58. port.setLoopbackMode(0) # don't get our own sends
  59. uuid = generateuuid()
  60. urlbase = 'http://%s:%d/' % (listenAddr, listenPort)
  61. # Create SOAP server
  62. from twisted.web import server, resource, static
  63. from ContentDirectory import ContentDirectoryServer
  64. from ConnectionManager import ConnectionManagerServer
  65. class WebServer(resource.Resource):
  66. def __init__(self):
  67. resource.Resource.__init__(self)
  68. class RootDevice(static.Data):
  69. def __init__(self):
  70. r = {
  71. 'hostname': socket.gethostname(),
  72. 'uuid': uuid,
  73. 'urlbase': urlbase,
  74. }
  75. d = file('root-device.xml').read() % r
  76. static.Data.__init__(self, d, 'text/xml')
  77. root = WebServer()
  78. debug.insertnamespace('root', root)
  79. content = resource.Resource()
  80. 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
  81. debug.insertnamespace('cds', cds)
  82. root.putChild('ContentDirectory', cds)
  83. cds = cds.control
  84. root.putChild('ConnectionManager', ConnectionManagerServer())
  85. root.putChild('root-device.xml', RootDevice())
  86. root.putChild('content', content)
  87. # Purely to ensure some sane mime-types. On MacOSX I need these.
  88. medianode = static.File('pymediaserv')
  89. medianode.contentTypes.update( {
  90. '.wmv': 'video/x-ms-wmv',
  91. #'.ts': 'video/mp2t',
  92. '.ts': 'video/mpeg', # we may want this instead of mp2t
  93. '.m2t': 'video/mpeg',
  94. '.mp4': 'video/mp4',
  95. #'.mp4': 'video/mpeg',
  96. '.dat': 'video/mpeg', # VCD tracks
  97. '.ogm': 'application/ogg',
  98. '.vob': 'video/mpeg',
  99. #'.m4a': 'audio/mp4', # D-Link can't seem to play AAC files.
  100. })
  101. del medianode
  102. site = server.Site(root)
  103. reactor.listenTCP(listenPort, site)
  104. # we need to do this after the children are there, since we send notifies
  105. s.register('%s::upnp:rootdevice' % uuid,
  106. 'upnp:rootdevice',
  107. urlbase + 'root-device.xml')
  108. s.register(uuid,
  109. uuid,
  110. urlbase + 'root-device.xml')
  111. s.register('%s::urn:schemas-upnp-org:device:MediaServer:1' % uuid,
  112. 'urn:schemas-upnp-org:device:MediaServer:1',
  113. urlbase + 'root-device.xml')
  114. s.register('%s::urn:schemas-upnp-org:service:ConnectionManager:1' % uuid,
  115. 'urn:schemas-upnp-org:device:ConnectionManager:1',
  116. urlbase + 'root-device.xml')
  117. s.register('%s::urn:schemas-upnp-org:service:ContentDirectory:1' % uuid,
  118. 'urn:schemas-upnp-org:device:ContentDirectory:1',
  119. urlbase + 'root-device.xml')
  120. # Main loop
  121. reactor.run()