From 6715bb6857fcaff1a3b3280e031dcef10f22c434 Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Wed, 13 Sep 2006 00:24:47 -0800 Subject: [PATCH] include the mpeg-ts helper programs, and comment on their location, update the module to point to the current location... document pydvdread... [git-p4: depot-paths = "//depot/": change = 889] --- README | 7 +- mpegts/mpegts.py | 1619 ++++++++++++++++++++++++++++++++++++++++++++++ mpegts/tssel.py | 68 ++ mpegtsmod.py | 4 +- 4 files changed, 1694 insertions(+), 4 deletions(-) create mode 100644 mpegts/mpegts.py create mode 100644 mpegts/tssel.py diff --git a/README b/README index a155253..ddce248 100644 --- a/README +++ b/README @@ -53,11 +53,14 @@ v0.3: patches. Look inside MPEG-TS for TVCT and/or PAT and if there is more than one program, make it a container w/ the different - programs. + programs. Includes the program and MPEG-TS python module in + the mpegts directory. 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. We can not yet - play the streams. + play the streams. This requires pydvdread which is included + in the directory pydvdread. It depends upon libdvdread and + requires a C compiler to build. 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 diff --git a/mpegts/mpegts.py b/mpegts/mpegts.py new file mode 100644 index 0000000..9a4dc49 --- /dev/null +++ b/mpegts/mpegts.py @@ -0,0 +1,1619 @@ +#!/usr/bin/env python +# + +import itertools +import sets +import struct +import traceback + +TSSYNC = '\x47' +TSPKTLEN = 188 +READBLK = 1024 + +def attribreprlist(obj, attrs): + return map(lambda x, y = obj: '%s: %s' % (x, repr(getattr(y, x))), itertools.ifilter(lambda x, y = obj: hasattr(y, x), attrs)) + +class UnReadSTR: + def __init__(self, s): + self.s = s + self.pos = 0 + + self._buf = [] + self._buftot = 0 + + def __nonzero__(self): + return self._buftot or self.pos < len(self.s) + + def tell(self): + return self.pos - self._buftot + + def unread(self, buf): + self._buf.append(buf) + self._buftot += len(buf) + + def peek(self, size): + r = self.read(size) + self.unread(r) + return r + + def sread(self, cnt = -1): + oldpos = self.pos + if cnt == -1: + self.pos = len(self.s) + return self.s[oldpos:] + + self.pos += cnt + if self.pos > len(self.s): + self.pos = len(self.s) + + return self.s[oldpos:self.pos] + + def read(self, size = None): + if size is None and self._buf: + ret = self._buf.pop() + self._buftot -= len(ret) + elif size is None: + ret = self.sread() + else: + ret = [] + while size and self._buftot: + ret.append(self._buf[-1][:size]) + l = len(ret[-1]) + if size > l: + assert len(self._buf[-1]) == l + self._buf.pop() + else: + self._buf[-1] = self._buf[-1][size:] + self._buftot -= l + size -= l + + if size: + ret.append(self.sread(size)) + + ret = ''.join(ret) + + return ret + +def DVDAudioFilter(itr, subchan): + '''subchan should be in the range [0x80, 0x8f], this will filter out all other subchannels in that range that do not match subchan.''' + assert subchan >= 0x80 and subchan <= 0x8f + + def checksubchan(pes, sc = subchan): + if pes.stream_id != 0xbd: + return False + + subchan = ord(pes.data[0]) + if subchan == sc or subchan < 0x80 or subchan > 0x8f: + return True + + return False + + # XXX - I probably should mess w/ muxr so SCR is stable. + for i in itr: + j = Pack(UnReadSTR(i)) + + if filter(checksubchan, j): + yield i + +def findcodes(buf): + ret = [] + + i = 0 + l = len(buf) + while i < l: + j = buf.find('\x00\x00\x01', i) + if j == -1 or (i + 4) >= l: + break + ret.append((j, buf[j + 3])) + i = j + 4 + return ret + +class UnRead(file): + def __init__(self, *args, **kwargs): + super(UnRead, self).__init__(*args, **kwargs) + self._buf = [] + self._buftot = 0 + + def unread(self, buf): + self._buf.append(buf) + self._buftot += len(buf) + + def peek(self, size): + r = self.read(size) + self.unread(r) + return r + + def read(self, size = None): + if size is None and self._buf: + ret = self._buf.pop() + self._buftot -= len(ret) + elif size is None: + ret = super(UnRead, self).read() + else: + ret = [] + while size and self._buftot: + ret.append(self._buf[-1][:size]) + l = len(ret[-1]) + if size > l: + assert len(self._buf[-1]) == l + self._buf.pop() + else: + self._buf[-1] = self._buf[-1][size:] + self._buftot -= l + size -= l + + if size: + ret.append(super(UnRead, self).read(size)) + + ret = ''.join(ret) + + return ret + +def read_timestamp(buf): + assert len(buf) == 5 + assert (ord(buf[0]) & 0x1) == 1 + assert (ord(buf[2]) & 0x1) == 1 + assert (ord(buf[4]) & 0x1) == 1 + return (long(ord(buf[0]) & 0xe) << 29) | (ord(buf[1]) << 21) | \ + ((ord(buf[2]) & 0xfe) << 14) | (ord(buf[3]) << 7) | \ + ((ord(buf[4]) & 0xfe) >> 1) + +def read_escr(buf): + assert len(buf) == 6 + assert (ord(buf[0]) & 0x4) == 0x4 and (ord(buf[2]) & 0x4) == 0x4 + assert (ord(buf[4]) & 0x4) == 0x4 and (ord(buf[5]) & 0x1) == 0x1 + + base = (long(ord(buf[0]) & 0x38) << 27) | ((ord(buf[0]) & 0x3) << 28) |\ + (ord(buf[1]) << 20) | ((ord(buf[2]) & 0xf8) << 15) | \ + ((ord(buf[2]) & 0x3) << 13) | (ord(buf[3]) << 5) | \ + ((ord(buf[4]) & 0xf8) >> 3) + extension = ((ord(buf[4]) & 0x3) << 7) | (ord(buf[5]) >> 1) + + return (base, extension) + +class MPEGWriter: + END_CODE = '\xb9' + + def __init__(self, f): + self.f = f + self.SCR = (0, 0) + + def write_header(self, header): + self.f.write('\x00\x00\x01' + header) + + def close(self): + self.write_header(self.END_CODE) + + def __del__(self): + self.close() + +class PES: + PROGRAM_STREAM_MAP_ID = 0xbc + PRIVATE_1_ID = 0xbd + PADDING_ID = 0xbe + PRIVATE_2_ID = 0xbf + IS_AUDIO_ID = lambda x: (x & 0xe0) == 0xc0 + IS_VIDEO_ID = lambda x: (x & 0xf0) == 0xe0 + ECM_ID = 0xf0 + EMM_ID = 0xf1 + DSMCC_ID = 0xf2 + H2221_TYPE_E_ID = 0xf8 + PROGRAM_STREAM_DIRECTORY_ID = 0xff + + def __init__(self, buf): + # Pull out an IndexError first + assert buf[0] == '\x00' and buf[:3] == '\x00\x00\x01' + self.stream_id = ord(buf[3]) + self.length = (ord(buf[4]) << 8) | ord(buf[5]) + if self.length == 0: + self.length = len(buf) + else: + self.length += 6 + if len(buf) < self.length: + raise IndexError, 'not enough data' + + if self.stream_id == self.PADDING_ID: + # Validate padding? + #self.length -= 6 + pass + elif self.stream_id in (self.PROGRAM_STREAM_MAP_ID, + self.PRIVATE_2_ID, self.ECM_ID, self.EMM_ID, self.DSMCC_ID, + self.H2221_TYPE_E_ID, self.PROGRAM_STREAM_DIRECTORY_ID, ): + self.data = buf[6:self.length] + else: + i = 6 + assert (ord(buf[i]) & 0xc0) == 0x80 + self.scrambling_control = (ord(buf[i]) & 0x30) >> 4 + self.priority = bool(ord(buf[i]) & 0x8) + self.data_alignment = bool(ord(buf[i]) & 0x4) + self.copyright = bool(ord(buf[i]) & 0x2) + self.originalcopy = bool(ord(buf[i]) & 0x1) + i +=1 + ptsdts_flag = (ord(buf[i]) & 0xc0) >> 6 + escr_flag = bool(ord(buf[i]) & 0x20) + es_rate_flag = bool(ord(buf[i]) & 0x10) + dsm_trick_mode_flag = bool(ord(buf[i]) & 0x8) + additional_copy_info_flag = bool(ord(buf[i]) & 0x4) + crc_flag = bool(ord(buf[i]) & 0x2) + extension_flag = bool(ord(buf[i]) & 0x1) + header_end = i + 2 + ord(buf[i + 1]) + i += 2 + if ptsdts_flag == 0x2: + assert (ord(buf[i]) & 0xf0) == 0x20 + self.PTS = read_timestamp(buf[i:i + 5]) + i += 5 + elif ptsdts_flag == 0x3: + assert (ord(buf[i]) & 0xf0) == 0x30 + self.PTS = read_timestamp(buf[i:i + 5]) + i += 5 + assert (ord(buf[i]) & 0xf0) == 0x10 + self.DTS = read_timestamp(buf[i:i + 5]) + i += 5 + elif ptsdts_flag == 0x1: + raise ValueError, "ptsdts flag forbidden: %d" % ptsdts_flag + if escr_flag: + self.ESCR = read_escr(buf[i:i + 6]) + i += 6 + if es_rate_flag: + assert (ord(buf[i]) & 0x80) == 0x80 + assert (ord(buf[i + 2]) & 0x01) == 0x01 + self.ES_rate = ((ord(buf[i]) & 0x7f) << 15) | \ + (ord(buf[i + 1]) << 7) | \ + (ord(buf[i + 2]) >> 1) + i += 3 + if dsm_trick_mode_flag: + self.trick_mode_control = ord(buf[i]) >> 5 + self.trick_mode_bits = ord(buf[i]) & 0x1f + i += 1 + if additional_copy_info_flag: + assert (ord(buf[i]) & 0x80) == 0x80 + self.additional_copy_info = ord(buf[i]) & 0x7f + i += 1 + if crc_flag: + self.prev_crc = (ord(buf[i]) << 8) | ord(buf[i + 1]) + i += 2 + if extension_flag: + private_data_flag = bool(ord(buf[i]) & 0x80) + pack_header_field_flag = bool(ord(buf[i]) & 0x40) + program_packet_sequence_counter_flag = bool(ord(buf[i]) & 0x20) + pstd_buffer_flag = bool(ord(buf[i]) & 0x10) + pes_extension_flag_2 = bool(ord(buf[i]) & 0x01) + i += 1 + if private_data_flag: + self.private_data = buf[i:i + 16] + i += 16 + if pack_header_field_flag: + pack_field_length = ord(buf[i]) + self.pack_header = buf[i + 1:i + 1 + pack_field_length] + i += 1 + pack_field_length + if program_packet_sequence_counter_flag: + assert (ord(buf[i]) & 0x80) == 0x80 + self.sequence_counter = ord(buf[i]) & 0x7f + i += 1 + assert (ord(buf[i]) & 0x80) == 0x80 + self.mpeg1_mpeg2_ident = bool(ord(buf[i]) & 0x4) + self.original_stuff_len = ord(buf[i]) & 0x3f + i += 1 + if pstd_buffer_flag: + assert (ord(buf[i]) & 0xc0) == 0x40 + self.pstd_buffer_scale = bool(ord(buf[i]) & 0x20) + self.pstd_buffer_size = ((ord(buf[i]) & 0x1f) << 8) | ord(buf[i + 1]) + i += 2 + if pes_extension_flag_2: + assert (ord(buf[i]) & 0x80) == 0x80 + extension_field_length = ord(buf[i]) & 0x7f + self.extension_field = buf[i + 1: i + 1 + extension_field_length] + i += 1 + extension_field_length + + assert i <= header_end + self.data = buf[header_end:self.length] + + def __repr__(self): + # XXX - data length + v = [ 'length', 'scrambling_control', + 'priority', 'data_alignment', 'copyright', + 'originalcopy', 'PTS', 'DTS', 'ESCR', 'ES_rate', + 'trick_mode_control', 'trick_mode_bits', + 'additional_copy_info', 'pack_header', + 'sequence_counter', 'mpeg1_mpeg2_ident', + 'original_stuff_len', 'pstd_buffer_scale', + 'pstd_buffer_size', 'extension_field', ] + return '' % (self.stream_id, ', '.join(attribreprlist(self, v)), ) + +class Pack(list): + def __init__(self, f = None, **keyw): + super(Pack, self).__init__() + if f is not None: + d = f.read(14) + assert d[:4] == '\x00\x00\x01\xba' + assert (ord(d[4]) & 0xc0) == 0x40 + self.SCR = read_escr(d[4:10]) + assert ord(d[12]) & 0x3 == 0x3 + m = map(ord, d[10:13]) + self.muxr = (m[0] << 14) | (m[1] << 6) | (m[2] >> 2) + self.stuff_len = ord(d[13]) & 0x7 + f.read(self.stuff_len) + + # system header + d = f.peek(6) + if d[:4] == '\x00\x00\x01\xbb': + f.read(6) + hlen = (ord(d[4]) << 8) | ord(d[5]) + header = f.read(hlen) + oh = map(ord, header) + assert (oh[0] & 0x80) == 0x80 and (oh[2] & 0x1) == 0x1 + self.rate_bound = ((oh[0] & 0x7f) << 15) | (oh[1] << 7) | (oh[2] >> 1) + self.audio_bound = oh[3] >> 2 + self.fixed = bool(oh[3] & 0x2) + self.CSPS = bool(oh[3] & 0x1) + self.system_audio_lock = bool(oh[4] & 0x80) + self.system_video_lock = bool(oh[4] & 0x40) + assert (oh[4] & 0x20) == 0x20 + self.video_bound = oh[4] & 0x1f + self.packet_rate_restriction = bool(oh[5] & 0x80) + d = f.peek(1) + self.streams = {} + while ord(d) & 0x80: + d = map(ord, f.read(3)) + assert (d[1] & 0xc0) == 0xc0 + scaleflag = bool(d[1] & 0x20) + self.streams[d[0]] = (((d[1] & 0x1f) << 8) | d[2]) * (128, 1024)[scaleflag] + d = f.peek(1) + # PES packets + d = f.peek(3) + bytestoread = 2048 + while (f or d) and d == '\x00\x00\x01': + try: + d = f.read(bytestoread) + self.append(PES(d)) + f.unread(d[self[-1].length:]) + except IndexError: + f.unread(d) + bytestoread <<= 2 + d = f.peek(4) + else: + self.SCR = keyw['SCR'] + self.muxr = keyw['muxr'] # in bps (converts to 50 bytes/sec) + self.stuff_len = 0 + + def __repr__(self): + v = [ 'SCR', 'muxr', 'stuff_len', + 'rate_bound', 'audio_bound', 'fixed', 'CSPS', + 'system_audio_lock', 'system_video_lock', + 'video_bound', 'packet_rate_restriction', + 'streams', + ] + return '' % (', '.join(attribreprlist(self, v)), list.__repr__(self)) + + def __str__(self): + buf = [] + buf.append('\x00\x00\x01\xba') + clock = (1l << 46) | (((self.SCR[0] >> 30) & 0x7) << 43) | (1l << 42) | (((self.SCR[0] >> 15) & 0x7ffff) << 27) | (1 << 26) | ((self.SCR[0] & 0x7fff) << 11) | (1 << 10) | ((self.SCR[1] << 1) & 0x3fe) | 0x1 + for i in range(6): + buf.append(chr(clock >> ((5 - i) * 8) & 0xff)) + muxr = self.muxr / 50 / 8 + buf.append(chr((muxr >> 14) & 0xff)) + buf.append(chr((muxr >> 6) & 0xff)) + buf.append(chr(((muxr << 2) & 0xfc) | 0x3)) + buf.append(chr(0xf8 | (self.stuff_len & 7))) + buf.append('\xff' * self.stuff_len) + buf.extend(map(str, self)) + return ''.join(buf) + +# These are strings due to the floating point numbers +frame_rate_code = { + 0x0: 'forbidden', 0x1: '23.976', 0x2: '24', 0x3: '25', + 0x4: '29.97', 0x5: '30', 0x6: '50', 0x7: '59.95', + 0x8: '60', 0x9: 'reserved', 0xa: 'reserved', + 0xb: 'reserved', 0xc: 'reserved', 0xd: 'reserved', + 0xe: 'reserved', 0xf: 'reserved', +} +chroma_format = { + 0x0: 'reserved', 0x1: '4:2:0', 0x2: '4:2:2', 0x3: '4:4:4', +} + +class BitRate(int): + def __init__(self, bitrate): + super(BitRate, self).__init__(bitrate) + + def __str__(self): + return repr(self) + + def __repr__(self): + return '%dbps' % self + +def max_bitrate_descriptor(b): + assert len(b) == 3 + return BitRate((((ord(b[0]) & 0x3f) << 16) | ((ord(b[1]) & 0xff) << 8) | (ord(b[0]) & 0xff)) * 50 * 8) + +class ISO639LangDescriptor(list): + atypedict = { + 0: 'undefined', + 1: 'clean effects', + 2: 'hearing impaired', + 3: 'visual impaired commentary', + } + + def __init__(self, b): + assert len(b) % 4 == 0 + + for i in range(len(b) / 4): + lang = unicode(b[i * 4:i * 4 + 3], 'iso8859-1') + atype = self.atypedict[ord(b[i * 4 + 3])] + self.append((lang, atype)) + +class VStreamDescriptor: + def __init__(self, b): + fb = ord(b[0]) + # XXX - libdvbpsi says no not for mpeg2 flag, but my data + # seems to say otherwise. + self.mpeg2 = not bool(fb & 0x04) + assert (not self.mpeg2 and len(b) == 1) or (self.mpeg2 and len(b) == 3) + self.multiple_frame_rate = bool(fb & 0x80) + self.frame_rate_code = frame_rate_code[(fb & 0x78) >> 3] + self.constrained_parameter = bool(fb & 0x02) + self.still_picture = bool(fb & 0x01) + if self.mpeg2: + self.profile_level_indication = ord(b[1]) + tb = ord(b[2]) + self.chroma_format = chroma_format[(tb & 0xc0) >> 6] + self.frame_rate_extension = bool(tb & 0x20) + + def __repr__(self): + v = ['mpeg2', 'multiple_frame_rate', 'frame_rate_code', + 'constrained_parameter', 'still_picture', + 'profile_level_indication', 'chroma_format', + 'frame_rate_extension', ] + + return '' % (', '.join(attribreprlist(self, v)), ) + +class AC3Descriptor: + src_dict = { 0: '48k', 1: '44.1k', 2: '32k', 3: None, 4: '48k or 44.1k', + 5: '48k or 32k', 6: '44.1k or 32k', 7: '48k or 44.1k or 32k' } + + brc_dict = { 0: 32, 1: 40, 2: 48, 3: 56, 4: 64, 5: 80, 6: 96, 7: 112, + 8: 128, 9: 160, 10: 192, 11: 224, 12: 256, 13: 320, 14: 384, + 15: 448, 16: 512, 17: 576, 18: 640, } + + sm_dict = { 0: 'Not indicated', 1: 'NOT Dolby surround encoded', + 2: 'Dolby surround encoded', 3: 'Reserved', } + + 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', + 15: 'Reserved', } + + def __init__(self, data): + srcbsid = ord(data[0]) + self.sample_rate = self.src_dict[srcbsid >> 5] + self.bsid = srcbsid & 0x1f + brcsm = ord(data[1]) + self.br_exact = (brcsm & 0x80) == 0x80 + self.bitrate = self.brc_dict[(brcsm >> 2) & 0x1f] + 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] + i = 4 + # dropped langcod as not used per A/52a 3.4 + if (bsmodnumchanfullsvc >> 1 & 0xf) == 0: + i += 1 + if self.bsmod < 2: + self.mainid = ord(data[i]) >> 5 + else: + self.asvcflags = ord(data[i]) + i += 1 + textlangcode = ord(data[i]) + textlen = textlangcode >> 1 + i += 1 + txt = data[i:i + textlen] + if textlangcode & 1: + self.text = txt.decode('latin-1') + else: + assert NotImplementedError, 'the following code is untested' + self.text = ''.join(map(lambda x: + unichr(ord(x[0]) * 256 + ord(x[1])), + [txt[i:i+2] for i in range(0, len(txt), 2)])) + + def __repr__(self): + v = ['sample_rate', 'bsid', 'br_exact', 'bitrate', + 'surround_mode', 'bsmod', 'num_channels', 'mainid', + 'asvcflags', 'text', ] + + return '' % (', '.join(attribreprlist(self, + v)), ) + +class ServiceLocationDescriptor(list): + tag = 0xa1 + sldel = '>BH3c' + + def __init__(self, data): + step = struct.calcsize(self.sldel) + assert ((len(data) - 3) % step) == 0 + + list.__init__(self) + + self.pcr_pid, cnt = struct.unpack('>HB', data[:3]) + self.pcr_pid &= 0x1fff + for i in range(cnt): + type, pid, a, b, c = struct.unpack(self.sldel, + data[3 + i * step:3 + (i + 1) * step]) + pid &= 0x1fff + lang = a + b + c + if lang == '\x00' * 3: + lang = None + self.append({ 'type': type, 'pid': pid, 'lang': lang }) + + def __repr__(self): + return '' % \ + (self.pcr_pid, list.__repr__(self)) + +class MultiStringStruct(dict): + def decode(self, comp, mode, data): + assert (mode == 0 and comp in (1, 2)) or comp == 0 + if comp == 0: + return ''.join(map(lambda x, y = mode * 256: + unichr(ord(x) + y), data)) + + raise NotImplementedError, 'no code for comp type %d' % comp + + def __init__(self, data): + cnt = ord(data[0]) + off = 1 + for i in range(cnt): + lang = data[off:off + 3] + nseg = ord(data[off + 3]) + segs = [] + self[lang] = segs + off += 4 + for j in range(nseg): + comp_type = ord(data[off]) + mode = ord(data[off + 1]) + nbytes = ord(data[off + 2]) + segs.append(self.decode(comp_type, mode, + data[off + 3: off + 3 + nbytes])) + +class ComponentNameDescriptor(MultiStringStruct): + def __repr__(self): + return '' % \ + MultiStringStruct.__repr__(self) + +Descriptors = { + 2: VStreamDescriptor, + 10: ISO639LangDescriptor, + 14: max_bitrate_descriptor, + 0x81: AC3Descriptor, + 0xa1: ServiceLocationDescriptor, + 0xa3: ComponentNameDescriptor, +} + +PIDs = { + 0x00: ('PAT', 'Program Association Table'), + 0x01: ('CAT', 'Conditional Access Table'), + 0x02: ('TSDT', 'Program Stream Descriptor Table'), + 0x10: ('NIT', 'Network Information Table'), + 0x11: ('BAT', 'Bouquet Association Table'), + 0x11: ('SDT', 'Service Descriptor Table'), + 0x12: ('EIT', 'Event Information Table'), + 0x13: ('RST', 'running Status Table'), + 0x14: ('TOT', 'Time Offset Table'), +} + +def psip_calc_crc32(data, verbose = False, table = ( + 0x00000000l, 0x04c11db7l, 0x09823b6el, 0x0d4326d9l, + 0x130476dcl, 0x17c56b6bl, 0x1a864db2l, 0x1e475005l, + 0x2608edb8l, 0x22c9f00fl, 0x2f8ad6d6l, 0x2b4bcb61l, + 0x350c9b64l, 0x31cd86d3l, 0x3c8ea00al, 0x384fbdbdl, + 0x4c11db70l, 0x48d0c6c7l, 0x4593e01el, 0x4152fda9l, + 0x5f15adacl, 0x5bd4b01bl, 0x569796c2l, 0x52568b75l, + 0x6a1936c8l, 0x6ed82b7fl, 0x639b0da6l, 0x675a1011l, + 0x791d4014l, 0x7ddc5da3l, 0x709f7b7al, 0x745e66cdl, + 0x9823b6e0l, 0x9ce2ab57l, 0x91a18d8el, 0x95609039l, + 0x8b27c03cl, 0x8fe6dd8bl, 0x82a5fb52l, 0x8664e6e5l, + 0xbe2b5b58l, 0xbaea46efl, 0xb7a96036l, 0xb3687d81l, + 0xad2f2d84l, 0xa9ee3033l, 0xa4ad16eal, 0xa06c0b5dl, + 0xd4326d90l, 0xd0f37027l, 0xddb056fel, 0xd9714b49l, + 0xc7361b4cl, 0xc3f706fbl, 0xceb42022l, 0xca753d95l, + 0xf23a8028l, 0xf6fb9d9fl, 0xfbb8bb46l, 0xff79a6f1l, + 0xe13ef6f4l, 0xe5ffeb43l, 0xe8bccd9al, 0xec7dd02dl, + 0x34867077l, 0x30476dc0l, 0x3d044b19l, 0x39c556ael, + 0x278206abl, 0x23431b1cl, 0x2e003dc5l, 0x2ac12072l, + 0x128e9dcfl, 0x164f8078l, 0x1b0ca6a1l, 0x1fcdbb16l, + 0x018aeb13l, 0x054bf6a4l, 0x0808d07dl, 0x0cc9cdcal, + 0x7897ab07l, 0x7c56b6b0l, 0x71159069l, 0x75d48ddel, + 0x6b93dddbl, 0x6f52c06cl, 0x6211e6b5l, 0x66d0fb02l, + 0x5e9f46bfl, 0x5a5e5b08l, 0x571d7dd1l, 0x53dc6066l, + 0x4d9b3063l, 0x495a2dd4l, 0x44190b0dl, 0x40d816bal, + 0xaca5c697l, 0xa864db20l, 0xa527fdf9l, 0xa1e6e04el, + 0xbfa1b04bl, 0xbb60adfcl, 0xb6238b25l, 0xb2e29692l, + 0x8aad2b2fl, 0x8e6c3698l, 0x832f1041l, 0x87ee0df6l, + 0x99a95df3l, 0x9d684044l, 0x902b669dl, 0x94ea7b2al, + 0xe0b41de7l, 0xe4750050l, 0xe9362689l, 0xedf73b3el, + 0xf3b06b3bl, 0xf771768cl, 0xfa325055l, 0xfef34de2l, + 0xc6bcf05fl, 0xc27dede8l, 0xcf3ecb31l, 0xcbffd686l, + 0xd5b88683l, 0xd1799b34l, 0xdc3abdedl, 0xd8fba05al, + 0x690ce0eel, 0x6dcdfd59l, 0x608edb80l, 0x644fc637l, + 0x7a089632l, 0x7ec98b85l, 0x738aad5cl, 0x774bb0ebl, + 0x4f040d56l, 0x4bc510e1l, 0x46863638l, 0x42472b8fl, + 0x5c007b8al, 0x58c1663dl, 0x558240e4l, 0x51435d53l, + 0x251d3b9el, 0x21dc2629l, 0x2c9f00f0l, 0x285e1d47l, + 0x36194d42l, 0x32d850f5l, 0x3f9b762cl, 0x3b5a6b9bl, + 0x0315d626l, 0x07d4cb91l, 0x0a97ed48l, 0x0e56f0ffl, + 0x1011a0fal, 0x14d0bd4dl, 0x19939b94l, 0x1d528623l, + 0xf12f560el, 0xf5ee4bb9l, 0xf8ad6d60l, 0xfc6c70d7l, + 0xe22b20d2l, 0xe6ea3d65l, 0xeba91bbcl, 0xef68060bl, + 0xd727bbb6l, 0xd3e6a601l, 0xdea580d8l, 0xda649d6fl, + 0xc423cd6al, 0xc0e2d0ddl, 0xcda1f604l, 0xc960ebb3l, + 0xbd3e8d7el, 0xb9ff90c9l, 0xb4bcb610l, 0xb07daba7l, + 0xae3afba2l, 0xaafbe615l, 0xa7b8c0ccl, 0xa379dd7bl, + 0x9b3660c6l, 0x9ff77d71l, 0x92b45ba8l, 0x9675461fl, + 0x8832161al, 0x8cf30badl, 0x81b02d74l, 0x857130c3l, + 0x5d8a9099l, 0x594b8d2el, 0x5408abf7l, 0x50c9b640l, + 0x4e8ee645l, 0x4a4ffbf2l, 0x470cdd2bl, 0x43cdc09cl, + 0x7b827d21l, 0x7f436096l, 0x7200464fl, 0x76c15bf8l, + 0x68860bfdl, 0x6c47164al, 0x61043093l, 0x65c52d24l, + 0x119b4be9l, 0x155a565el, 0x18197087l, 0x1cd86d30l, + 0x029f3d35l, 0x065e2082l, 0x0b1d065bl, 0x0fdc1becl, + 0x3793a651l, 0x3352bbe6l, 0x3e119d3fl, 0x3ad08088l, + 0x2497d08dl, 0x2056cd3al, 0x2d15ebe3l, 0x29d4f654l, + 0xc5a92679l, 0xc1683bcel, 0xcc2b1d17l, 0xc8ea00a0l, + 0xd6ad50a5l, 0xd26c4d12l, 0xdf2f6bcbl, 0xdbee767cl, + 0xe3a1cbc1l, 0xe760d676l, 0xea23f0afl, 0xeee2ed18l, + 0xf0a5bd1dl, 0xf464a0aal, 0xf9278673l, 0xfde69bc4l, + 0x89b8fd09l, 0x8d79e0bel, 0x803ac667l, 0x84fbdbd0l, + 0x9abc8bd5l, 0x9e7d9662l, 0x933eb0bbl, 0x97ffad0cl, + 0xafb010b1l, 0xab710d06l, 0xa6322bdfl, 0xa2f33668l, + 0xbcb4666dl, 0xb8757bdal, 0xb5365d03l, 0xb1f740b4l +)): + '''Validate a PSIP CRC. Include the CRC in the data. The return value will be the valid data, or an exception will be raised if invalid.''' + + if verbose: + i_crc = 0xffffffffl + for i in data: + i_crc = ((i_crc << 8) & 0xffffffffl) ^ table[(i_crc >> 24) ^ ord(i)] + print hex(i_crc) + + i_crc = reduce(lambda x, y: ((x << 8) & 0xffffffffl) ^ table[(x >> 24) ^ ord(y)], data, 0xffffffffl) + return i_crc + +def psip_crc32(data): + return psip_calc_crc32(data) == 0 + +def getdescriptors(tb): + d = {} + i = 0 + while i < len(tb): + t = ord(tb[i]) + if d.has_key(t): + l = ord(tb[i + 1]) + data = tb[i + 2: i + 2 + l] + #print repr(d[t]), t, repr(data) + #assert not d.has_key(t) + l = ord(tb[i + 1]) + data = tb[i + 2: i + 2 + l] + try: + item = Descriptors[t](data) + except KeyError: + item = data + + try: + d[t].append(item) + except KeyError: + d[t] = [ item ] + + i += 2 + l + + return d + +class TSPSIPHandler(dict): + '''This object is used to represent the tables that come in on a +specific PID. Since there can be multiple tables on a specific PID +(ATSC's 0x1ffb), a dictionary of callable objects must be passed in, +and the key is the table number.''' + + def __init__(self, *t): + super(TSPSIPHandler, self).__init__() + self.update(*t) + self.discontinuity = True + self.complete = False + self.last_continuity = None + + # config knobs + self.current_only = True + self.ignerror = False + + def next_continuity(self, nc): + if self.last_continuity is None: + return True + + return ((self.last_continuity + 1) % 16) != nc + + def get_table_id(self): + if self.complete: + return self._table_id + + return None + + table_id = property(get_table_id) + + def decode_section_header(self, payload, i): + self._table_id = ord(payload[i]) + self.syntax = bool(ord(payload[i + 1]) & 0x80) + self.private = bool(ord(payload[i + 1]) & 0x40) + self.sect_len = (((ord(payload[i + 1]) & 0xf) << 8) | ord(payload[i + 2])) + 3 + self.stored_sects = [ payload[i:] ] + #print 'bar', i, repr(payload) + self.stored_len = len(self.stored_sects[0]) + self.discontinuity = False + + def __call__(self, p): + '''Pass in a TSPacket instance.''' + + if p.error and not self.ignerror: + return + + if p.start: + payload = p.payload + i = ord(payload[0]) + 1 + self.decode_section_header(payload, i) + else: + if self.discontinuity or self.next_continuity(p.continuity): + self.discontinuity = True + return + self.stored_sects.append(p.payload) + self.stored_len += len(p.payload) + + while self.table_id != 0xff: + if self.stored_len < self.sect_len: + # we need more data + self.last_continuity = p.continuity + return + + payload = ''.join(self.stored_sects) + assert len(payload) == self.stored_len + + if self.syntax: + # XXX I may need to include the skipped part above in + # the crc calculations. + if not psip_crc32(payload[:self.sect_len]): + raise ValueError, 'CRC check failed: %s' % `payload[:self.sect_len]` + self.extension = (ord(payload[3]) << 8) | ord(payload[4]) + self.version = (ord(payload[5]) & 0x3e) >> 1 + self.current_next = bool(ord(payload[5]) & 1) + self.section_number = ord(payload[6]) + self.last_section_number = ord(payload[7]) + self.protocol_version = ord(payload[8]) + # don't include the CRC + self.table = payload[8:self.sect_len - 4] + #if self.last_section_number: + # print repr(self), repr(p) + else: + self.table = payload[3:self.sect_len] + + self.complete = True + if self.current_only and not self.current_next: + continue + + # If this fails there are multiple sections + try: + self[self.table_id].clean_up() + self[self.table_id](self) + except KeyError: + pass # No handler, ignore or raise exception? + + # hmm. I had a packet with some low bits clear + # the spec seems to imply that there can be multiple sections, + # but every case I've seen in the world there isn't. + if ord(payload[self.sect_len]) != 0xff: + #print 'prev:', self.last_section_number + # I should make sure there is enough data + self.decode_section_header(payload, self.sect_len) + #print 'starting next section:', repr(self), repr(payload) + continue + else: + break + + def __repr__(self): + v = ('table_id', 'syntax', 'private', 'table', + 'extension', 'version', 'current_next', 'section_number', + 'last_section_number', 'protocol_version', ) + return '' % (', '.join(attribreprlist(self, v)), super(TSPSIPHandler, self).__repr__()) + +class PAT(dict): + def __init__(self): + '''In order to prevent confusion, you can't init w/ a packet.''' + + super(PAT, self).__init__() + self.pid_dict = {} + + def clean_up(self): + self.pid_dict = {} + self.clear() + + def has_pid(self, pid): + return self.pid_dict.has_key(pid) + + def get_prog(self, pid): + return self.pid_dict[pid] + + def __call__(self, psip, s = '>HH', sl = struct.calcsize('>HH')): + assert psip.table_id == 0x00 + + for i in range(len(psip.table) / sl): + prog, pid = struct.unpack(s, psip.table[i * sl:(i + 1) * sl]) + pid &= 0x1fff + self.pid_dict[pid] = prog + self[prog] = pid + + def __repr__(self): + return '' % dict.__repr__(self) + +def getaudiovideopids(pmt, lang = None): + anapid = None + apids = [] + vpids = [] + for i in pmt.es: + cpid = i[1] + j = i[2] + if i[0] == 2: + vpids.append(cpid) + elif j.has_key(5): + assert 'AC-3' in map(lambda x: x[:4], j[5]) + if lang is None or lang == j[10][0][0]: + apids.append(cpid) + else: + anapid = cpid + + if not apids and anapid is not None: + apids.append(anapid) + + return (apids, vpids) + +def iteravpids(stream, avpids): + avpids = sets.ImmutableSet(avpids) + + for i in stream: + if SimpleTSPacket(i).pid in avpids: + yield i + +class PMT(dict): + def __init__(self): + super(PMT, self).__init__() + self.es = [] + + def clean_up(self): + self.clear() + del self.es[:] + + def __nonzero__(self): + return len(self) or bool(self.es) + + def __call__(self, psip): + assert psip.table_id == 0x02 + + tb = psip.table + ltmp = ((ord(tb[2]) & 0xf) << 8) | ord(tb[3]) + 4 + self.update(getdescriptors(tb[4:ltmp])) + i = ltmp + + es = self.es + while i < len(tb): + t = ord(tb[i]) + p = ((ord(tb[i + 1]) & 0x1f) << 8) | ord(tb[i + 2]) + l = ((ord(tb[i + 3]) & 0x0f) << 8) | ord(tb[i + 4]) + i += 5 + d = getdescriptors(tb[i:i + l]) + i += l + es.append((t, p, d)) + + def __repr__(self): + return '' % (dict.__repr__(self), repr(self.es)) + +def channelmajorminorsort(x, y): + if x['major'] != y['major']: + return cmp(x['major'], y['major']) + + return cmp(x['minor'], y['minor']) + +class TVCT(dict): + def __init__(self): + '''In order to prevent confusion, you can't init w/ a packet.''' + + super(TVCT, self).__init__() + + def clean_up(self): + self.clear() + + def __call__(self, psip): + assert psip.table_id == 0xc8 + + self['channels'] = [] + tb = psip.table + i = ord(tb[0]) + 1 + chancnt = ord(tb[i]) + i += 1 + for foo in range(chancnt): + shrtnm = ''.join(map(lambda x: unichr((ord(x[0]) << 8) | ord(x[1])), [tb[i + x * 2:i + (x + 1) * 2] for x in range(7)])).rstrip(unichr(0)) + i += 7 * 2 + major = (((ord(tb[i]) << 8) | ord(tb[i + 1])) >> 2) & 0x3ff + minor = ((ord(tb[i + 1]) & 0x3) << 8) | ord(tb[i + 2]) + mode = ord(tb[i + 3]) + i += 4 + carrier, tsid, prog_num, flagsa, source, desc_len = struct.unpack('>IHHHHH', tb[i:i + 14]) + i += 14 + etm_loc = (flagsa & 0xc000) >> 14 + access_control = bool(flagsa & 0x2000) + hidden = bool(flagsa & 0x1000) + hide_guide = bool(flagsa & 0x200) + service = flagsa & 0x3f + desc_len &= 0x3ff + descs = getdescriptors(tb[i:i + desc_len]) + i += desc_len + self['channels'].append({ 'name': shrtnm, + 'major': major, 'minor': minor, 'mode': mode, + 'carrier': carrier, 'tsid': tsid, + 'prog_num': prog_num, 'source': source, + 'etm_loc': etm_loc, + 'access_control': access_control, 'hidden': hidden, + 'service': service, 'descriptors': descs }) + + desc_len = ((ord(tb[i]) & 0x3) << 8) | ord(tb[i + 1]) + i += 2 + self['descriptors'] = getdescriptors(tb[i:i + desc_len]) + + def __repr__(self): + return '' % (dict.__repr__(self), ) + +class TSPESHandler: + def __init__(self, cb): + self.cb = cb + self.discontinuity = True + self.complete = False + self.last_continuity = None + self.pes_len = None + + def next_continuity(self, nc): + if self.last_continuity is None: + return True + + return ((self.last_continuity + 1) % 16) == nc + + def is_video(self): + return (self.stream_id & 0xf0) == 0xe0 + + def __call__(self, p): + if p.error: + #print 'got error:', `p` + return + + if p.start: + if self.pes_len == 0: + assert self.is_video() + # if we were unbounded, dump the last one + if self.next_continuity(p.continuity): + self.cb(''.join(self.stored_sects)) + + payload = p.payload + if payload[:3] != '\x00\x00\x01': + raise ValueError, 'packet start code invalid' + self.stream_id = ord(payload[3]) + self.pes_len = (ord(payload[4]) << 8) | ord(payload[5]) + if not self.is_video(): + #print 'pes', hex(self.stream_id), repr(p) + assert self.pes_len != 0 + # A value of 0 indicates that the PES packet + # length is neither specified nor bounded and is + # allowed only in PES packets whose payload is a + # video elementary stream contained in Transport + # Stream packets. -- iso-13818-1 Sect. 2.4.3.7 + if self.pes_len != 0: + self.pes_len += 6 # add in header + self.stored_sects = [ payload ] + self.stored_len = len(self.stored_sects[0]) + self.discontinuity = False + else: + if self.discontinuity or not self.next_continuity(p.continuity): + self.discontinuity = True + return + self.stored_sects.append(p.payload) + self.stored_len += len(p.payload) + + self.last_continuity = p.continuity + if self.stored_len < self.pes_len or self.pes_len == 0: + return + + ps = ''.join(self.stored_sects) + assert self.stored_len == self.pes_len and self.pes_len == len(ps) + self.cb(ps) + +def read_clock(buf): + assert len(buf) == 6 + + base = (long(ord(buf[0])) << 25) | (ord(buf[1]) << 17) | \ + (ord(buf[2]) << 9) | (ord(buf[3]) << 1) | \ + (ord(buf[4]) >> 7) + extension = ((ord(buf[4]) & 0x1) << 8) | ord(buf[5]) + + return (base, extension) + +class SimpleTSPacket: + def __init__(self, p): + assert len(p) == TSPKTLEN + assert p[0] == TSSYNC + + f = ord(p[1]) + self.error = bool(f & 0x80) + self.start = bool(f & 0x40) + self.priority = bool(f & 0x20) + self.pid = ((f & 0x1f) << 8) + ord(p[2]) + if self.pid == 0x1fff: + return + f = ord(p[3]) + self.scramble = (f & 0xc0) >> 6 + adapt = (f & 0x30) >> 4 + self.continuity = f & 0xf + if self.error: + return + +class TSPacket: + def __init__(self, *p): + assert len(p) <= 1 + if len(p) == 0: + return + p = p[0] + origp = p + + assert len(p) == TSPKTLEN + assert p[0] == TSSYNC + + f = ord(p[1]) + self.error = bool(f & 0x80) + self.start = bool(f & 0x40) + self.priority = bool(f & 0x20) + self.pid = ((f & 0x1f) << 8) + ord(p[2]) + if self.pid == 0x1fff: + return + f = ord(p[3]) + self.scramble = (f & 0xc0) >> 6 + adapt = (f & 0x30) >> 4 + self.continuity = f & 0xf + if self.error: + return + i = 4 + if adapt >= 2: + adapt_len = ord(p[4]) + buf = p[i + 1:i + 1 + adapt_len] + assert len(buf) == adapt_len + self.decode_adaptation(buf) + # XXX - handle adpatation + i += 1 + adapt_len + self.payload = p[i:] + + def decode_adaptation(self, adp): + if len(adp) == 0: + return + + self.discontinuity_indicator = bool(ord(adp[0]) & 0x80) + self.random_access_indicator = bool(ord(adp[0]) & 0x40) + self.elementary_stream_priority = bool(ord(adp[0]) & 0x20) + PCR_flag = bool(ord(adp[0]) & 0x10) + OPCR_flag = bool(ord(adp[0]) & 0x08) + splicing_point_flag = bool(ord(adp[0]) & 0x04) + transport_private_data_flag = bool(ord(adp[0]) & 0x02) + adaptation_field_extension_flag = bool(ord(adp[0]) & 0x01) + + i = 1 + if PCR_flag: + self.PCR = read_clock(adp[i: i + 6]) + i += 6 + if OPCR_flag: + self.OPCR = read_clock(adp[i: i + 6]) + i += 6 + if splicing_point_flag: + self.splice_countdown = ord(adp[i]) + i += 1 + if transport_private_data_flag: + plen = ord(adp[i]) + self.private_data = adp[i + 1: i + 1 + plen] + i += 1 + plen + if adaptation_field_extension_flag: + alen = ord(adp[i]) + ltw_flag = ord(adp[i + 1]) & 0x80 + piecewise_rate_flag = ord(adp[i + 1]) & 0x40 + seamless_splice_flag = ord(adp[i + 1]) & 0x20 + i += 2 + if ltw_flag: + self.ltw_valid = bool(ord(adp[i]) & 0x80) + self.ltw_offset = ((ord(adp[i]) & 0x7f) << 8) |\ + ord(adp[i + 1]) + i += 2 + if piecewise_rate_flag: + self.piecewise_rate = ((ord(adp[i]) & 0x3f) << + 16) | (ord(adp[i + 1]) << 8) | \ + ord(adp[i + 2]) + i += 3 + if seamless_splice_flag: + self.splice_type = (ord(adp[i]) & 0xf0) >> 4 + self.DTS_next_AU = read_timestamp(adp[i:i + 5]) + + def __repr__(self): + v = ('pid', 'error', 'start', 'continuity', 'priority', + 'scramble', 'payload', 'discontinuity_indicator', + 'random_access_indicator', 'elementary_stream_priority', + 'PCR', 'OPCR', 'splice_countdown', 'private_data', + 'ltw_valid', 'ltw_offset', 'piecewise_rate', 'splice_type', + 'DTS_next_AU', ) + return '' % ', '.join(attribreprlist(self, v)) + + def __str__(self): + '''We may want to save the original packet, and return it until the +data gets modified.''' + if self.error: + return '%c%s' % (TSSYNC, '\xff' * 187) + sb = (self.pid >> 8) & 0x1f + if self.error: + sb |= 0x80 + if self.start: + sb |= 0x40 + if self.priority: + sb |= 0x20 + tb = self.pid & 0xff + fb = ((self.scramble & 0x3) << 6) | (self.continuity & 0xf) + alenstr = '' + if self.adaptation: + fb |= 1 << 5 + alenstr = chr(len(self.adaptation)) + if self.payload: + fb |= 1 << 4 + ret = '%c%c%c%c%s%s%s' % (TSSYNC, sb, tb, fb, alenstr, self.adaptation, self.payload) + if len(ret) != TSPKTLEN: + pass + #print >>sys.stderr, repr(self) + #print >>sys.stderr, len(self.adaptation), len(self.payload) + assert len(ret) == TSPKTLEN + return ret + +class TSPStream: + '''This class takes a file object, and outputs TS packets.''' + + def __init__(self, f): + self.f = f + + def __iter__(self): + foundsync = False + buf = self.f.read(READBLK) + while buf: + if not foundsync: + try: + start = buf.index(TSSYNC) + except ValueError: + buf = self.f.read(READBLK) + continue + + try: + if buf[start + TSPKTLEN] == '\x47': + #if start != 0: + # print >>sys.stderr, 'sync off:', start, 'data:', repr(buf[:start]) + foundsync = True + else: + #print >>sys.stderr, 'drop to sync:', start, 'data:', repr(buf[:start]) + buf = buf[start + 1:] + continue + except IndexError: + buf += self.f.read(READBLK) + + continue + + if buf[start] != '\x47': + #print >>sys.stderr, 'sync failed' + foundsync = False + continue + + t = buf[start:start + TSPKTLEN] + if len(t) != TSPKTLEN: + r = self.f.read(READBLK) + if not r: + #No more data + break + buf = buf[start:] + r + start = 0 + if not buf: + buf = self.f.read(READBLK) + continue + + yield t + buf = buf[start + TSPKTLEN:] + start = 0 + if not buf: + buf = self.f.read(READBLK) + +import getopt +import re +import sys + +def usage(): + print 'Usage: %s -lmty ' % sys.argv[0] + print ' %s -b [ -p ] ' % sys.argv[0] + print ' %s -c -o ' % sys.argv[0] + print '' + print ' -l list channels' + print ' -m print PAT and PMT' + print ' -t print TVCT' + print ' -y file offset when done' + +def findchannel(tvct, chan): + for i in tvct['channels']: + if isinstance(chan, int): + if i['minor'] == chan: + return i + elif isinstance(chan, tuple): + assert len(chan) == 2 + if i['major'] == chan[0] and i['minor'] == chan[1]: + return i + else: + if i['name'] == chan: + return i + + return None + +def GetTVCT(tsstream): + listchan = True + needtvct = True + + needpat = False + needpmts = {} + + pat = PAT() + pmts = {} + tvct = TVCT() + + psippids = { 0x00: TSPSIPHandler({ 0x00: pat }), + 0x1ffb: TSPSIPHandler({ 0xc8: tvct, }), + } + + def getpmt(pid, pm = pmts, psp = psippids): + if not pm.has_key(pid): + pm[pid] = PMT() + psp[pid] = TSPSIPHandler({ 0x02: pm[pid] }) + + def needpmts(pm = pmts): + for i in pm.itervalues(): + if not i: + return True + + return False + + for i in itertools.imap(TSPacket, tsstream): + try: + psippids[i.pid](i) + except ValueError: + continue + except KeyError: + pass + + # XXX - we need to give up finding the TVCT after a while, and + # pretend we found it w/ some defaults. KCSM doesn't + # broadcast a TVCT. + if needtvct and tvct: + needtvct = False + needpat = True + + if needpat and pat: + needpat = False + for j in pat.itervalues(): + getpmt(j) + + if not (needtvct or needpat or needpmts()): + break + + try: + lst = tvct['channels'] + lst.sort(channelmajorminorsort) + except KeyError: + # unable to find TVCT + lst = pat.items() + lst.sort() + lst = map(lambda x, y: { 'name': 'PAT%d' % x[1], 'prog_num': x[1], 'major': '?', 'minor': y}, lst, range(1, len(pat) + 1)) + tvct = { 'channels': lst } + + for i in lst: + if i['prog_num'] != 0: + i['PMT'] = pmts[pat[i['prog_num']]] + i['PMTpid'] = pat[i['prog_num']] + + return tvct + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], "bc:hlmo:pty") + except getopt.GetoptError: + # print help information and exit: + usage() + sys.exit(2) + + printbyteoffset = False + printbandwidth = False + printbandwidthpercent = False + listchan = False + channelsel = None + output = None + allmaps = False + printtvct = False + + needtvct = False + needpat = False + needpmts = {} + channelfnd = False + needallmaps = False + + for o, a in opts: + if o == '-b': + printbandwidth = True + elif o == "-c": + try: + channelsel = int(a) + except ValueError: + try: + channelsel = tuple(map(int, re.split('[-.]', a, 1))) + except ValueError: + channelsel = a + if channelsel is not None: + needpat = True + needtvct = True + elif o == "-m": + allmaps = True + needallmaps = True + needpat = True + elif o == '-t': + printtvct = True + needtvct = True + elif o == "-l": + listchan = True + needpat = True + needtvct = True + elif o in ("-h", "--help"): + usage() + sys.exit() + elif o == '-o': + output = file(a, 'w') + elif o == '-p': + printbandwidthpercent = True + elif o == '-y': + printbyteoffset = True + + if len(args) != 1: + usage() + sys.exit() + + inp = open(args[0]) + s = TSPStream(inp) + pat = PAT() + pmts = {} + tvct = TVCT() + + def null(p): + #print 'null', repr(p) + pass + null.clean_up = lambda: None + + psippids = { 0x00: TSPSIPHandler({ 0x00: pat }), + 0x1ffb: TSPSIPHandler({ + #0xc7: null, + 0xc8: tvct, + }), + } + pidcnt = {} + + def getpmt(pid, pm = pmts, psp = psippids): + if not pm.has_key(pid): + pm[pid] = PMT() + psp[pid] = TSPSIPHandler({ 0x02: pm[pid] }) + + def needpmts(pm = pmts): + for i in pm.itervalues(): + if not i: + return True + + 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, 'PCR'): + # print 'PCR:', repr(i) + #if i.pid in (48, 64, 80, 112): + # print `i` + + try: + psippids[i.pid](i) + except ValueError, x: + #import traceback + #print traceback.print_exc() + print >>sys.stderr, 'bad crc:', repr(i) + continue + except KeyError: + pass + + try: + pidcnt[i.pid] += 1 + except KeyError: + pidcnt[i.pid] = 1 + + # XXX - we need to give up finding the TVCT after a while, and + # pretend we found it w/ some defaults. KCSM doesn't + # broadcast a TVCT. + if needtvct and tvct: + # Handle TVCT + needtvct = False + + 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(): + if not pmts.has_key(i): + break + needallmaps = False + + #print repr(tvct), repr(pat), repr(pmts) + #print needtvct, needpat, needpmts(), printbandwidth, needallmaps + if not (needtvct or needpat or needpmts() or printbandwidth or needallmaps): + break + + if allmaps: + print repr(pat) + print repr(pmts) + + if printtvct: + print repr(tvct) + + if listchan: + #List channels + #try: + lst = tvct['channels'] + lst.sort(channelmajorminorsort) + #except KeyError: + # # unable to find TVCT + # sys.stderr.write("unable to find TVCT table, faking it.\n") + # lst = pat.items() + # lst.sort() + # lst = map(lambda x, y: { 'prog_num': x[1], 'major': '?', 'minor': y}, lst, range(1, len(pat) + 1)) + for i in lst: + if i['prog_num'] != 0: + #print repr(pmts[pat[i['prog_num']]]) + av = getaudiovideopids(pmts[pat[i['prog_num']]]) + prog_info = '\t'.join(map(lambda x: ','.join(map(str, x)), av)) + else: + prog_info = '' + print ('%(major)d.%(minor)d\t%(name)s\t' % i) + prog_info + + if printbandwidth: + totpkts = sum(pidcnt.itervalues()) + i = pidcnt.items() + if printbandwidthpercent: + def secondfirst(x, y): + if x[1] == y[1]: + return cmp(x[0], y[0]) + return cmp(x[1], y[1]) + i.sort(secondfirst) + else: + i.sort() + for pid, cnt in i: + print '%4d\t%d\t%5.2f' % (pid, cnt, float(cnt) * 100 / totpkts) + + if printbyteoffset: + print inp.tell() + +def justprint(v, p): + '''v is pid, p is the data''' + if v != 49: + return + pes = PES(p) + if pes.data[3] != '\x00': + print `pes` + return + fc = findcodes(pes.data) + print 'justprint', v, len(p), repr(pes), repr(pes.data[:20]), fc + for i in filter(lambda x: x[1] == '\x00', fc): + print `pes.data[i[0] + 3: i[0] + 7]` + if ((ord(pes.data[i[0] + 5]) & 0x38) >> 3) in (2, 3): + print 'non I frame found: %d' % ((ord(pes.data[i[0] + 5]) & 0x38) >> 3) + +if __name__ == '__main__': + if True: + main() + sys.exit(0) + + if False: + ps = UnRead(sys.argv[1]) + while ps: + print `Pack(ps)` + sys.exit() + + s = TSPStream(open(sys.argv[1])) + if False: + cleaned = open(sys.argv[2], 'w') + for j in s: + cleaned.write(str(j)) + continue + sys.exit() + + pids = {} + skipped = 0 + cont = {} + count = 0 + pat = PAT() + pmts = {} + pesstreams = {} + tvct = TVCT() + pidhandlers = { 0x00: TSPSIPHandler({ 0x00: pat }), + 0x1ffb: TSPSIPHandler({ 0xc8: tvct }), + } + first = last = None + for j in itertools.imap(TSPacket, s): + count += 1 + if j.pid == 8191 or (j.pid != 0 and j.pid != 48): + skipped += 1 + continue + else: + #if j.pid > 4000: + #print `j` + try: + pidhandlers[j.pid](j) + except KeyError: + pass + except ValueError, x: + print 'VE:', x + #if pidhandlers[0x1ffb][0xc8]: + # print repr(pidhandlers[0x1ffb][0xc8]) + # We should probably cache which ones we've added, and remove + # Ones that aren't there. Or do a clean_up callback. + for k in pidhandlers[0][0].itervalues(): + if pmts.has_key(k): + continue + pmts[k] = PMT() + pidhandlers[k] = TSPSIPHandler({ 0x02: pmts[k] }) + + for k in itertools.ifilter(lambda x: x.es, pmts.itervalues()): + #print repr(k) + for l in k.es: + if pesstreams.has_key(l[1]): + continue + print repr(l) + pesstreams[l[1]] = TSPESHandler(lambda x, y = l[1]: justprint(y, x)) + pidhandlers[l[1]] = pesstreams[l[1]] + + try: + if (cont[j.pid] + 1) % 16 != j.continuity: + pass + #print 'continuity failed' + cont[j.pid] = j.continuity + except KeyError: + cont[j.pid] = j.continuity + + try: + pids[j.pid] += 1 + except KeyError: + pids[j.pid] = 1 + p = pids.items() + p.sort() + print p + print 'skipped:', skipped + print 'total:', count diff --git a/mpegts/tssel.py b/mpegts/tssel.py new file mode 100644 index 0000000..8999f33 --- /dev/null +++ b/mpegts/tssel.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +import sys +sys.path.append('/Users/jgurney/p4/bktrau/info') + +import itertools +import mpegts +import sys +import sets + +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") + + patidx = 4 + 1 # TS header + pointer table + BASEPAT[patidx + 10] = chr(0xe0 | ((pmt >> 8) & 0x1f)) + BASEPAT[patidx + 11] = chr(pmt & 0xff) + newcrc = mpegts.psip_calc_crc32(''.join(BASEPAT[patidx:patidx + 12])) + newcrc = map(lambda x, crc = newcrc: chr((crc >> (8 * (3 - x))) & 0xff), range(4)) + BASEPAT[patidx + 12:patidx + 16] = newcrc + + assert len(BASEPAT) == mpegts.TSPKTLEN + + ret = [] + old = ord(BASEPAT[3]) & 0xf0 + for i in range(16): # continuity + BASEPAT[3] = chr(old | i) + ret.append(''.join(BASEPAT)) + + return ret + +def producets(inp, pmtpid, *pids): + print `inp`, `pmtpid`, `pids` + pats = itertools.cycle(genpats(pmtpid)) + # XXX - check if all pids are ints? in range? + pids = sets.Set(pids) + + stream = mpegts.TSPStream(inp) + 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: + yield pats.next() + # XXX - we probably want to rewrite the PMT to only + # include the pids we are sending. + yield i + elif pid in pids: + yield i + +def main(): + if len(sys.argv) < 3: + usage() + + pmtpid = int(sys.argv[2]) + pids = map(int, sys.argv[3:]) + + inp = open(sys.argv[1]) + out = sys.stdout + + producer = producets(inp, pmtpid, *pids) + filter(lambda x: out.write(x), producer) + +if __name__ == '__main__': + main() diff --git a/mpegtsmod.py b/mpegtsmod.py index 63fbe62..0863051 100644 --- a/mpegtsmod.py +++ b/mpegtsmod.py @@ -5,14 +5,14 @@ __version__ = '$Change$' # $Id$ -tsselpypath = '/Users/jgurney/p4/bktrau/info/tssel.py' +tsselpypath = 'mpegts/tssel.py' default_audio_lang = 'eng' import os import sets import sys -mpegtspath = '/Users/jgurney/p4/bktrau/info' +mpegtspath = 'mpegts' if mpegtspath not in sys.path: sys.path.append(mpegtspath) import mpegts