Browse Source

make seeking possible on mpegts files... This does some

magic to simply replace the pat w/ only the program we want,
but keeps all the data there...  it wastes a bit of bandwidth,
but makes the file seekable, which is good for the media player..

We need to extend this so that we generate the PAT's first, and
then start reading/writing the file so we don't pass a PAT unchanged..

[git-p4: depot-paths = "//depot/": change = 1096]
replace/b43bf02ddeddd088c0e6b94974ca1a46562eb3db
John-Mark Gurney 18 years ago
parent
commit
aa594de14a
1 changed files with 125 additions and 30 deletions
  1. +125
    -30
      mpegtsmod.py

+ 125
- 30
mpegtsmod.py View File

@@ -8,8 +8,11 @@ __version__ = '$Change$'
tsselpypath = 'mpegts/tssel.py' tsselpypath = 'mpegts/tssel.py'
default_audio_lang = 'eng' default_audio_lang = 'eng'


import array
import itertools
import os import os
import sets import sets
import struct


import sys import sys
mpegtspath = 'mpegts' mpegtspath = 'mpegts'
@@ -23,8 +26,8 @@ from FSStorage import FSObject, registerklassfun


from twisted.python import log, threadable from twisted.python import log, threadable
from twisted.spread import pb from twisted.spread import pb
from twisted.internet import process, protocol, reactor
from twisted.web import resource, server
from twisted.internet import abstract, process, protocol, reactor
from twisted.web import error, http, resource, server


class _LimitedFile(file): class _LimitedFile(file):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -100,39 +103,68 @@ class MPEGTSTransfer(pb.Viewable):


threadable.synchronize(MPEGTSTransfer) threadable.synchronize(MPEGTSTransfer)


class DynamTSTransfer(protocol.ProcessProtocol):
class DynamTSTransfer(pb.Viewable):
def __init__(self, path, pmt, *pids): def __init__(self, path, pmt, *pids):
self.path = path self.path = path
#log.msg("DynamTSTransfer: pmt: %s, pids: %s" % (pmt, pids))
self.pmt = pmt self.pmt = pmt
self.pids = pids self.pids = pids
self.didpmt = False


def outReceived(self, data):
self.request.write(data)
def resumeProducing(self):
if not self.request:
return


def outConnectionLost(self):
if self.request:
repcnt = 0
data = self.fp.read(min(abstract.FileDescriptor.bufferSize,
self.size - self.written) // 188 * 188)
dataarray = array.array('B', data)
for i in xrange(0, len(data), 188):
if data[i] != 'G':
print 'bad sync'
continue
frst = dataarray[i + 1]
pid = (frst & 0x1f) << 8 | dataarray[i + 2]
if not frst & 0x40:
continue
elif not self.didpmt and pid == self.pmt:
startpmt = 4
if ((dataarray[i + 3] >> 4) & 0x3) == 0x3:
# Adaptation
startpmt += dataarray[i + startpmt] + 1
startpmt += dataarray[i + startpmt] + 1
assert data[i + startpmt] =='\x02', (startpmt,
data[i:i + 20])
self.pats = itertools.cycle(tssel.genpats(
self.pmt, struct.unpack('>H', data[i +
startpmt + 3:i + startpmt + 5])[0]))
self.didpmt = True

if pid == 0 and self.didpmt:
assert data[i + 4] =='\x00' and \
data[i + 5] == '\x00', 'error: %s' % `data[i:i + 10]`
repcnt += 1
pn = self.pats.next()
data = data[:i] + pn + data[i +
188:]

if repcnt > 1:
print 'repcnt:', repcnt, 'len(data):', len(data)

if data:
self.written += len(data)
self.request.write(data)
if self.request and self.fp.tell() == self.size:
self.request.unregisterProducer() self.request.unregisterProducer()
self.request.finish() self.request.finish()
self.request = None self.request = None


def errReceived(self, data):
def pauseProducing(self):
pass pass
#log.msg(data)


def stopProducing(self): def stopProducing(self):
if self.request:
self.request.unregisterProducer()
self.request.finish()

if self.proc:
self.proc.loseConnection()
self.proc.signalProcess('INT')

self.fp.close()
self.request = None self.request = None
self.proc = None

pauseProducing = lambda x: x.proc.pauseProducing()
resumeProducing = lambda x: x.proc.resumeProducing()


def render(self, request): def render(self, request):
path = self.path path = self.path
@@ -140,15 +172,79 @@ class DynamTSTransfer(protocol.ProcessProtocol):
pids = self.pids pids = self.pids
self.request = request self.request = request


fsize = size = os.path.getsize(path)

request.setHeader('accept-ranges','bytes')

request.setHeader('content-type', 'video/mpeg') request.setHeader('content-type', 'video/mpeg')
if request.method == 'HEAD':

try:
self.fp = open(path)
except IOError, e:
import errno
if e[0] == errno.EACCESS:
return error.ForbiddenResource().render(request)
else:
raise

if request.setLastModified(os.path.getmtime(path)) is http.CACHED:
return '' return ''


args = [ 'tssel.py', path, str(pmt), ] + map(str, pids)
self.proc = process.Process(reactor, tsselpypath, args,
None, None, self)
self.proc.closeStdin()
request.registerProducer(self, 1)
trans = True
# Commented out because it's totally broken. --jknight 11/29/04
# XXX - fixed? jmg 2/17/06
range = request.getHeader('range')

tsize = size
if range is not None:
# This is a request for partial data...
bytesrange = range.split('=')
assert bytesrange[0] == 'bytes', \
"Syntactically invalid http range header!"
start, end = bytesrange[1].split('-', 1)
if start:
start = int(start)
self.fp.seek(start)
if end and int(end) < size:
end = int(end)
else:
end = size - 1
else:
lastbytes = int(end)
if size < lastbytes:
lastbytes = size
start = size - lastbytes
self.fp.seek(start)
fsize = lastbytes
end = size - 1
start = start // 188 * 188
self.fp.seek(start)
size = (end + 1) // 188 * 188
fsize = end - int(start) + 1
# start is the byte offset to begin, and end is the
# byte offset to end.. fsize is size to send, tsize
# is the real size of the file, and size is the byte
# position to stop sending.

if fsize <= 0:
request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE
)
fsize = tsize
trans = False
else:
request.setResponseCode(http.PARTIAL_CONTENT)
request.setHeader('content-range',"bytes %s-%s/%s " % (
str(start), str(end), str(tsize)))

request.setHeader('content-length', str(fsize))

if request.method == 'HEAD' or trans is False:
request.method = 'HEAD'
return ''

self.size = tsize
self.written = 0
request.registerProducer(self, 0)


return server.NOT_DONE_YET return server.NOT_DONE_YET


@@ -163,9 +259,6 @@ class MPEGTSResource(resource.Resource):
def render(self, request): def render(self, request):
request.setHeader('content-type', 'video/mpeg') request.setHeader('content-type', 'video/mpeg')


if request.method == 'HEAD':
return ''

# return data # return data
return DynamTSTransfer(*self.args).render(request) return DynamTSTransfer(*self.args).render(request)


@@ -207,6 +300,7 @@ class MultiMPEGTS(FSObject, StorageFolder):


doupdate = False doupdate = False
origchildren, toindex = _gennameindexes(self.tvct['channels']) origchildren, toindex = _gennameindexes(self.tvct['channels'])
#log.msg('MultiMPEGTS doUpdate: tvct: %s' % self.tvct)
children = sets.Set(origchildren) children = sets.Set(origchildren)
for i in self.pathObjmap.keys(): for i in self.pathObjmap.keys():
if i not in children: if i not in children:
@@ -243,6 +337,7 @@ def detectmpegts(path, fobj):
# fast enough. # fast enough.
return MPEGTS, { 'path': path, 'tvct': tvct['channels'][0] } return MPEGTS, { 'path': path, 'tvct': tvct['channels'][0] }
elif len(tvct['channels']) > 1: elif len(tvct['channels']) > 1:
#log.msg('MultiMPEGTS: path: %s' % path)
return MultiMPEGTS, { 'path': path } return MultiMPEGTS, { 'path': path }


return None, None return None, None


Loading…
Cancel
Save