@@ -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 '<AC3Descritor: %s>' % (', '.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 <start> Starting pos'
print ' -e <end> 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)