#!/usr/bin/env python # Licensed under the MIT license # http://opensource.org/licenses/mit-license.php # Copyright 2005, Tim Potter # Copyright 2006 John-Mark Gurney __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 FSStorage import FSDirectory import os import os.path import random import socket import string from twisted.application import internet, service from twisted.python import usage def generateuuid(): if False: return 'uuid:asdflkjewoifjslkdfj' return ''.join([ 'uuid:'] + map(lambda x: random.choice(string.letters), xrange(20))) class Options(usage.Options): optParameters = [ [ 'title', 't', 'My Media Server', 'Title of the server.', ], [ 'path', 'p', 'media', 'Root path of the media to be served.', ], ] def postOptions(self): p = self['path'] if not os.path.isdir(p): raise usage.UsageError, 'path %s does not exist' % `p` def parseArgs(self, *args): # XXX - twisted doesn't let you provide a message on what # arguments are required, so we will do our own work in here. if len(args) not in (1, 2): raise usage.UsageError, 'Arguments: addr [ port ]' self['addr'] = args[0] if len(args) == 1: port = random.randint(10000, 65000) else: port = int(args[1]) if port < 1024 or port > 65535: raise ValueError( 'port must be between 1024 and 65535') self['port'] = port def makeService(config): listenAddr = config['addr'] listenPort = config['port'] serv = service.MultiService() # Create SSDP server from SSDP import SSDPServer, SSDP_PORT s = SSDPServer() debug.insertnamespace('s', s) internet.MulticastServer(SSDP_PORT, s, listenMultiple=True).setServiceParent(serv) uuid = generateuuid() urlbase = 'http://%s:%d/' % (listenAddr, listenPort) # Create SOAP server and content 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() # This sets up the root to be the media dir so we don't have to enumerate # the directory cds = ContentDirectoryServer(config['title'], klass=FSDirectory, path=config['path'], 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. # XXX - There isn't any easier way to get to the mime-type dict that I know of. 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', # From: http://www.matroska.org/technical/specs/notes.html '.mkv': 'video/x-matroska', '.mka': 'audio/x-matroska', #'.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) internet.TCPServer(listenPort, site).setServiceParent(serv) # we need to do this after the children are there, since we send notifies import urlparse rdxml = urlparse.urljoin(urlbase, 'root-device.xml') s.register('%s::upnp:rootdevice' % uuid, 'upnp:rootdevice', rdxml) s.register(uuid, uuid, rdxml) s.register('%s::urn:schemas-upnp-org:device:MediaServer:1' % uuid, 'urn:schemas-upnp-org:device:MediaServer:1', rdxml) s.register('%s::urn:schemas-upnp-org:service:ConnectionManager:1' % uuid, 'urn:schemas-upnp-org:device:ConnectionManager:1', rdxml) s.register('%s::urn:schemas-upnp-org:service:ContentDirectory:1' % uuid, 'urn:schemas-upnp-org:device:ContentDirectory:1', rdxml) return serv