|
- #!/usr/bin/env python
- # Copyright 2006-2009 John-Mark Gurney <jmg@funkthat.com>
-
- __version__ = '$Change$'
- # $Id$
-
- ffmpeg_path = '/a/home/jmg/src/ffmpeg/ffmpeg'
- ffmpeg_path = '/usr/local/bin/ffmpeg'
- ffmpeg_path = '/a/home/jmg/src/ffmpeg.tmp/ffmpeg'
- ffmpeg_path = '/usr/local/bin/ffmpeg-devel'
-
- import datetime
- import FileDIDL
- import errno
- import itertools
- import os
- import stat
-
- from DIDLLite import StorageFolder, Item, Resource, ResourceList
- 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 implementer
-
- __all__ = [ 'registerklassfun', 'registerfiletoignore',
- 'FSObject', 'FSItem', 'FSDirectory',
- ]
-
- mimedict = static.loadMimeTypes()
-
- _klassfuns = []
-
- def registerklassfun(fun, debug=False):
- _klassfuns.append((fun, debug))
-
- _filestoignore = {
- '.DS_Store': None
- }
-
- def registerfiletoignore(f):
- _filestoignore[f] = None
-
- # Return this class when you want the file to be skipped. If you return this,
- # no other modules will be applied, and it won't be added. Useful for things
- # like .DS_Store which are known to useless on a media server.
- class IgnoreFile:
- pass
-
- def statcmp(a, b, cmpattrs = [ 'st_ino', 'st_dev', 'st_size', 'st_mtime', ]):
- if a is None or b is None:
- return False
-
- for i in cmpattrs:
- if getattr(a, i) != getattr(b, i):
- return False
- return True
-
- class FSObject(object):
- def __init__(self, path):
- self.FSpath = path
- self.pstat = None
-
- def checkUpdate(self, **kwargs):
- # need to handle no such file or directory
- # push it up? but still need to handle disappearing
- try:
- nstat = os.stat(self.FSpath)
- #print 'cU:', `self`, `self.pstat`, `nstat`, statcmp(self.pstat, nstat)
- if statcmp(self.pstat, nstat):
- return
-
- self.pstat = nstat
- self.doUpdate(**kwargs)
- except OSError as x:
- log.msg('os.stat, OSError: %s' % x)
- if x.errno in (errno.ENOENT, errno.ENOTDIR, errno.EPERM, ):
- # We can't access it anymore, delete it
- self.cd.delItem(self.id)
- return
- else:
- raise
-
- def __repr__(self):
- return '<%s.%s: path: %s, id: %s, parent: %s, title: %s>' % \
- (self.__class__.__module__, self.__class__.__name__,
- repr(self.FSpath), self.id, self.parentID, repr(self.title))
-
- #@implementer(interfaces.IConsumer)
- #class NullConsumer(file, abstract.FileDescriptor):
- #
- # 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
-
- mimetype = { 'xvid': 'video/x-msvideo',
- 'mpeg2': 'video/mpeg',
- 'mp4': 'video/mp4',
- }
-
- vcodec = mods[0]
- if mods[0] not in mimetype:
- vcodec = 'mp4'
-
- request.setHeader('content-type', mimetype[vcodec])
- if request.method == 'HEAD':
- return ''
-
- audiomp3 = [ '-acodec', 'mp3', '-ab', '192k', '-ac', '2', ]
- audiomp2 = [ '-acodec', 'mp2', '-ab', '256k', '-ac', '2', ]
- audioac3 = [ '-acodec', 'ac3', '-ab', '640k', ]
- audioaac = [ '-acodec', 'aac', '-ab', '640k', ]
- optdict = {
- 'xvid': [ '-vcodec', 'xvid',
- #'-mv4', '-gmc', '-g', '240',
- '-f', 'avi', ] + audiomp3,
- 'mpeg2': [ '-vcodec', 'mpeg2video', #'-g', '60',
- '-f', 'mpegts', ] + audioac3,
- 'mp4': [ '-vcodec', 'libx264', #'-g', '60',
- '-f', 'mpegts', ] + audioaac,
- }
- args = [ 'ffmpeg', '-i', path,
- '-sameq',
- '-threads', '4',
- #'-vb', '8000k',
- #'-sc_threshold', '500000', '-b_strategy', '1', '-max_b_frames', '6',
- ] + optdict[vcodec] + [ '-', ]
- log.msg(*[repr(i) for i in 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.getHeader('getcontentfeatures.dlna.org'):
- # request.setHeader('contentFeatures.dlna.org', 'DLNA.ORG_OP=01;DLNA.ORG_CI=0')
- # # we only want the headers
- # self.notrans.render(request)
- # request.unregisterProducer()
- # return ''
-
- if request.postpath:
- # Translation request
- return DynamTransfer(self.path, request.postpath, request).render()
- else:
- request.setHeader('transferMode.dlna.org', 'Streaming')
- request.setHeader('contentFeatures.dlna.org', 'DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=017000 00000000000000000000000000')
- return self.notrans.render(request)
-
- class FSItem(FSObject, Item):
- def __init__(self, *args, **kwargs):
- FSObject.__init__(self, kwargs.pop('path'))
- mimetype = kwargs.pop('mimetype')
- kwargs['content'] = DynamicTrans(self.FSpath,
- static.File(self.FSpath, mimetype))
-
- kwargs['date'] = datetime.datetime.utcfromtimestamp(os.stat(self.FSpath).st_mtime).isoformat() + '+00:00'
-
- Item.__init__(self, *args, **kwargs)
- self.url = '%s/%s' % (self.cd.urlbase, self.id)
- self.mimetype = mimetype
- self.checkUpdate()
-
- def doUpdate(self):
- #print 'FSItem doUpdate:', `self`
- self.res = ResourceList()
- r = Resource(self.url, 'http-get:*:%s:*' % self.mimetype)
- r.size = os.path.getsize(self.FSpath)
- self.res.append(r)
- if self.mimetype.split('/', 1)[0] == 'video':
- self.res.append(Resource(self.url + '/mpeg2',
- 'http-get:*:%s:*' % 'video/mpeg'))
- self.res.append(Resource(self.url + '/xvid',
- 'http-get:*:%s:*' % 'video/x-msvideo'))
- Item.doUpdate(self)
-
- def ignoreFiles(path, fobj):
- bn = os.path.basename(path)
- if bn in _filestoignore:
- return IgnoreFile, None
- elif bn[:2] == '._' and open(path).read(4) == '\x00\x05\x16\x07':
- # AppleDouble encoded Macintosh Resource Fork
- return IgnoreFile, None
-
- return None, None
-
- def defFS(path, fobj):
- if os.path.isdir(path):
- # new dir
- return FSDirectory, { 'path': path }
- elif os.path.isfile(path):
- # new file - fall through to below
- pass
- else:
- log.msg('skipping (not dir or reg): %s' % path)
- return None, None
-
- klass, mt = FileDIDL.buildClassMT(FSItem, path)
-
- return klass, { 'path': path, 'mimetype': mt }
-
- def dofileadd(path, name):
- klass = None
- fsname = os.path.join(path, name)
- try:
- fobj = open(fsname)
- except:
- fobj = None
- for i, debug in itertools.chain(( (ignoreFiles, False), ), _klassfuns, ( (defFS, False), )):
- try:
- try:
- # incase the call expects a clean file
- fobj.seek(0)
- except:
- pass
-
- #log.msg('testing:', `i`, `fsname`, `fobj`)
- klass, kwargs = i(fsname, fobj)
- if klass is not None:
- break
- except:
- if debug:
- import traceback
- traceback.print_exc(file=log.logfile)
-
- if klass is None or klass is IgnoreFile:
- return None, None, None, None
-
- #print 'matched:', os.path.join(path, name), `i`, `klass`
- return klass, name, (), kwargs
-
- class FSDirectory(FSObject, StorageFolder):
- def __init__(self, *args, **kwargs):
- path = kwargs['path']
- del kwargs['path']
- StorageFolder.__init__(self, *args, **kwargs)
- FSObject.__init__(self, path)
-
- def genCurrent(self):
- return ((x.id, os.path.basename(x.FSpath)) for x in self )
-
- def genChildren(self):
- return os.listdir(self.FSpath)
-
- def createObject(self, i):
- return dofileadd(self.FSpath, i)
-
- def __repr__(self):
- return ('<%s.%s: path: %s, id: %s, parent: %s, title: %s, ' + \
- 'cnt: %d>') % (self.__class__.__module__,
- self.__class__.__name__, self.FSpath, self.id,
- self.parentID, self.title, len(self))
|