diff --git a/pymediaserv b/pymediaserv new file mode 100755 index 0000000..5a2ddda --- /dev/null +++ b/pymediaserv @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +from twisted.internet import reactor +from twisted.application import service +from twisted.python import log, usage +import pymeds +import sys + +if __name__ == '__main__': + config = pymeds.Options() + try: + config.parseOptions() + except usage.UsageError, errortext: + print '%s: %s' % (sys.argv[0], errortext) + print '%s: Try --help for usage details.' % sys.argv[0] + sys.exit(1) + + log.startLogging(sys.stdout) + ser = pymeds.makeService(config) + ser.startService() + reactor.addSystemEventTrigger('before', 'shutdown', + service.IService(ser).stopService) + reactor.run() diff --git a/pymeds.py b/pymeds.py index 4e4dcb5..378788a 100755 --- a/pymeds.py +++ b/pymeds.py @@ -47,9 +47,6 @@ import os.path import random import socket import string -import sys -from twisted.python import log -from twisted.internet import reactor from twisted.application import internet, service from twisted.python import usage @@ -64,120 +61,132 @@ class Options(usage.Options): [ 'path', 'p', 'media', 'Root path of the media to be served.', ], ] - def parseArgs(self, addr, port=None): - self['addr'] = addr - if port is None: + 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(port) - if listenPort < 1024 or listenPort > 65535: + port = int(args[1]) + if port < 1024 or port > 65535: raise ValueError( 'port must be between 1024 and 65535') self['port'] = port -listenAddr = config['addr'] -listenPort = config['port'] - -application = service.Application("PyMeds") - -# Create SSDP server -from SSDP import SSDPServer, SSDP_PORT, SSDP_ADDR - -s = SSDPServer() -debug.insertnamespace('s', s) - -port = internet.MulticastServer(SSDP_PORT, s, listenMultiple=True) -port.setServiceParent(application) -port.joinGroup(SSDP_ADDR) -port.setLoopbackMode(0) # don't get our own sends - -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(application) - -# we need to do this after the children are there, since we send notifies -import urlparse -rdxml = urlparse.join(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) +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