diff --git a/mpegts/mpegts.py b/mpegts/mpegts.py old mode 100644 new mode 100755 index f948ffc..5d47e7c --- a/mpegts/mpegts.py +++ b/mpegts/mpegts.py @@ -1,7 +1,34 @@ #!/usr/bin/env python # +# Copyright 2006-2007 John-Mark Gurney. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $Id$ +# import itertools +import os import sets import struct import traceback @@ -478,6 +505,23 @@ class AC3Descriptor: sm_dict = { 0: 'Not indicated', 1: 'NOT Dolby surround encoded', 2: 'Dolby surround encoded', 3: 'Reserved', } + bsmod_dict = { 0: 'main: complete', 1: 'main: music and effects', + 2: 'associated: visually imparied', + 3: 'associated: hearing imparied', 4: 'associated: dialogue', + 5: 'associated: commentary', 6: 'associated: emergency', } + + bs_mod = property(lambda x: x.bsmoddesc()) + num_channels = property(lambda x: x.numchan_dict[x.numchan]) + + def bsmoddesc(self): + if self.bsmod == 7: + if (self.numchan & 0x8) and self.numchan == 1: + return 'associated: voice over' + else: + return 'main: karaoke' + else: + return self.bsmod_dict[self.bsmod] + numchan_dict = { 0: '1+1', 1: '1/0', 2: '2/0', 3: '3/0', 4: '2/1', 5: '3/1', 6: '2/2', 7: '3/2', 8: '1', 9: '<=2', 10: '<=3', 11: '<=4', 12: '<=5', 13: '<=6', 14: 'Reserved', @@ -493,11 +537,16 @@ class AC3Descriptor: self.surround_mode = self.sm_dict[brcsm & 0x3] bsmodnumchanfullsvc = ord(data[2]) self.bsmod = bsmodnumchanfullsvc >> 6 - self.num_channels = \ - self.numchan_dict[bsmodnumchanfullsvc >> 1 & 0xf] + numchan = (bsmodnumchanfullsvc >> 1) & 0xf + self.numchan = numchan + + # KTVU only puts 3 bytes here + if len(data) == 3: + return + i = 4 # dropped langcod as not used per A/52a 3.4 - if (bsmodnumchanfullsvc >> 1 & 0xf) == 0: + if numchan == 0: i += 1 if self.bsmod < 2: self.mainid = ord(data[i]) >> 5 @@ -518,7 +567,7 @@ class AC3Descriptor: def __repr__(self): v = ['sample_rate', 'bsid', 'br_exact', 'bitrate', - 'surround_mode', 'bsmod', 'num_channels', 'mainid', + 'surround_mode', 'bs_mod', 'num_channels', 'mainid', 'asvcflags', 'text', ] return '' % (', '.join(attribreprlist(self, @@ -880,6 +929,7 @@ def iteravpids(stream, avpids): class PMT(dict): def __init__(self): super(PMT, self).__init__() + self.pcrpid = None self.es = [] def clean_up(self): @@ -893,6 +943,8 @@ class PMT(dict): assert psip.table_id == 0x02 tb = psip.table + pcrpid = ((ord(tb[0]) & 0x1f) << 8) | ord(tb[1]) + self.pcrpid = pcrpid ltmp = ((ord(tb[2]) & 0xf) << 8) | ord(tb[3]) + 4 self.update(getdescriptors(tb[4:ltmp])) i = ltmp @@ -1081,11 +1133,26 @@ class TSPacket: if self.error: return i = 4 + adapt_len = ord(p[4]) + # XXX - this is a large adapt, is it real? + if adapt >= 2 and adapt_len >= 188: + return + if adapt >= 2: - adapt_len = ord(p[4]) + if adapt == 3: + assert adapt_len >= 0 and adapt_len <= 182 + else: + pass + # my reading of the spec says this, but in + # practice this isn't the case + #assert adapt == 2 and adapt_len == 183 buf = p[i + 1:i + 1 + adapt_len] - assert len(buf) == adapt_len - self.decode_adaptation(buf) + #print self.error, self.start, self.priority, self.pid, self.scramble, adapt, self.continuity, adapt_len, i + assert len(buf) == adapt_len, 'lengths: %d, %d' % (len(buf), adapt_len) + try: + self.decode_adaptation(buf) + except: + pass # XXX - handle adpatation i += 1 + adapt_len self.payload = p[i:] @@ -1177,13 +1244,17 @@ data gets modified.''' class TSPStream: '''This class takes a file object, and outputs TS packets.''' - def __init__(self, f): + def __init__(self, f, endpos = None): self.f = f + self.endpos = endpos def __iter__(self): foundsync = False buf = self.f.read(READBLK) while buf: + if self.endpos is not None and self.f.tell() > self.endpos: + break + if not foundsync: try: start = buf.index(TSSYNC) @@ -1244,6 +1315,8 @@ def usage(): print ' -m print PAT and PMT' print ' -t print TVCT' print ' -y file offset when done' + print ' -s Starting pos' + print ' -e Ending pos' def findchannel(tvct, chan): for i in tvct['channels']: @@ -1329,7 +1402,7 @@ def GetTVCT(tsstream): def main(): try: - opts, args = getopt.getopt(sys.argv[1:], "bc:hlmo:pty") + opts, args = getopt.getopt(sys.argv[1:], "bc:e:hlmo:ps:ty") except getopt.GetoptError: # print help information and exit: usage() @@ -1343,6 +1416,8 @@ def main(): output = None allmaps = False printtvct = False + startpos = None + endpos = None needtvct = False needpat = False @@ -1364,10 +1439,14 @@ def main(): if channelsel is not None: needpat = True needtvct = True + elif o == '-e': + endpos = int(a) elif o == "-m": allmaps = True needallmaps = True needpat = True + elif o == '-s': + startpos = int(a) elif o == '-t': printtvct = True needtvct = True @@ -1379,18 +1458,20 @@ def main(): usage() sys.exit() elif o == '-o': - output = file(a, 'w') + output = a elif o == '-p': printbandwidthpercent = True elif o == '-y': printbyteoffset = True - if len(args) != 1: + if len(args) != 1 or (channelsel and not output): usage() sys.exit() inp = open(args[0]) - s = TSPStream(inp) + if startpos is not None: + inp.seek(startpos) + s = TSPStream(inp, endpos) pat = PAT() pmts = {} tvct = TVCT() @@ -1421,8 +1502,8 @@ def main(): return False for i in itertools.imap(TSPacket, s): - if hasattr(i, 'splice_countdown') or hasattr(i, 'DTS_next_AU'): - print 'splice_countdown:', repr(i) + #if hasattr(i, 'splice_countdown') or hasattr(i, 'DTS_next_AU'): + # print 'splice_countdown:', repr(i) #if hasattr(i, 'PCR'): # print 'PCR:', repr(i) #if i.pid in (48, 64, 80, 112): @@ -1449,25 +1530,20 @@ def main(): if needtvct and tvct: # Handle TVCT needtvct = False + if channelsel is not None: + channelfnd = findchannel(tvct, channelsel) + if channelfnd is None: + sys.stderr.write("Unable to find channel: %s\n" % channelsel) + channelsel = None + else: + channelfnd = pat[channelfnd['prog_num']] + getpmt(channelfnd) if needpat and pat: needpat = False for j in pat.itervalues(): if listchan or allmaps: getpmt(j) - if channelsel is not None: - channelfnd = findchannel(tvct, channelsel) - if channelfnd is None: - sys.stderr.write("Unable to find channel: %s\n" % channelsel) - channelsel = None - else: - channelfnd = pat[channelfnd['prog_num']] - getpmt(channelfnd) - - if channelfnd and pmts[channelfnd]: - av = getaudiovideopids(pmts[channelfnd]) - for j in itertools.chain(*av): - print j if needallmaps and pat and pmts: for i in pat.itervalues(): @@ -1480,6 +1556,10 @@ def main(): if not (needtvct or needpat or needpmts() or printbandwidth or needallmaps): break + if channelfnd and pmts[channelfnd]: + av = getaudiovideopids(pmts[channelfnd]) + os.system("python tssel.py %s %s %s > '%s'" % (args[0], channelfnd, ' '.join(map(str, itertools.chain(*av))), output)) + if allmaps: print repr(pat) print repr(pmts) diff --git a/mpegts/tssel.py b/mpegts/tssel.py index 8999f33..6f8e306 100755 --- a/mpegts/tssel.py +++ b/mpegts/tssel.py @@ -1,4 +1,31 @@ #!/usr/bin/env python +# +# Copyright 2006-2007 John-Mark Gurney. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $Id$ +# import sys sys.path.append('/Users/jgurney/p4/bktrau/info') @@ -7,15 +34,18 @@ import itertools import mpegts import sys import sets +import struct def usage(): print >>sys.stderr, 'Usage: %s ...' % sys.argv[0] sys.exit(1) -def genpats(pmt): - BASEPAT = map(None, "G@\x00\x10\x00\x00\xb0\r\x00\x00\xc1\x00\x00\x00\x01\xe0\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") +def genpats(pmt, prognum): + BASEPAT = map(None, "\x47\x40\x00\x10\x00\x00\xb0\x0d\x00\x00\xc1\x00\x00\x00\x00\xe0\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff") patidx = 4 + 1 # TS header + pointer table + BASEPAT[patidx + 8] = chr(prognum >> 8) + BASEPAT[patidx + 9] = chr(prognum & 0xff) BASEPAT[patidx + 10] = chr(0xe0 | ((pmt >> 8) & 0x1f)) BASEPAT[patidx + 11] = chr(pmt & 0xff) newcrc = mpegts.psip_calc_crc32(''.join(BASEPAT[patidx:patidx + 12])) @@ -25,6 +55,8 @@ def genpats(pmt): assert len(BASEPAT) == mpegts.TSPKTLEN ret = [] + + # Generate continuity_counter old = ord(BASEPAT[3]) & 0xf0 for i in range(16): # continuity BASEPAT[3] = chr(old | i) @@ -33,22 +65,28 @@ def genpats(pmt): return ret def producets(inp, pmtpid, *pids): - print `inp`, `pmtpid`, `pids` - pats = itertools.cycle(genpats(pmtpid)) + #print `inp`, `pmtpid`, `pids` # XXX - check if all pids are ints? in range? pids = sets.Set(pids) stream = mpegts.TSPStream(inp) + didpmt = False for i in stream: frst = ord(i[1]) # Get first and error bits for testing. pid = (frst & 0x1f) << 8 | ord(i[2]) - if pid == pmtpid and (frst & 0xc0) == 0x40: + if pid == 0 and didpmt: yield pats.next() + elif pid == pmtpid: + if not didpmt: + assert i[4] == '\x00' and i[5] == '\x02' + pats = itertools.cycle(genpats(pmtpid, struct.unpack('>H', i[8:10])[0])) + yield pats.next() + didpmt = True # XXX - we probably want to rewrite the PMT to only # include the pids we are sending. yield i - elif pid in pids: + elif pid in pids and didpmt: yield i def main():