|
|
@@ -0,0 +1,177 @@ |
|
|
|
#!/usr/bin/env python |
|
|
|
|
|
|
|
# Licensed under the MIT license |
|
|
|
# http://opensource.org/licenses/mit-license.php |
|
|
|
# Copyright 2005, Tim Potter <tpot@samba.org> |
|
|
|
# Copyright 2006 John-Mark Gurney <jmg@funkthat.com> |
|
|
|
|
|
|
|
__version__ = '$Change$' |
|
|
|
# $Id$ |
|
|
|
|
|
|
|
# make sure debugging is initalized first, other modules can be pulled in |
|
|
|
# before the "real" debug stuff is setup. (hmm I could make this a two |
|
|
|
# stage, where we simulate a namespace to either be thrown away when the |
|
|
|
# time comes, or merge into the correct one) |
|
|
|
import debug # my debugging module |
|
|
|
debug.doDebugging(True) # open up debugging port |
|
|
|
|
|
|
|
# Modules to import, maybe config file or something? |
|
|
|
def tryloadmodule(mod): |
|
|
|
try: |
|
|
|
return __import__(mod) |
|
|
|
except ImportError: |
|
|
|
#import traceback |
|
|
|
#traceback.print_exc() |
|
|
|
pass |
|
|
|
|
|
|
|
# ZipStorage w/ tar support should be last as it will gobble up empty files. |
|
|
|
# These should be sorted by how much work they do, the least work the earlier. |
|
|
|
# mpegtsmod can be really expensive. |
|
|
|
modules = [ |
|
|
|
'shoutcast', |
|
|
|
'pyvr', |
|
|
|
'dvd', |
|
|
|
'ZipStorage', |
|
|
|
'mpegtsmod', |
|
|
|
] |
|
|
|
modmap = {} |
|
|
|
for i in modules: |
|
|
|
modmap[i] = tryloadmodule(i) |
|
|
|
|
|
|
|
for i in modules: |
|
|
|
debug.insertnamespace(i, modmap[i]) |
|
|
|
|
|
|
|
from DIDLLite import TextItem, AudioItem, VideoItem, ImageItem, Resource, StorageFolder |
|
|
|
from FSStorage import FSDirectory |
|
|
|
import os |
|
|
|
import os.path |
|
|
|
import random |
|
|
|
import socket |
|
|
|
import string |
|
|
|
import sys |
|
|
|
from twisted.python import log |
|
|
|
from twisted.internet import reactor |
|
|
|
|
|
|
|
def generateuuid(): |
|
|
|
if False: |
|
|
|
return 'uuid:asdflkjewoifjslkdfj' |
|
|
|
return ''.join([ 'uuid:'] + map(lambda x: random.choice(string.letters), xrange(20))) |
|
|
|
|
|
|
|
listenAddr = sys.argv[1] |
|
|
|
if len(sys.argv) > 2: |
|
|
|
listenPort = int(sys.argv[2]) |
|
|
|
if listenPort < 1024 or listenPort > 65535: |
|
|
|
raise ValueError, 'port out of range' |
|
|
|
else: |
|
|
|
listenPort = random.randint(10000, 65000) |
|
|
|
|
|
|
|
log.startLogging(sys.stdout) |
|
|
|
|
|
|
|
# Create SSDP server |
|
|
|
|
|
|
|
from SSDP import SSDPServer, SSDP_PORT, SSDP_ADDR |
|
|
|
|
|
|
|
s = SSDPServer() |
|
|
|
debug.insertnamespace('s', s) |
|
|
|
|
|
|
|
port = reactor.listenMulticast(SSDP_PORT, s, listenMultiple=True) |
|
|
|
port.joinGroup(SSDP_ADDR) |
|
|
|
port.setLoopbackMode(0) # don't get our own sends |
|
|
|
|
|
|
|
uuid = generateuuid() |
|
|
|
urlbase = 'http://%s:%d/' % (listenAddr, listenPort) |
|
|
|
|
|
|
|
# Create SOAP server |
|
|
|
|
|
|
|
from twisted.web import server, resource, static |
|
|
|
from ContentDirectory import ContentDirectoryServer |
|
|
|
from ConnectionManager import ConnectionManagerServer |
|
|
|
|
|
|
|
class WebServer(resource.Resource): |
|
|
|
def __init__(self): |
|
|
|
resource.Resource.__init__(self) |
|
|
|
|
|
|
|
class RootDevice(static.Data): |
|
|
|
def __init__(self): |
|
|
|
r = { |
|
|
|
'hostname': socket.gethostname(), |
|
|
|
'uuid': uuid, |
|
|
|
'urlbase': urlbase, |
|
|
|
} |
|
|
|
d = file('root-device.xml').read() % r |
|
|
|
static.Data.__init__(self, d, 'text/xml') |
|
|
|
|
|
|
|
root = WebServer() |
|
|
|
debug.insertnamespace('root', root) |
|
|
|
content = resource.Resource() |
|
|
|
mediapath = 'media' |
|
|
|
if not os.path.isdir(mediapath): |
|
|
|
print >>sys.stderr, \ |
|
|
|
'Sorry, %s is not a directory, no content to serve.' % `mediapath` |
|
|
|
sys.exit(1) |
|
|
|
|
|
|
|
# This sets up the root to be the media dir so we don't have to |
|
|
|
# enumerate the directory |
|
|
|
cds = ContentDirectoryServer('My Media Server', klass=FSDirectory, |
|
|
|
path=mediapath, urlbase=os.path.join(urlbase, 'content'), webbase=content) |
|
|
|
debug.insertnamespace('cds', cds) |
|
|
|
root.putChild('ContentDirectory', cds) |
|
|
|
cds = cds.control |
|
|
|
root.putChild('ConnectionManager', ConnectionManagerServer()) |
|
|
|
root.putChild('root-device.xml', RootDevice()) |
|
|
|
root.putChild('content', content) |
|
|
|
|
|
|
|
|
|
|
|
# Purely to ensure some sane mime-types. On MacOSX I need these. |
|
|
|
medianode = static.File('pymediaserv') |
|
|
|
medianode.contentTypes.update( { |
|
|
|
# From: http://support.microsoft.com/kb/288102 |
|
|
|
'.asf': 'video/x-ms-asf', |
|
|
|
'.asx': 'video/x-ms-asf', |
|
|
|
'.wma': 'audio/x-ms-wma', |
|
|
|
'.wax': 'audio/x-ms-wax', |
|
|
|
'.wmv': 'video/x-ms-wmv', |
|
|
|
'.wvx': 'video/x-ms-wvx', |
|
|
|
'.wm': 'video/x-ms-wm', |
|
|
|
'.wmx': 'video/x-ms-wmx', |
|
|
|
|
|
|
|
#'.ts': 'video/mp2t', |
|
|
|
'.ts': 'video/mpeg', # we may want this instead of mp2t |
|
|
|
'.m2t': 'video/mpeg', |
|
|
|
'.m2ts': 'video/mpeg', |
|
|
|
'.mp4': 'video/mp4', |
|
|
|
#'.mp4': 'video/mpeg', |
|
|
|
'.dat': 'video/mpeg', # VCD tracks |
|
|
|
'.ogm': 'application/ogg', |
|
|
|
'.vob': 'video/mpeg', |
|
|
|
#'.m4a': 'audio/mp4', # D-Link can't seem to play AAC files. |
|
|
|
}) |
|
|
|
del medianode |
|
|
|
|
|
|
|
site = server.Site(root) |
|
|
|
reactor.listenTCP(listenPort, site) |
|
|
|
|
|
|
|
# we need to do this after the children are there, since we send notifies |
|
|
|
s.register('%s::upnp:rootdevice' % uuid, |
|
|
|
'upnp:rootdevice', |
|
|
|
urlbase + 'root-device.xml') |
|
|
|
|
|
|
|
s.register(uuid, |
|
|
|
uuid, |
|
|
|
urlbase + 'root-device.xml') |
|
|
|
|
|
|
|
s.register('%s::urn:schemas-upnp-org:device:MediaServer:1' % uuid, |
|
|
|
'urn:schemas-upnp-org:device:MediaServer:1', |
|
|
|
urlbase + 'root-device.xml') |
|
|
|
|
|
|
|
s.register('%s::urn:schemas-upnp-org:service:ConnectionManager:1' % uuid, |
|
|
|
'urn:schemas-upnp-org:device:ConnectionManager:1', |
|
|
|
urlbase + 'root-device.xml') |
|
|
|
|
|
|
|
s.register('%s::urn:schemas-upnp-org:service:ContentDirectory:1' % uuid, |
|
|
|
'urn:schemas-upnp-org:device:ContentDirectory:1', |
|
|
|
urlbase + 'root-device.xml') |
|
|
|
|
|
|
|
# Main loop |
|
|
|
|
|
|
|
reactor.run() |