2008-07-30 22:07:47 +00:00
|
|
|
"""tstools.pyx -- Pyrex bindings for the TS tools
|
2008-07-28 22:20:43 +00:00
|
|
|
"""
|
|
|
|
|
2008-07-31 10:09:59 +00:00
|
|
|
# ***** BEGIN LICENSE BLOCK *****
|
|
|
|
# Version: MPL 1.1
|
|
|
|
#
|
|
|
|
# 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/
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
#
|
|
|
|
# The Original Code is the MPEG TS, PS and ES tools.
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
#
|
|
|
|
# Contributor(s):
|
|
|
|
# Tibs (tibs@berlios.de)
|
|
|
|
#
|
|
|
|
# ***** END LICENSE BLOCK *****
|
|
|
|
|
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.
|
|
|
|
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)
|
|
|
|
|
|
|
|
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-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-18 22:52:39 +00:00
|
|
|
ctypedef char byte
|
|
|
|
|
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 *...
|
|
|
|
int build_ES_unit(ES_unit_p *unit)
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
def __cinit__(self, *args,**kwargs):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
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 _set_unit(self, ES_unit_p unit):
|
|
|
|
# if self.unit:
|
|
|
|
# raise TSToolsException,'ESUnit already has an ES unit associated'
|
|
|
|
# else:
|
|
|
|
# self.unit = unit
|
|
|
|
|
|
|
|
def __dealloc__(self):
|
|
|
|
free_ES_unit(&self.unit)
|
|
|
|
|
|
|
|
def __repr__(self):
|
2008-08-18 22:52:39 +00:00
|
|
|
return 'ES unit: start code %02x'%self.unit.start_code
|
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-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-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
|
|
|
|
#u._set_unit(unit)
|
|
|
|
return u
|
|
|
|
|
2008-07-28 22:20:43 +00:00
|
|
|
cdef class ESStream:
|
2008-08-22 22:58:15 +00:00
|
|
|
"""A Python class representing an ES stream.
|
|
|
|
|
|
|
|
Initially, always a file (so maybe this should be ESFile?)
|
|
|
|
|
|
|
|
We support opening for read, or opening (creating) a new file
|
|
|
|
for write. For the moment, we don't support appending.
|
|
|
|
|
|
|
|
So, create a new ESStream as either:
|
|
|
|
|
|
|
|
* ESStream(filename,'r') or
|
|
|
|
* ESStream(filename,'w')
|
|
|
|
|
|
|
|
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 object python_file # File as opened by Python
|
|
|
|
cdef FILE *file_stream # The corresponding C file stream
|
|
|
|
cdef ES_p stream # For reading an existing ES stream
|
2008-07-30 22:07:47 +00:00
|
|
|
cdef readonly object filename
|
2008-08-22 22:58:15 +00:00
|
|
|
cdef readonly object mode
|
|
|
|
cdef object actual_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):
|
|
|
|
if mode == 'r':
|
|
|
|
actual_mode = 'rb'
|
|
|
|
self.python_file = open(filename,actual_mode)
|
|
|
|
fileno = self.python_file.fileno()
|
|
|
|
self.file_stream = fdopen(fileno,actual_mode)
|
|
|
|
retval = build_elementary_stream_file(fileno,&self.stream)
|
|
|
|
if retval != 0:
|
|
|
|
raise TSToolsException,'Error starting to read ES file %s'%filename
|
|
|
|
elif mode == 'w':
|
|
|
|
actual_mode = 'wb'
|
|
|
|
self.python_file = open(filename,actual_mode)
|
|
|
|
fileno = self.python_file.fileno()
|
|
|
|
self.file_stream = fdopen(fileno,actual_mode)
|
|
|
|
else:
|
|
|
|
raise ValueError,"Only modes 'r' and 'w' supported for ESStream"
|
|
|
|
|
|
|
|
def __init__(self,filename,mode='r'):
|
|
|
|
# What should go in __init__ and what in __cinit__ ???
|
2008-07-28 22:20:43 +00:00
|
|
|
self.filename = filename
|
2008-08-22 22:58:15 +00:00
|
|
|
self.mode = mode
|
|
|
|
self.actual_mode = mode + 'b'
|
2008-07-28 22:20:43 +00:00
|
|
|
|
|
|
|
def __dealloc__(self):
|
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.
|
|
|
|
"""
|
|
|
|
return _next_ESUnit(self.stream,self.filename)
|
2008-08-22 22:58:15 +00:00
|
|
|
|
2008-08-23 21:15:29 +00:00
|
|
|
|
2008-08-22 22:58:15 +00:00
|
|
|
def close(self):
|
|
|
|
if self.python_file:
|
|
|
|
self.python_file.close()
|
|
|
|
self.python_file = None
|
|
|
|
self.mode = None
|
|
|
|
# Apparently we can't call the __dealloc__ method itself,
|
|
|
|
# but I think this is sensible to do here...
|
|
|
|
if self.stream != NULL:
|
|
|
|
free_elementary_stream(&self.stream)
|