From 31ddad6041b10bf2c859fd1e5ed18049147f6254 Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Sat, 8 Jul 2006 04:49:49 -0800 Subject: [PATCH] support multiple res elements... add code to dynamicly transcode to mpeg2 or xvid via additional res elements.. document this and other new features in the README... [git-p4: depot-paths = "//depot/": change = 837] --- DIDLLite.py | 6 ++- FSStorage.py | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++- README | 7 ++++ 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/DIDLLite.py b/DIDLLite.py index b029f55..e5e64ab 100644 --- a/DIDLLite.py +++ b/DIDLLite.py @@ -82,7 +82,11 @@ class Object(object): SubElement(root, 'dc:creator').text = self.creator if self.res is not None: - root.append(self.res.toElement()) + try: + for res in iter(self.res): + root.append(res.toElement()) + except TypeError: + root.append(self.res.toElement()) if self.writeStatus is not None: SubElement(root, 'upnp:writeStatus').text = self.writeStatus diff --git a/FSStorage.py b/FSStorage.py index 48abd9a..817cb88 100644 --- a/FSStorage.py +++ b/FSStorage.py @@ -4,6 +4,8 @@ # $Id$ # +ffmpeg_path = '/Users/jgurney/src/ffmpeg/ffmpeg' + import FileDIDL import errno import itertools @@ -12,8 +14,10 @@ import sets import stat from DIDLLite import StorageFolder, Item, VideoItem, AudioItem, TextItem, ImageItem, Resource -from twisted.web import static +from twisted.web import resource, server, static from twisted.python import log +from twisted.internet import abstract, interfaces, process, protocol, reactor +from zope.interface import implements __all__ = [ 'registerklassfun', 'FSObject', 'FSItem', 'FSVideoItem', 'FSAudioItem', 'FSTextItem', 'FSImageItem', 'mimetoklass', @@ -69,13 +73,105 @@ class FSObject(object): return '<%s.%s: path: %s>' % (self.__class__.__module__, self.__class__.__name__, self.FSpath) +class NullConsumer(file, abstract.FileDescriptor): + implements(interfaces.IConsumer) + + def __init__(self): + file.__init__(self, '/dev/null', 'w') + abstract.FileDescriptor.__init__(self) + + def write(self, data): + pass + +class DynamTransfer(protocol.ProcessProtocol): + def __init__(self, path, mods, request): + self.path = path + self.mods = mods + self.request = request + + def outReceived(self, data): + self.request.write(data) + + def outConnectionLost(self): + if self.request: + self.request.unregisterProducer() + self.request.finish() + self.request = None + + def errReceived(self, data): + pass + #log.msg(data) + + def stopProducing(self): + if self.request: + self.request.unregisterProducer() + self.request.finish() + + if self.proc: + self.proc.loseConnection() + self.proc.signalProcess('INT') + + self.request = None + self.proc = None + + pauseProducing = lambda x: x.proc.pauseProducing() + resumeProducing = lambda x: x.proc.resumeProducing() + + def render(self): + mods = self.mods + path = self.path + request = self.request + + vcodec = mods[0] + if mods[0] not in ('xvid', 'mpeg2', ): + vcodec = 'xvid' + + mimetype = { 'xvid': 'video/avi', 'mpeg2': 'video/mpeg', } + request.setHeader('content-type', mimetype[vcodec]) + if request.method == 'HEAD': + return '' + + optdict = { + 'xvid': [ '-vcodec', 'xvid', + #'-mv4', '-gmc', '-g', '240', + '-f', 'avi', ], + 'mpeg2': [ '-vcodec', 'mpeg2video', #'-g', '60', + '-f', 'mpeg', ], + } + audio = [ '-acodec', 'mp3', '-ab', '192', ] + args = [ 'ffmpeg', '-i', path, '-b', '8000', + #'-sc_threshold', '500000', '-b_strategy', '1', '-max_b_frames', '6', + ] + optdict[vcodec] + audio + [ '-', ] + #log.msg(*args) + self.proc = process.Process(reactor, ffmpeg_path, args, + None, None, self) + self.proc.closeStdin() + request.registerProducer(self, 1) + + return server.NOT_DONE_YET + +class DynamicTrans(resource.Resource): + isLeaf = True + + def __init__(self, path, notrans): + self.path = path + self.notrans = notrans + + def render(self, request): + if request.postpath: + # Translation request + return DynamTransfer(self.path, request.postpath, request).render() + else: + return self.notrans.render(request) + class FSItem(FSObject, Item): def __init__(self, *args, **kwargs): FSObject.__init__(self, kwargs['path']) del kwargs['path'] mimetype = kwargs['mimetype'] del kwargs['mimetype'] - kwargs['content'] = static.File(self.FSpath) + kwargs['content'] = DynamicTrans(self.FSpath, + static.File(self.FSpath, mimetype)) Item.__init__(self, *args, **kwargs) self.url = '%s/%s' % (self.cd.urlbase, self.id) self.mimetype = mimetype @@ -83,6 +179,9 @@ class FSItem(FSObject, Item): def doUpdate(self): self.res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype) self.res.size = os.path.getsize(self.FSpath) + self.res = [ self.res ] + self.res.append(Resource(self.url + '/mpeg2', 'http-get:*:%s:*' % 'video/mpeg')) + self.res.append(Resource(self.url + '/xvid', 'http-get:*:%s:*' % 'video/avi')) Item.doUpdate(self) def defFS(path, fobj): diff --git a/README b/README index 0417103..6cc5c58 100644 --- a/README +++ b/README @@ -25,10 +25,17 @@ Good Luck! John-Mark Gurney Ideas for future improvements: + Figure out how to rearchitect ContentDirectoryControl so I don't + need to use doRecall. This may be helped by not necessarily + figuring out all the children of a member just to fetch it. + childCount isn't a required attribute. Autodetect IP address. Support sorting by other attributes. v0.x: + Add support for multiple res elements and automatic transcoding + to either avi/xvid or mpeg2 using ffmpeg. + Look inside DVDs and handle titles and chapters. Empty dirs w/ no content would disappear, and cause a short response to BrowseDirectChildren. The DSM-520 askes for one more than displayed, and uses the existant of the extra item