2008-07-30 22:07:47 +00:00
|
|
|
"""tstools.pyx -- Pyrex bindings for the TS tools
|
2008-08-25 18:22:01 +00:00
|
|
|
|
|
|
|
This is being developed on a Mac, running OS X. I expect that it will also
|
|
|
|
build on a Linux machine. I do not expect it to build (as it stands) on
|
|
|
|
Windows, as it is making assumptions that may not follow thereon.
|
|
|
|
|
|
|
|
It is my intent to worry about Windows after it works on the platforms that
|
|
|
|
I can test most easily!
|
2008-07-28 22:20:43 +00:00
|
|
|
"""
|
|
|
|
|
2008-07-31 10:09:59 +00:00
|
|
|
# ***** BEGIN LICENSE BLOCK *****
|
|
|
|
# Version: MPL 1.1
|
2008-09-17 20:34:42 +00:00
|
|
|
#
|
2008-07-31 10:09:59 +00:00
|
|
|
# The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
# 1.1 (the "License"); you may not use this file except in compliance with
|
|
|
|
# the License. You may obtain a copy of the License at
|
|
|
|
# http://www.mozilla.org/MPL/
|
2008-09-17 20:34:42 +00:00
|
|
|
#
|
2008-07-31 10:09:59 +00:00
|
|
|
# Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
# for the specific language governing rights and limitations under the
|
|
|
|
# License.
|
2008-09-17 20:34:42 +00:00
|
|
|
#
|
2008-07-31 10:09:59 +00:00
|
|
|
# The Original Code is the MPEG TS, PS and ES tools.
|
2008-09-17 20:34:42 +00:00
|
|
|
#
|
2008-07-31 10:09:59 +00:00
|
|
|
# The Initial Developer of the Original Code is Amino Communications Ltd.
|
|
|
|
# Portions created by the Initial Developer are Copyright (C) 2008
|
|
|
|
# the Initial Developer. All Rights Reserved.
|
2008-09-17 20:34:42 +00:00
|
|
|
#
|
2008-07-31 10:09:59 +00:00
|
|
|
# Contributor(s):
|
|
|
|
# Tibs (tibs@berlios.de)
|
2008-09-17 20:34:42 +00:00
|
|
|
#
|
2008-07-31 10:09:59 +00:00
|
|
|
# ***** END LICENSE BLOCK *****
|
|
|
|
|
2008-09-17 20:34:42 +00:00
|
|
|
import array
|
|
|
|
|
2008-07-28 22:20:43 +00:00
|
|
|
# If we're going to use definitions like this in more than one pyx file, we'll
|
|
|
|
# need to define the shared types in a .pxd file and use cimport to import
|
|
|
|
# them.
|
2008-08-26 20:37:15 +00:00
|
|
|
|
2008-07-28 22:20:43 +00:00
|
|
|
cdef extern from "stdio.h":
|
|
|
|
ctypedef struct FILE:
|
|
|
|
int _fileno
|
2008-08-18 21:43:53 +00:00
|
|
|
cdef enum:
|
|
|
|
EOF = -1
|
|
|
|
cdef FILE *stdout
|
2008-08-22 22:58:15 +00:00
|
|
|
# Associate a stream (returned) with an existing file descriptor.
|
|
|
|
# The specified mode must be compatible with the existing mode of
|
|
|
|
# the file descriptor. Closing the stream will close the descriptor
|
|
|
|
# as well.
|
|
|
|
cdef FILE *fdopen(int fildes, char *mode)
|
|
|
|
|
2008-08-24 18:48:40 +00:00
|
|
|
cdef FILE *fopen(char *path, char *mode)
|
|
|
|
cdef int fclose(FILE *stream)
|
|
|
|
cdef int fileno(FILE *stream)
|
|
|
|
|
|
|
|
cdef extern from "errno.h":
|
|
|
|
cdef int errno
|
|
|
|
|
|
|
|
cdef extern from "string.h":
|
|
|
|
cdef char *strerror(int errnum)
|
|
|
|
|
2008-08-25 18:22:01 +00:00
|
|
|
# Copied from the Pyrex documentation...
|
|
|
|
cdef extern from "Python.h":
|
2008-08-26 20:37:15 +00:00
|
|
|
# Return a new string object with a copy of the string v as value and
|
|
|
|
# length len on success, and NULL on failure. If v is NULL, the contents of
|
|
|
|
# the string are uninitialized.
|
2008-08-25 18:22:01 +00:00
|
|
|
object PyString_FromStringAndSize(char *v, int len)
|
2008-08-26 20:37:15 +00:00
|
|
|
|
|
|
|
# Return a NUL-terminated representation of the contents of the object obj
|
|
|
|
# through the output variables buffer and length.
|
|
|
|
#
|
|
|
|
# The function accepts both string and Unicode objects as input. For
|
|
|
|
# Unicode objects it returns the default encoded version of the object. If
|
|
|
|
# length is NULL, the resulting buffer may not contain NUL characters; if
|
|
|
|
# it does, the function returns -1 and a TypeError is raised.
|
|
|
|
#
|
|
|
|
# The buffer refers to an internal string buffer of obj, not a copy. The
|
|
|
|
# data must not be modified in any way, unless the string was just created
|
|
|
|
# using PyString_FromStringAndSize(NULL, size). It must not be deallocated.
|
|
|
|
# If string is a Unicode object, this function computes the default
|
|
|
|
# encoding of string and operates on that. If string is not a string object
|
|
|
|
# at all, PyString_AsStringAndSize() returns -1 and raises TypeError.
|
2008-08-25 18:22:01 +00:00
|
|
|
int PyString_AsStringAndSize(object obj, char **buffer, Py_ssize_t* length) except -1
|
|
|
|
|
2008-09-17 20:34:42 +00:00
|
|
|
# Returns a pointer to a read-only memory location containing arbitrary
|
|
|
|
# data. The obj argument must support the single-segment readable buffer
|
|
|
|
# interface. On success, returns 0, sets buffer to the memory location and
|
|
|
|
# buffer_len to the buffer length. Returns -1 and sets a TypeError on
|
|
|
|
# error.
|
|
|
|
int PyObject_AsReadBuffer(object obj, void **buffer, Py_ssize_t *buffer_len) except -1
|
|
|
|
# Unfortunately, that second argument is declared "const void **", which
|
|
|
|
# seems to mean ending up with grumbles from gcc when we can't declare a
|
|
|
|
# const void ** item...
|
|
|
|
|
2008-08-22 22:58:15 +00:00
|
|
|
cdef FILE *convert_python_file(object file):
|
|
|
|
"""Given a Python file object, return an equivalent stream.
|
|
|
|
There are *so many things* dodgy about doing this...
|
|
|
|
"""
|
|
|
|
cdef int fileno
|
|
|
|
cdef char *mode
|
|
|
|
cdef FILE *stream
|
|
|
|
fileno = file.fileno()
|
|
|
|
mode = file.mode
|
|
|
|
stream = fdopen(fileno, mode)
|
|
|
|
if stream == NULL:
|
|
|
|
raise TSToolsException, 'Error converting Python file to C FILE *'
|
|
|
|
else:
|
|
|
|
return stream
|
2008-07-28 22:20:43 +00:00
|
|
|
|
2008-08-25 18:22:01 +00:00
|
|
|
cdef extern from "stdint.h":
|
|
|
|
ctypedef int uint8_t # !!! *some* sort of int..
|
2008-09-04 21:20:52 +00:00
|
|
|
ctypedef int uint32_t # !!! *some* sort of int..
|
|
|
|
|
|
|
|
# PIDs are too long for 16 bits, short enough to fit in 32
|
|
|
|
ctypedef uint32_t PID
|
2008-08-25 18:22:01 +00:00
|
|
|
|
2008-08-20 22:11:27 +00:00
|
|
|
cdef extern from "compat.h":
|
|
|
|
# We don't need to define 'offset_t' exactly, just to let Pyrex
|
|
|
|
# know it's vaguely int-like
|
|
|
|
ctypedef int offset_t
|
2008-08-25 18:22:01 +00:00
|
|
|
ctypedef uint8_t byte
|
2008-08-18 22:52:39 +00:00
|
|
|
|
2008-07-28 22:20:43 +00:00
|
|
|
cdef extern from 'es_defns.h':
|
|
|
|
# The reader for an ES file
|
|
|
|
struct elementary_stream:
|
|
|
|
pass
|
2008-08-18 22:52:39 +00:00
|
|
|
|
2008-07-28 22:20:43 +00:00
|
|
|
ctypedef elementary_stream ES
|
|
|
|
ctypedef elementary_stream *ES_p
|
|
|
|
|
2008-08-20 22:11:27 +00:00
|
|
|
# A location within said stream
|
|
|
|
struct _ES_offset:
|
|
|
|
offset_t infile # as used by lseek
|
|
|
|
int inpacket # in PES file, offset within PES packet
|
|
|
|
|
|
|
|
ctypedef _ES_offset ES_offset
|
|
|
|
|
2008-07-28 22:20:43 +00:00
|
|
|
# An actual ES unit
|
|
|
|
struct ES_unit:
|
2008-08-20 22:11:27 +00:00
|
|
|
ES_offset start_posn
|
|
|
|
byte *data
|
|
|
|
unsigned data_len
|
|
|
|
unsigned data_size
|
|
|
|
byte start_code
|
|
|
|
byte PES_had_PTS
|
2008-08-18 22:52:39 +00:00
|
|
|
|
2008-07-28 22:20:43 +00:00
|
|
|
ctypedef ES_unit *ES_unit_p
|
|
|
|
|
|
|
|
cdef extern from 'es_fns.h':
|
|
|
|
int open_elementary_stream(char *filename, ES_p *es)
|
2008-07-31 09:28:23 +00:00
|
|
|
void close_elementary_stream(ES_p *es)
|
2008-08-22 22:58:15 +00:00
|
|
|
|
|
|
|
int build_elementary_stream_file(int input, ES_p *es)
|
|
|
|
void free_elementary_stream(ES_p *es)
|
|
|
|
|
2008-07-28 22:20:43 +00:00
|
|
|
int find_and_build_next_ES_unit(ES_p es, ES_unit_p *unit)
|
|
|
|
void free_ES_unit(ES_unit_p *unit)
|
|
|
|
void report_ES_unit(FILE *stream, ES_unit_p unit)
|
|
|
|
|
2008-08-22 22:58:15 +00:00
|
|
|
# We perhaps need a Python object to represent an ES_offset?
|
|
|
|
# Otherwise, it's going to be hard to use them within Python itself
|
2008-08-20 22:11:27 +00:00
|
|
|
int seek_ES(ES_p es, ES_offset where)
|
|
|
|
int compare_ES_offsets(ES_offset offset1, ES_offset offset2)
|
|
|
|
|
2008-08-22 22:58:15 +00:00
|
|
|
# I'd like to be able to *write* ES files, so...
|
|
|
|
# Python file objects can return a file descriptor (i.e., integer)
|
|
|
|
# via their fileno() method, so the simplest thing to do may be to
|
|
|
|
# add a new C function that uses write() instead of fwrite(). Or I
|
|
|
|
# could use fdopen to turn the fileno() into a FILE *...
|
2008-08-26 20:37:15 +00:00
|
|
|
int build_ES_unit_from_data(ES_unit_p *unit, byte *data, unsigned data_len)
|
2008-08-22 22:58:15 +00:00
|
|
|
int write_ES_unit(FILE *output, ES_unit_p unit)
|
|
|
|
|
2008-08-20 22:11:27 +00:00
|
|
|
|
2008-07-28 22:20:43 +00:00
|
|
|
# Is this the best thing to do?
|
|
|
|
class TSToolsException(Exception):
|
|
|
|
pass
|
2008-08-18 21:43:53 +00:00
|
|
|
|
2008-08-23 21:15:29 +00:00
|
|
|
cdef same_ES_unit(ES_unit_p this, ES_unit_p that):
|
|
|
|
"""Two ES units do not need to be at the same place to be the same.
|
|
|
|
"""
|
|
|
|
if this.data_len != that.data_len:
|
|
|
|
return False
|
|
|
|
for 0 <= ii < this.data_len:
|
|
|
|
if this.data[ii] != that.data[ii]:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2008-08-26 21:44:05 +00:00
|
|
|
cdef class ESOffset:
|
|
|
|
"""An offset within an ES file.
|
|
|
|
|
|
|
|
If the ES unit was read directly from a raw ES file, then a simple file
|
|
|
|
offset is sufficient.
|
|
|
|
|
|
|
|
However, if we're reading from a PS or TS file (via the PES reading layer),
|
|
|
|
then we have the offset of the PES packet, and then the offset of the ES
|
|
|
|
unit therein.
|
|
|
|
|
|
|
|
We *could* just use a tuple for this, but it's nice to have a bit more
|
|
|
|
documentation self-evident.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Keep the original names, even though they're not very Pythonic
|
|
|
|
cdef readonly long long infile # Hoping this is 64 bit...
|
|
|
|
cdef readonly int inpacket
|
|
|
|
|
2008-08-28 20:33:47 +00:00
|
|
|
def __cinit__(self, infile=0, inpacket=0):
|
2008-08-26 21:44:05 +00:00
|
|
|
self.infile = infile
|
|
|
|
self.inpacket = inpacket
|
|
|
|
|
2008-08-28 20:33:47 +00:00
|
|
|
def __init__(self, infile=0, inpacket=0):
|
2008-08-26 21:44:05 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
def __repr__(self):
|
2008-08-28 20:33:47 +00:00
|
|
|
"""Return a fairly compact and (relatively) self-explanatory format
|
|
|
|
"""
|
|
|
|
return '%d+%d'%(self.infile,self.inpacket)
|
|
|
|
|
|
|
|
def formatted(self):
|
|
|
|
"""Return a representation that is similar to that returned by the C tools.
|
|
|
|
|
|
|
|
Beware that this is <inpacket>+<infile>, which is reversed from the ``repr``.
|
|
|
|
"""
|
2008-08-26 21:44:05 +00:00
|
|
|
return '%08d/%08d'%(self.inpacket,self.infile)
|
|
|
|
|
|
|
|
def report(self):
|
|
|
|
print 'Offset %d in packet at offset %d in file'%(self.inpacket,self.infile)
|
|
|
|
|
|
|
|
def __cmp__(self,other):
|
|
|
|
if self.infile > other.infile:
|
|
|
|
return 1
|
|
|
|
elif self.infile < other.infile:
|
|
|
|
return -1
|
|
|
|
elif self.inpacket > other.inpacket:
|
|
|
|
return 1
|
|
|
|
elif self.inpacket < other.inpacket:
|
|
|
|
return -1
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
2008-08-23 21:15:29 +00:00
|
|
|
cdef class ESUnit # Forward declaration
|
|
|
|
cdef object compare_ESUnits(ESUnit this, ESUnit that, int op):
|
|
|
|
"""op is 2 for ==, 3 for !=, other values not allowed.
|
|
|
|
"""
|
|
|
|
if op == 2: # ==
|
|
|
|
return same_ES_unit(this.unit, that.unit)
|
|
|
|
elif op == 3: # !=
|
|
|
|
return not same_ES_unit(this.unit, that.unit)
|
|
|
|
else:
|
|
|
|
#return NotImplemented
|
|
|
|
raise TypeError, 'ESUnit only supports == and != comparisons'
|
2008-08-18 21:43:53 +00:00
|
|
|
|
|
|
|
cdef class ESUnit:
|
|
|
|
"""A Python class representing an ES unit.
|
|
|
|
"""
|
|
|
|
|
|
|
|
cdef ES_unit_p unit
|
|
|
|
|
|
|
|
# It appears to be recommended to make __cinit__ expand to take more
|
|
|
|
# arguments (if __init__ ever gains them), since both get the same
|
|
|
|
# things passed to them. Hmm, normally I'd trust myself, but let's
|
|
|
|
# try the recommended route
|
2008-08-26 21:44:05 +00:00
|
|
|
def __cinit__(self, data=None, *args,**kwargs):
|
2008-08-26 20:37:15 +00:00
|
|
|
cdef char *buffer
|
|
|
|
cdef Py_ssize_t length
|
|
|
|
if data:
|
|
|
|
PyString_AsStringAndSize(data, &buffer, &length)
|
|
|
|
retval = build_ES_unit_from_data(&self.unit, <byte *>buffer, length);
|
|
|
|
if retval < 0:
|
|
|
|
raise TSToolsException,'Error building ES unit from Python string'
|
|
|
|
|
|
|
|
def __init__(self,data=None):
|
2008-08-18 21:43:53 +00:00
|
|
|
pass
|
|
|
|
|
2008-08-20 22:11:27 +00:00
|
|
|
def report(self):
|
|
|
|
"""Report (briefly) on an ES unit. This write to C stdout, which means
|
|
|
|
that Python has no control over the output. A proper Python version of
|
|
|
|
this will be provided eventually.
|
|
|
|
"""
|
2008-08-18 21:43:53 +00:00
|
|
|
report_ES_unit(stdout, self.unit)
|
|
|
|
|
|
|
|
def __dealloc__(self):
|
|
|
|
free_ES_unit(&self.unit)
|
|
|
|
|
|
|
|
def __repr__(self):
|
2008-08-26 20:37:15 +00:00
|
|
|
text = 'ES unit: start code %02x, len %4d:'%(self.unit.start_code,
|
|
|
|
self.unit.data_len)
|
|
|
|
for 0 <= ii < min(self.unit.data_len,8):
|
|
|
|
text += ' %02x'%self.unit.data[ii]
|
2008-09-02 12:18:15 +00:00
|
|
|
|
|
|
|
if self.unit.data_len == 9:
|
|
|
|
text += ' %02x'%self.unit.data[8]
|
|
|
|
elif self.unit.data_len > 9:
|
|
|
|
text += '...'
|
2008-08-26 20:37:15 +00:00
|
|
|
return text
|
2008-08-18 21:43:53 +00:00
|
|
|
|
|
|
|
cdef __set_es_unit(self, ES_unit_p unit):
|
|
|
|
if self.unit == NULL:
|
|
|
|
raise ValueError,'ES unit already defined'
|
|
|
|
else:
|
|
|
|
self.unit = unit
|
|
|
|
|
2008-08-23 21:15:29 +00:00
|
|
|
def __richcmp__(self,other,op):
|
|
|
|
return compare_ESUnits(self,other,op)
|
|
|
|
|
2008-08-25 18:22:01 +00:00
|
|
|
def __getattr__(self,name):
|
|
|
|
if name == 'start_posn':
|
2008-08-26 21:44:05 +00:00
|
|
|
return ESOffset(self.unit.start_posn.infile,
|
|
|
|
self.unit.start_posn.inpacket)
|
2008-08-25 18:22:01 +00:00
|
|
|
elif name == 'data':
|
|
|
|
# Cast the first parameter so that the C compiler is happy
|
|
|
|
# when compiling the (derived) tstools.c
|
|
|
|
return PyString_FromStringAndSize(<char *>self.unit.data, self.unit.data_len)
|
|
|
|
elif name == 'start_code':
|
|
|
|
return self.unit.start_code
|
|
|
|
elif name == 'PES_had_PTS':
|
|
|
|
return self.unit.PES_had_PTS
|
|
|
|
else:
|
|
|
|
raise AttributeError
|
|
|
|
|
2008-08-18 21:43:53 +00:00
|
|
|
# Is this the simplest way? Since it appears that a class method
|
|
|
|
# doesn't want to take a non-Python item as an argument...
|
|
|
|
cdef _next_ESUnit(ES_p stream, filename):
|
|
|
|
cdef ES_unit_p unit
|
2008-08-22 22:58:15 +00:00
|
|
|
# The C function assumes it has a valid ES stream passed to it
|
|
|
|
# = I don't think we're always called with such
|
|
|
|
if stream == NULL:
|
|
|
|
raise TSToolsException,'No ES stream to read'
|
|
|
|
|
2008-08-18 21:43:53 +00:00
|
|
|
retval = find_and_build_next_ES_unit(stream, &unit)
|
|
|
|
if retval == EOF:
|
|
|
|
raise StopIteration
|
|
|
|
elif retval != 0:
|
|
|
|
raise TSToolsException,'Error getting next ES unit from file %s'%filename
|
|
|
|
|
2008-08-26 21:44:05 +00:00
|
|
|
# I'd like to be able to do:
|
|
|
|
# return ESUnit(unit=unit)
|
|
|
|
# but it's not possible to pass anything other than a Python object
|
|
|
|
# to methods, and an ES_unit_p is not (which is the point of what we're
|
|
|
|
# doing!). I could take the innards of the ES unit, and pass them as
|
|
|
|
# individual arguments, appropriately mangled, but that seems a bit like
|
|
|
|
# overkill when the (rather inelegant but at least hidden in this factory
|
|
|
|
# method) approach below actually works.
|
|
|
|
# Maybe I'll figure out something better as I learn more about Pyrex.
|
|
|
|
|
2008-08-20 22:11:27 +00:00
|
|
|
# From http://www.philhassey.com/blog/2007/12/05/pyrex-from-confusion-to-enlightenment/
|
|
|
|
# Pyrex doesn't do type inference, so it doesn't detect that 'u' is allowed
|
|
|
|
# to hold an ES_unit_p. It's up to us to *tell* it, specifically, what type
|
|
|
|
# 'u' is going to be.
|
2008-08-18 21:43:53 +00:00
|
|
|
cdef ESUnit u
|
|
|
|
u = ESUnit()
|
|
|
|
u.unit = unit
|
|
|
|
return u
|
|
|
|
|
2008-08-24 18:48:40 +00:00
|
|
|
cdef class ESFile:
|
2008-08-22 22:58:15 +00:00
|
|
|
"""A Python class representing an ES stream.
|
|
|
|
|
|
|
|
We support opening for read, or opening (creating) a new file
|
2008-08-24 18:48:40 +00:00
|
|
|
for write. For the moment, we don't support appending, and
|
|
|
|
support for trying to read and write the same file is undefined.
|
2008-08-22 22:58:15 +00:00
|
|
|
|
2008-08-24 18:48:40 +00:00
|
|
|
So, create a new ESFile as either:
|
2008-08-22 22:58:15 +00:00
|
|
|
|
2008-08-24 18:48:40 +00:00
|
|
|
* ESFile(filename,'r') or
|
|
|
|
* ESFile(filename,'w')
|
2008-08-22 22:58:15 +00:00
|
|
|
|
|
|
|
Note that there is always an implicit 'b' attached to the mode (i.e., the
|
|
|
|
file is accessed in binary mode).
|
2008-07-28 22:20:43 +00:00
|
|
|
"""
|
|
|
|
|
2008-08-22 22:58:15 +00:00
|
|
|
cdef FILE *file_stream # The corresponding C file stream
|
2008-08-24 18:48:40 +00:00
|
|
|
cdef int fileno # and file number
|
2008-08-22 22:58:15 +00:00
|
|
|
cdef ES_p stream # For reading an existing ES stream
|
2008-08-24 18:48:40 +00:00
|
|
|
cdef readonly object name
|
2008-08-22 22:58:15 +00:00
|
|
|
cdef readonly object mode
|
2008-07-28 22:20:43 +00:00
|
|
|
|
|
|
|
# It appears to be recommended to make __cinit__ expand to take more
|
|
|
|
# arguments (if __init__ ever gains them), since both get the same
|
|
|
|
# things passed to them. Hmm, normally I'd trust myself, but let's
|
|
|
|
# try the recommended route
|
2008-08-22 22:58:15 +00:00
|
|
|
def __cinit__(self,filename,mode='r',*args,**kwargs):
|
2008-08-24 18:48:40 +00:00
|
|
|
actual_mode = mode+'b'
|
|
|
|
self.file_stream = fopen(filename,mode)
|
|
|
|
if self.file_stream == NULL:
|
|
|
|
raise TSToolsException,"Error opening file '%s'"\
|
|
|
|
" with (actual) mode '%s': %s"%(filename,mode,strerror(errno))
|
|
|
|
self.fileno = fileno(self.file_stream)
|
2008-08-22 22:58:15 +00:00
|
|
|
if mode == 'r':
|
2008-08-24 18:48:40 +00:00
|
|
|
retval = build_elementary_stream_file(self.fileno,&self.stream)
|
2008-08-22 22:58:15 +00:00
|
|
|
if retval != 0:
|
2008-08-24 18:48:40 +00:00
|
|
|
raise TSToolsException,'Error attaching elementary stream to file %s'%filename
|
2008-08-22 22:58:15 +00:00
|
|
|
|
|
|
|
def __init__(self,filename,mode='r'):
|
|
|
|
# What should go in __init__ and what in __cinit__ ???
|
2008-08-24 18:48:40 +00:00
|
|
|
self.name = filename
|
2008-08-22 22:58:15 +00:00
|
|
|
self.mode = mode
|
2008-07-28 22:20:43 +00:00
|
|
|
|
|
|
|
def __dealloc__(self):
|
2008-08-24 18:48:40 +00:00
|
|
|
if self.file_stream != NULL:
|
|
|
|
retval = fclose(self.file_stream)
|
|
|
|
if retval != 0:
|
|
|
|
raise TSToolsException,"Error closing file '%s':"\
|
2008-09-04 21:20:52 +00:00
|
|
|
" %s"%(self.name,strerror(errno))
|
2008-08-22 22:58:15 +00:00
|
|
|
if self.stream != NULL:
|
|
|
|
free_elementary_stream(&self.stream)
|
2008-08-18 21:43:53 +00:00
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
return self
|
|
|
|
|
2008-08-22 22:58:15 +00:00
|
|
|
def is_readable(self):
|
|
|
|
"""This is a convenience method, whilst reading and writing are exclusive.
|
|
|
|
"""
|
|
|
|
return self.mode == 'r' and self.stream != NULL
|
|
|
|
|
|
|
|
def is_writable(self):
|
|
|
|
"""This is a convenience method, whilst reading and writing are exclusive.
|
|
|
|
"""
|
|
|
|
return self.mode == 'w' and self.file_stream != NULL
|
|
|
|
|
2008-08-18 21:43:53 +00:00
|
|
|
# For Pyrex classes, we define a __next__ instead of a next method
|
|
|
|
# in order to form our iterator
|
|
|
|
def __next__(self):
|
|
|
|
"""Our iterator interface retrieves the ES units from the stream.
|
|
|
|
"""
|
2008-08-24 18:48:40 +00:00
|
|
|
return _next_ESUnit(self.stream,self.name)
|
2008-08-22 22:58:15 +00:00
|
|
|
|
2008-08-27 22:16:02 +00:00
|
|
|
def seek(self,*args):
|
|
|
|
"""Seek to the given 'offset', which should be the start of an ES unit.
|
|
|
|
|
|
|
|
'offset' may be a single integer (if the file is a raw ES file), an
|
|
|
|
ESOffset (for any sort of ES file), or a tuple of (infile,inpacket)
|
|
|
|
|
2008-08-28 20:33:47 +00:00
|
|
|
Returns an ESOffset according to where it sought to.
|
2008-08-27 22:16:02 +00:00
|
|
|
"""
|
|
|
|
cdef ES_offset where
|
|
|
|
try:
|
|
|
|
if len(args) == 1:
|
|
|
|
try:
|
|
|
|
where.infile = args[0].infile
|
|
|
|
where.inpacket = args[0].inpacket
|
|
|
|
except:
|
|
|
|
where.infile = args[0]
|
|
|
|
where.inpacket = 0
|
|
|
|
elif len(args) == 2:
|
|
|
|
where.infile, where.inpacket = args
|
|
|
|
else:
|
|
|
|
raise TypeError
|
|
|
|
except:
|
|
|
|
raise TypeError,'Seek argument must be one integer, two integers or an ESOffset'
|
|
|
|
retval = seek_ES(self.stream,where)
|
|
|
|
if retval != 0:
|
|
|
|
raise TSToolsException,"Error seeking to %s in file '%s'"%(args,self.name)
|
|
|
|
else:
|
2008-08-28 20:33:47 +00:00
|
|
|
return ESOffset(where.infile,where.inpacket)
|
2008-08-27 22:16:02 +00:00
|
|
|
|
2008-08-24 18:48:40 +00:00
|
|
|
def read(self):
|
|
|
|
"""Read the next ES unit from this stream.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
return _next_ESUnit(self.stream,self.name)
|
|
|
|
except StopIteration:
|
|
|
|
raise EOFError
|
|
|
|
|
|
|
|
def write(self, ESUnit unit):
|
|
|
|
"""Write an ES unit to this stream.
|
|
|
|
"""
|
|
|
|
if self.file_stream == NULL:
|
|
|
|
raise TSToolsException,'ESFile does not seem to have been opened for write'
|
|
|
|
|
|
|
|
retval = write_ES_unit(self.file_stream,unit.unit)
|
|
|
|
if retval != 0:
|
|
|
|
raise TSToolsException,'Error writing ES unit to file %s'%self.name
|
2008-08-23 21:15:29 +00:00
|
|
|
|
2008-08-22 22:58:15 +00:00
|
|
|
def close(self):
|
|
|
|
# Apparently we can't call the __dealloc__ method itself,
|
|
|
|
# but I think this is sensible to do here...
|
2008-08-24 18:48:40 +00:00
|
|
|
if self.file_stream != NULL:
|
|
|
|
retval = fclose(self.file_stream)
|
|
|
|
if retval != 0:
|
|
|
|
raise TSToolsException,"Error closing file '%s':"\
|
|
|
|
" %s"%(filename,strerror(errno))
|
2008-08-22 22:58:15 +00:00
|
|
|
if self.stream != NULL:
|
|
|
|
free_elementary_stream(&self.stream)
|
2008-08-24 18:48:40 +00:00
|
|
|
# And obviously we're not available any more
|
|
|
|
self.file_stream = NULL
|
|
|
|
self.fileno = -1
|
|
|
|
self.name = None
|
|
|
|
self.mode = None
|
2008-09-02 12:18:15 +00:00
|
|
|
|
2008-09-03 09:22:43 +00:00
|
|
|
def __enter__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, etype, value, tb):
|
|
|
|
if tb is None:
|
|
|
|
# No exception, so just finish normally
|
|
|
|
self.close()
|
|
|
|
else:
|
|
|
|
# Exception occurred, so tidy up
|
|
|
|
self.close()
|
|
|
|
# And allow the exception to be re-raised
|
|
|
|
return False
|
|
|
|
|
2008-09-04 21:20:52 +00:00
|
|
|
cdef extern from "ts_defns.h":
|
|
|
|
struct _ts_reader:
|
|
|
|
pass
|
|
|
|
ctypedef _ts_reader TS_reader
|
|
|
|
ctypedef _ts_reader *TS_reader_p
|
|
|
|
|
|
|
|
cdef extern from "ts_fns.h":
|
|
|
|
int open_file_for_TS_read(char *filename, TS_reader_p *tsreader)
|
|
|
|
int close_TS_reader(TS_reader_p *tsreader)
|
|
|
|
int seek_using_TS_reader(TS_reader_p tsreader, offset_t posn)
|
|
|
|
int read_next_TS_packet(TS_reader_p tsreader, byte **packet)
|
2008-09-06 17:41:26 +00:00
|
|
|
int split_TS_packet(byte *buf, PID *pid, int *payload_unit_start_indicator,
|
2008-09-04 21:20:52 +00:00
|
|
|
byte **adapt, int *adapt_len,
|
|
|
|
byte **payload, int *payload_len)
|
|
|
|
int get_next_TS_packet(TS_reader_p tsreader,
|
2008-09-06 17:41:26 +00:00
|
|
|
PID *pid, int *payload_unit_start_indicator,
|
2008-09-04 21:20:52 +00:00
|
|
|
byte **adapt, int *adapt_len,
|
|
|
|
byte **payload, int *payload_len)
|
|
|
|
|
2008-09-06 17:41:26 +00:00
|
|
|
DEF TS_PACKET_LEN = 188
|
2008-09-04 21:20:52 +00:00
|
|
|
|
|
|
|
cdef class TSPacket:
|
|
|
|
"""A convenient representation of a (dissected) TS packet.
|
|
|
|
"""
|
|
|
|
|
2008-09-17 20:34:42 +00:00
|
|
|
cdef readonly object data
|
2008-09-04 21:20:52 +00:00
|
|
|
cdef readonly PID pid
|
|
|
|
|
2008-09-17 20:34:42 +00:00
|
|
|
# The following are lazily calculated if necessary
|
|
|
|
cdef byte _already_split
|
|
|
|
cdef int _pusi # payload unit start indicator
|
|
|
|
cdef object _adapt
|
|
|
|
cdef object _payload
|
|
|
|
|
|
|
|
def __cinit__(self,buffer,*args,**kwargs):
|
|
|
|
"""The buffer *must* be 188 bytes long, by definition.
|
|
|
|
"""
|
|
|
|
# An array is easier to access than a string, can can be initialised
|
|
|
|
# from any sensible sequence. This may not be the most efficient thing
|
|
|
|
# to do, though, so later on we might want to consider ways of iterating
|
|
|
|
# over TS entries in a file without needing to create TS packets...
|
|
|
|
self.data = array.array('B',buffer)
|
|
|
|
# We *really* believe that the first character had better be 0x47...
|
|
|
|
if self.data[0] != 0x47:
|
|
|
|
raise TSToolsException,\
|
|
|
|
'First byte of TS packet is %#02x, not 0x47'%(ord(buffer[0]))
|
|
|
|
# And the length is, well, defined
|
|
|
|
if len(self.data) != TS_PACKET_LEN:
|
|
|
|
raise TSToolsException,\
|
|
|
|
'TS packet is %d bytes long, not %d'%(len(self.data))
|
|
|
|
# The PID is useful to know early on, and fairly easy to work out
|
|
|
|
self.pid = ((ord(buffer[1]) & 0x1F) << 8) | ord(buffer[2])
|
2008-09-06 17:41:26 +00:00
|
|
|
|
|
|
|
def __init__(self,pid=None,pusi=None,adapt=None,payload=None,data=None):
|
|
|
|
pass
|
|
|
|
|
2008-09-17 20:34:42 +00:00
|
|
|
def __dealloc__(self):
|
2008-09-06 17:41:26 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
def is_padding(self):
|
|
|
|
return self.pid == 0x1fff
|
|
|
|
|
|
|
|
def __repr__(self):
|
2008-09-17 20:34:42 +00:00
|
|
|
self._split()
|
|
|
|
text = 'TS packet PID %04x '%self.pid
|
2008-09-06 17:41:26 +00:00
|
|
|
if self.pusi:
|
2008-09-17 20:34:42 +00:00
|
|
|
text += '[pusi] '
|
2008-09-06 17:41:26 +00:00
|
|
|
if self.adapt and self.payload:
|
2008-09-17 20:34:42 +00:00
|
|
|
text += 'A+P '
|
2008-09-06 17:41:26 +00:00
|
|
|
elif self.adapt:
|
2008-09-17 20:34:42 +00:00
|
|
|
text += 'A '
|
2008-09-06 17:41:26 +00:00
|
|
|
elif self.payload:
|
2008-09-17 20:34:42 +00:00
|
|
|
text += 'P '
|
|
|
|
data = self.data[3:11]
|
|
|
|
words = []
|
|
|
|
for val in data:
|
|
|
|
words.append('%02x'%val)
|
|
|
|
text += ' '.join(words) + '...'
|
2008-09-06 17:41:26 +00:00
|
|
|
return text
|
|
|
|
|
|
|
|
def __richcmp__(self,other,op):
|
|
|
|
if op == 2: # ==
|
2008-09-17 20:34:42 +00:00
|
|
|
return self.data == other.data
|
2008-09-06 17:41:26 +00:00
|
|
|
elif op == 3: # !=
|
2008-09-17 20:34:42 +00:00
|
|
|
return self.data != other.data
|
2008-09-06 17:41:26 +00:00
|
|
|
else:
|
|
|
|
#return NotImplemented
|
|
|
|
raise TypeError, 'TSPacket only supports == and != comparisons'
|
2008-09-04 21:20:52 +00:00
|
|
|
|
2008-09-17 20:34:42 +00:00
|
|
|
def _split(self):
|
|
|
|
"""Split the packet up when requested to do so.
|
|
|
|
"""
|
|
|
|
cdef void *buffer
|
|
|
|
cdef Py_ssize_t length
|
|
|
|
cdef PID pid
|
|
|
|
cdef char *adapt_buf
|
|
|
|
cdef int adapt_len
|
|
|
|
cdef char *payload_buf
|
|
|
|
cdef int payload_len
|
|
|
|
if not self._already_split:
|
|
|
|
PyObject_AsReadBuffer(self.data, &buffer, &length)
|
|
|
|
retval = split_TS_packet(<byte *>buffer,&pid,&self._pusi,
|
|
|
|
<byte **>&adapt_buf,&adapt_len,
|
|
|
|
<byte **>&payload_buf,&payload_len)
|
|
|
|
if retval < 0:
|
|
|
|
raise TSToolsException,'Error splitting TS packet data'
|
|
|
|
if adapt_len == 0:
|
|
|
|
self._adapt = None
|
|
|
|
else:
|
|
|
|
self._adapt = PyString_FromStringAndSize(adapt_buf,adapt_len)
|
|
|
|
if payload_len == 0:
|
|
|
|
self._payload = None
|
|
|
|
else:
|
|
|
|
self._payload = PyString_FromStringAndSize(payload_buf,payload_len)
|
|
|
|
self._already_split = True
|
|
|
|
|
|
|
|
def __getattr__(self,name):
|
|
|
|
self._split()
|
|
|
|
if name == 'pusi':
|
|
|
|
return self._pusi
|
|
|
|
elif name == 'adapt':
|
|
|
|
return self._adapt
|
|
|
|
elif name == 'payload':
|
|
|
|
return self._payload
|
|
|
|
else:
|
|
|
|
raise AttributeError
|
|
|
|
|
2008-09-07 19:00:29 +00:00
|
|
|
# Not a method, honest
|
|
|
|
# (__dealloc__ is not allowed to call Python methods, just in case,
|
|
|
|
# and Python methods don't seem to be allowed to call __dealloc__,
|
|
|
|
# since I assume it's not a "real" method)
|
|
|
|
cdef _TSFile_close_for_read(TS_reader_p *tsreader):
|
|
|
|
if tsreader != NULL:
|
|
|
|
retval = close_TS_reader(tsreader)
|
|
|
|
if retval != 0:
|
|
|
|
raise TSToolsException,"Error closing file '%s':"\
|
|
|
|
" %s"%(self.name,strerror(errno))
|
|
|
|
|
|
|
|
cdef TSPacket _next_TSPacket(TS_reader_p tsreader, filename):
|
2008-09-17 20:34:42 +00:00
|
|
|
cdef byte *buffer
|
2008-09-07 19:00:29 +00:00
|
|
|
if tsreader == NULL:
|
|
|
|
raise TSToolsException,'No TS stream to read'
|
2008-09-17 20:34:42 +00:00
|
|
|
retval = read_next_TS_packet(tsreader, &buffer)
|
2008-09-07 19:00:29 +00:00
|
|
|
if retval == EOF:
|
|
|
|
raise StopIteration
|
|
|
|
elif retval == 1:
|
|
|
|
raise TSToolsException,'Error getting next TS packet from file %s'%filename
|
2008-09-17 20:34:42 +00:00
|
|
|
# Remember the buffer we get handed a pointer to is transient
|
|
|
|
# so we need to take a copy of it (which we might as well keep in
|
|
|
|
# a Python object...)
|
|
|
|
buffer_str = PyString_FromStringAndSize(<char *>buffer, TS_PACKET_LEN)
|
|
|
|
try:
|
|
|
|
return TSPacket(buffer_str)
|
|
|
|
except TSToolsException, what:
|
|
|
|
raise TSToolsException,\
|
|
|
|
'Error getting next TS packet from file %s (%s)'%(filename,what)
|
2008-09-07 19:00:29 +00:00
|
|
|
|
2008-09-04 21:20:52 +00:00
|
|
|
cdef class TSFile:
|
|
|
|
"""A Python class representing a TS file.
|
|
|
|
|
|
|
|
We support opening for read, or opening (creating) a new file
|
|
|
|
for write. For the moment, we don't support appending, and
|
|
|
|
support for trying to read and write the same file is undefined.
|
|
|
|
|
|
|
|
So, create a new TSFile as either:
|
|
|
|
|
|
|
|
* TSFile(filename,'r') or
|
|
|
|
* TSFile(filename,'w')
|
|
|
|
|
|
|
|
Note that there is always an implicit 'b' attached to the mode (i.e., the
|
|
|
|
file is accessed in binary mode).
|
|
|
|
"""
|
|
|
|
|
|
|
|
cdef TS_reader_p tsreader
|
|
|
|
|
|
|
|
cdef readonly object name
|
|
|
|
cdef readonly object mode
|
|
|
|
|
|
|
|
# It appears to be recommended to make __cinit__ expand to take more
|
|
|
|
# arguments (if __init__ ever gains them), since both get the same
|
|
|
|
# things passed to them. Hmm, normally I'd trust myself, but let's
|
|
|
|
# try the recommended route
|
|
|
|
def __cinit__(self,filename,mode='r',*args,**kwargs):
|
|
|
|
actual_mode = mode+'b'
|
|
|
|
if mode == 'r':
|
|
|
|
retval = open_file_for_TS_read(filename,&self.tsreader)
|
|
|
|
if retval == 1:
|
|
|
|
raise TSToolsException,"Error opening file '%s'"\
|
|
|
|
" for TS reading: %s"%(filename,strerror(errno))
|
|
|
|
elif mode == 'w':
|
|
|
|
raise NotImplemented
|
|
|
|
else:
|
|
|
|
raise TSToolsException,"Error opening file '%s'"\
|
|
|
|
" with mode '%s' (only 'r' and 'w' supported)"%(filename,mode)
|
|
|
|
|
|
|
|
def __init__(self,filename,mode='r'):
|
|
|
|
# What should go in __init__ and what in __cinit__ ???
|
|
|
|
self.name = filename
|
|
|
|
self.mode = mode
|
|
|
|
|
|
|
|
def __dealloc__(self):
|
2008-09-07 19:00:29 +00:00
|
|
|
_TSFile_close_for_read(&self.tsreader)
|
|
|
|
#if self.tsreader != NULL:
|
|
|
|
# retval = close_TS_reader(&self.tsreader)
|
|
|
|
# if retval != 0:
|
|
|
|
# raise TSToolsException,"Error closing file '%s':"\
|
|
|
|
# " %s"%(self.name,strerror(errno))
|
2008-09-04 21:20:52 +00:00
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
def is_readable(self):
|
|
|
|
"""This is a convenience method, whilst reading and writing are exclusive.
|
|
|
|
"""
|
|
|
|
return self.mode == 'r' and self.tsreader != NULL
|
|
|
|
pass
|
|
|
|
|
|
|
|
def is_writable(self):
|
|
|
|
"""This is a convenience method, whilst reading and writing are exclusive.
|
|
|
|
"""
|
|
|
|
return self.mode == 'w'
|
|
|
|
#return self.mode == 'w' and self.file_stream != NULL
|
|
|
|
pass
|
|
|
|
|
|
|
|
# For Pyrex classes, we define a __next__ instead of a next method
|
|
|
|
# in order to form our iterator
|
|
|
|
def __next__(self):
|
|
|
|
"""Our iterator interface retrieves the TS packets from the stream.
|
|
|
|
"""
|
2008-09-07 19:00:29 +00:00
|
|
|
return _next_TSPacket(self.tsreader,self.name)
|
2008-09-04 21:20:52 +00:00
|
|
|
|
2008-09-07 19:00:29 +00:00
|
|
|
def seek(self,offset):
|
2008-09-04 21:20:52 +00:00
|
|
|
"""Seek to the given offset, which should be a multiple of 188.
|
2008-09-07 19:00:29 +00:00
|
|
|
|
|
|
|
Note that the method does not check the value of 'offset'.
|
2008-09-04 21:20:52 +00:00
|
|
|
"""
|
2008-09-07 19:00:29 +00:00
|
|
|
retval = seek_using_TS_reader(self.tsreader,offset)
|
|
|
|
if retval == 1:
|
|
|
|
raise TSToolsException,'Error seeking to %d in file %s'%(offset,self.name)
|
2008-09-04 21:20:52 +00:00
|
|
|
|
|
|
|
def read(self):
|
|
|
|
"""Read the next TS packet from this stream.
|
|
|
|
"""
|
2008-09-07 19:00:29 +00:00
|
|
|
try:
|
|
|
|
return _next_TSPacket(self.tsreader,self.name)
|
|
|
|
except StopIteration:
|
|
|
|
raise EOFError
|
2008-09-04 21:20:52 +00:00
|
|
|
|
|
|
|
def write(self, TSPacket tspacket):
|
2008-09-17 20:34:42 +00:00
|
|
|
"""Write a TS packet to this stream.
|
2008-09-04 21:20:52 +00:00
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
def close(self):
|
2008-09-07 19:00:29 +00:00
|
|
|
## Since we don't appear to be able to call our __dealloc__ "method",
|
|
|
|
## and we're not allowed to call Python methods..
|
|
|
|
#if self.tsreader != NULL:
|
|
|
|
# retval = close_TS_reader(&self.tsreader)
|
|
|
|
# if retval != 0:
|
|
|
|
# raise TSToolsException,"Error closing file '%s':"\
|
|
|
|
# " %s"%(self.name,strerror(errno))
|
|
|
|
_TSFile_close_for_read(&self.tsreader)
|
2008-09-04 21:20:52 +00:00
|
|
|
self.name = None
|
|
|
|
self.mode = None
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, etype, value, tb):
|
|
|
|
if tb is None:
|
|
|
|
# No exception, so just finish normally
|
|
|
|
self.close()
|
|
|
|
else:
|
|
|
|
# Exception occurred, so tidy up
|
|
|
|
self.close()
|
|
|
|
# And allow the exception to be re-raised
|
|
|
|
return False
|
|
|
|
|
2008-09-02 12:18:15 +00:00
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
# vim: set filetype=python expandtab shiftwidth=4:
|
|
|
|
# [X]Emacs local variables declaration - place us into python mode
|
|
|
|
# Local Variables:
|
|
|
|
# mode:python
|
|
|
|
# py-indent-offset:4
|
|
|
|
# End:
|
|
|
|
|