Python wrapping: first stab at reading PMTs -- find the next PMT.

--HG--
extra : convert_revision : svn%3Aeff31bef-be4a-0410-a8fe-e47997df2690/trunk%40101
issue20
tibs 2008-11-26 22:34:09 +00:00
rodzic 2af44b632b
commit 1d07e138fc
2 zmienionych plików z 212 dodań i 21 usunięć

Wyświetl plik

@ -509,6 +509,23 @@ Indeed, it will automatically be read in for us as we read its record(s):
PAT({1:0x20})
>>> f.close()
We can also find PMTs in a similar manner:
>>> f = TSFile(test_ts_file)
>>> num_read,pmt = f.find_PMT(0x20,1)
>>> num_read
441
>>> print pmt
PMT program 1, version 0, PCR PID 0030 (48)
>>> pmt.report()
PMT program 1, version 0, PCR PID 0030 (48)
Program info ''
Program streams:
PID 0031 ( 49) -> Stream type 02 ( 2)
ES info '\x52\x01\x00'
PID 0032 ( 50) -> Stream type 81 (129)
ES info '\x52\x01\x10'
// Local Variables:

Wyświetl plik

@ -187,6 +187,32 @@ cdef extern from 'es_fns.h':
class TSToolsException(Exception):
pass
def hexify(bytes):
"""Return a representation of an array of bytes as a hex values string.
"""
words = []
for val in bytes:
words.append('\\x%02x'%val)
return ''.join(words)
def hexify_string(bytes):
"""Return a representation of a Python string as a hex values string.
"""
words = []
for character in bytes:
words.append('\\x%02x'%ord(character))
return ''.join(words)
cdef hexify_byte_array(byte *bytes, int bytes_len):
"""Return a representation of a (byte) array as a hex values string.
Doesn't leave any spaces between hex bytes.
"""
words = []
for 0 <= ii < bytes_len:
words.append('\\x%02x'%bytes[ii])
return ''.join(words)
cdef class ESOffset:
"""An offset within an ES file.
@ -312,10 +338,7 @@ cdef class ESUnit:
return text
def __repr__(self):
words = []
for 0 <= ii < self.unit.data_len:
words.append('\\x%02x'%self.unit.data[ii])
return 'ESUnit("%s")'%(''.join(words))
return 'ESUnit("%s")'%hexify_byte_array(self.unit.data,self.unit.data_len)
cdef __set_es_unit(self, ES_unit_p unit):
if self.unit == NULL:
@ -547,9 +570,8 @@ cdef extern from "pidint_defns.h":
uint32_t PCR_pid
uint16_t program_info_length
byte *program_info
int streams_size
int num_streams
pmt_stream_p streams
pmt_stream *streams
ctypedef _pmt pmt
ctypedef _pmt *pmt_p
@ -620,6 +642,50 @@ class PAT(object):
words.append('%d:%#x'%(key,self._data[key]))
return 'PAT({%s})'%(','.join(words))
# XXX Should this be an extension type, and enforce the datatypes it can hold?
# XXX Or is that just too much bother?
class ProgramStream(object):
"""A program stream, within a PMT.
"""
def __init__(self,stream_type,elementary_PID,es_info):
self.stream_type = stream_type
self.elementary_PID = elementary_PID
self.es_info = es_info
def __str__(self):
"""Return a fairly compact and (relatively) self-explanatory format
"""
return "PID %04x (%4d) -> Stream type %02x (%3d) ES info '%s'"%(\
self.elementary_PID,
self.stream_type,
hexify_string(self.es_info))
def __repr__(self):
"""Return something we could be recreated from.
"""
return "ProgramStream(%#02x,%#04x,'%s')"%(self.stream_type,
self.elementary_PID,
hexify_string(self.es_info))
def formatted(self):
"""Return a representation that is similar to that returned by the C tools.
...not easy for program streams
"""
return self.__str__()
def report(self,indent=2):
print "%sPID %04x (%4d) -> Stream type %02x (%3d)"%(' '*indent,
self.elementary_PID,
self.elementary_PID,
self.stream_type,
self.stream_type)
# XXX should actually output them as descriptors
print "%s ES info '%s'"%(' '*indent,hexify_string(self.es_info))
# XXX Should this be an extension type, and enforce the datatypes it can hold?
# XXX Or is that just too much bother?
class PMT(object):
"""A Program Map Table.
@ -632,7 +698,56 @@ class PMT(object):
* key: elementary_PID
* value: (stream_type, ES_info)
"""
pass
def __init__(self,program_number,version_number,PCR_pid):
self.program_number = program_number
self.version_number = version_number
self.PCR_pid = PCR_pid
self.program_info = None
self.streams = []
def set_program_info(self,program_info):
"""Set our program_info bytes.
"""
self.program_info = program_info
def add_stream(self,stream):
"""Append a ProgramStream to our list of such.
"""
self.streams.append(stream)
def __str__(self):
# XXX Don't see what I can do aboout the program info and streams
return "PMT program %d, version %d, PCR PID %04x (%d)"%(self.program_number,
self.version_number,
self.PCR_pid,
self.PCR_pid)
def __repr__(self):
# XXX Don't see what I can do aboout the program streams
return "PMT(%d,%d,%#04x,'%s')"%(self.program_number,
self.version_number,
self.PCR_pid,
hexify_string(self.program_info))
def formatted(self):
"""Return a representation that is similar to that returned by the C tools.
...not easy for PMT
"""
return self.__str__()
def report(self):
print "PMT program %d, version %d, PCR PID %04x (%d)"%(self.program_number,
self.version_number,
self.PCR_pid,
self.PCR_pid)
# XXX should actually output them as descriptors
print " Program info '%s'"%hexify_string(self.program_info)
if self.streams:
print " Program streams:"
for stream in self.streams:
stream.report(indent=4)
cdef extern from "ts_fns.h":
int open_file_for_TS_read(char *filename, TS_reader_p *tsreader)
@ -648,7 +763,7 @@ cdef extern from "ts_fns.h":
byte **data, int *data_len, int *data_used)
int find_pat(TS_reader_p tsreader, int max, int verbose, int quiet,
int *num_read, pidint_list_p *prog_list)
int find_next_pmt(TS_reader_p tsreader, uint32_t pmt_pid,
int find_next_pmt(TS_reader_p tsreader, uint32_t pmt_pid, int program_number,
int max, int verbose, int quiet,
int *num_read, pmt_p *pmt)
int find_pmt(TS_reader_p tsreader, int max, int verbose, int quiet,
@ -723,10 +838,7 @@ cdef class TSPacket:
return text
def __repr__(self):
words = []
for val in self.data:
words.append('\\x%02x'%val)
return 'TSPacket("%s")'%(''.join(words))
return 'TSPacket("%s")'%hexify(self.data)
def __richcmp__(self,other,op):
if op == 2: # ==
@ -819,7 +931,8 @@ cdef class TSFile:
cdef readonly object name
cdef readonly object mode
cdef readonly object PAT # The latest PAT read, if any
cdef readonly object PAT # The latest PAT read, if any
cdef readonly object PMT_dict # A dictionary of {program number : PMT}
# We have a byte buffer in which we accumulate partial PAT parts,
# as we read TS packets
@ -848,6 +961,7 @@ cdef class TSFile:
# What should go in __init__ and what in __cinit__ ???
self.name = filename
self.mode = mode
self.PMT_dict = {}
def _clear_pat_data(self):
"""Clear the buffers we use to accumulate PAT data
@ -913,6 +1027,36 @@ cdef class TSFile:
finally:
free_pidint_list(&prog_list)
cdef _pmt_from_pmt_p(self, pmt_p pmt):
"""Dissect a PMT from C and store it.
XXX Should we remember the PMT's PID?
Returns the new PMT object, or None if none
"""
try:
this = PMT(pmt.program_number,
pmt.version_number,
pmt.PCR_pid)
prog_info = PyString_FromStringAndSize(<char *>pmt.program_info,
pmt.program_info_length)
this.set_program_info(prog_info)
for 0 <= ii < pmt.num_streams:
es_info = PyString_FromStringAndSize(<char *>pmt.streams[ii].ES_info,
pmt.streams[ii].ES_info_length)
stream = ProgramStream(pmt.streams[ii].stream_type,
pmt.streams[ii].elementary_PID,
es_info)
this.add_stream(stream)
# And remember it on the file as well
self.PMT_dict[pmt.program_number] = this
return this
finally:
free_pmt(&pmt)
cdef _check_pat(self, byte *buffer):
"""Check if the current buffer represents (another) part of a PAT
"""
@ -1065,17 +1209,47 @@ cdef class TSFile:
return (num_read,None)
elif retval == 1:
raise TSToolsException,'Error searching for next PAT'
#try:
# pat = PAT()
# for 0 <= ii < prog_list.length:
# pat[prog_list.number[ii]] = prog_list.pid[ii]
# # And remember it on the file as well
# self.PAT = pat
#finally:
# free_pidint_list(&prog_list)
self._pat_from_prog_list(prog_list)
return (num_read,self.PAT)
def find_PMT(self,pmt_pid,program_number=-1,max=0,verbose=False,quiet=False):
"""Read TS packets to find the (next) PMT with PID `pmt_pid`.
If `program_number` is 0 or more, then only a PMT with that program
number will do, otherwise any PMT of the given PID will be OK.
If non-zero, `max` is the maximum number of TS packets to scan forwards
whilst looking. If it is zero, there is no limit.
If `verbose` is True, then extra information is output. If `quiet` is
True, then the search will be as quiet as possible.
Returns (num_read, pmt), where `num_read` is how many TS packets were
read (whether the PMT is found or not), and `pmt` is None if no
appropriate PMT was found.
The new PMT is also saved as self.PMT[progno] (replacing, rather than
updating, any previous self.PMT[progno] object), where `progno` is the
actual program number of the PMT.
This method is more efficient than using repeated calls of ``read``,
because it uses the underlying C function to find the next PMT.
"""
cdef pmt_p pmt
cdef int num_read
cdef unsigned actual_prog_num
if self.tsreader == NULL:
raise TSToolsException,'No TS stream to read'
retval = find_next_pmt(self.tsreader,pmt_pid,program_number,max,verbose,quiet,
&num_read,&pmt)
if retval == EOF: # No PMT found
return (num_read,None)
elif retval == 1:
raise TSToolsException,'Error searching for next PMT'
this_pmt = self._pmt_from_pmt_p(pmt)
return (num_read,this_pmt)
def close(self):
## Since we don't appear to be able to call our __dealloc__ "method",
## and we're not allowed to call Python methods..