%PDF- %PDF-
| Direktori : /home/waritko/build/Bento4/Source/Python/bento4/ |
| Current File : //home/waritko/build/Bento4/Source/Python/bento4/core.py |
from bento4 import *
from bento4.errors import check_result
from ctypes import c_int, c_char_p, string_at
from struct import pack, unpack
def atom_type(name):
return unpack('>I', pack('>4s', name))[0]
def atom_name(type):
return unpack('>4s', pack('>I', type))[0]
class File(object):
FILE_BRAND_QT__ = atom_type('qt ')
FILE_BRAND_ISOM = atom_type('isom')
FILE_BRAND_MP41 = atom_type('mp41')
FILE_BRAND_MP42 = atom_type('mp42')
FILE_BRAND_3GP1 = atom_type('3gp1')
FILE_BRAND_3GP2 = atom_type('3gp2')
FILE_BRAND_3GP3 = atom_type('3gp3')
FILE_BRAND_3GP4 = atom_type('3gp4')
FILE_BRAND_3GP5 = atom_type('3gp5')
FILE_BRAND_3G2A = atom_type('3g2a')
FILE_BRAND_MMP4 = atom_type('mmp4')
FILE_BRAND_M4A_ = atom_type('M4A ')
FILE_BRAND_M4P_ = atom_type('M4P ')
FILE_BRAND_MJP2 = atom_type('mjp2')
def __init__(self, name='', movie=None):
self.moov = movie # can't have self.movie because movie is a property
if movie is None:
if len(name) == 0:
raise ValueError("name param cannot be empty")
result = Ap4Result()
self.bt4stream = lb4.AP4_FileByteStream_Create(c_char_p(name),
c_int(0), # read
byref(result))
check_result(result.value)
self.bt4file = lb4.AP4_File_FromStream(self.bt4stream, 0)
else:
self.bt4file = lb4.AP4_File_Create(movie.bt4movie)
movie.bt4owner = False
movie.file = self
def __del__(self):
lb4.AP4_File_Destroy(self.bt4file)
try:
lb4.AP4_Stream_Release(self.bt4stream)
except AttributeError:
pass # depending on how the object was created,
# self.bt4stream may or may not exist
def inspect(self, inspector):
f = lb4.AP4_File_Inspect
f.restype = check_result
f(self.bt4file, inspector.bt4inspector)
@property
def moov_is_before_mdat(self):
c = lb4.AP4_File_IsMoovBeforeMdat(self.bt4file)
return True if c !=0 else False
@property
def movie(self):
if self.moov:
return self.moov
bt4movie = lb4.AP4_File_GetMovie(self.bt4file)
if bt4movie is None:
return None
else:
self.moov = Movie(bt4movie=bt4movie)
self.moov.file = file # add a reference here for ref counting
return self.moov
def get_type(self):
"""returns a tuple (major_brand, minor_version, [compatible_brands])"""
major_brand = Ap4UI32()
minor_version = Ap4UI32()
compat = Ap4UI32()
compat_count = Ap4UI32()
# get the file type
f = lb4.AP4_File_GetFileType
f.restype = check_result
f(self.bt4file, byref(major_brand),
byref(minor_version), byref(compat_count))
# get the compatible brands
f = lb4.AP4_File_GetCompatibleBrand
f.restype = check_result
compat_brands = []
for i in xrange(compat_count.value):
f(self.bt4file, i, byref(compat))
compat_brands += [compat.value]
return (major_brand.value, minor_version.value, compat_brands)
def set_type(self, value):
"""value: a tuple (major_brand, minor_version, [compatible_brands])"""
major_brand, minor_version, compat_brands = value
compat_count = len(compat_brands)
compat_brand_array = Ap4UI32*compat_count
f = lb4.AP4_File_SetFileType
f.restype = check_result
f(self.bt4file, major_brand, minor_version,
compat_brand_array(*compat_brands), compat_count)
type = property(get_type, set_type)
class Movie(object):
def __init__(self, timescale=0, bt4movie=None):
self.bt4owner = False
if bt4movie is None:
self.bt4movie = lb4.AP4_Movie_Create(Ap4UI32(timescale))
else:
self.bt4movie = bt4movie
def __del__(self):
if self.bt4owner:
lb4.AP4_Movie_Destroy(self.bt4movie)
@property
def tracks(self):
result = {}
count = lb4.AP4_Movie_GetTrackCount(self.bt4movie)
for i in xrange(count):
bt4track = lb4.AP4_Movie_GetTrackByIndex(self.bt4movie, Ap4Ordinal(i))
track = Track(bt4track=bt4track)
track.movie = self # add a reference here for ref counting
result[track.id] = track
return result
@property
def duration(self):
return (lb4.AP4_Movie_GetDuration(self.bt4movie),
lb4.AP4_Movie_GetTimeScale(self.bt4movie))
def add_track(self, track):
result = lb4.AP4_Movie_AddTrack(self.bt4movie. track.bt4track)
if result.value != 0:
raise RuntimeError("Track insertion failed with error %d" %
result.value)
track.bt4owner = False # change ownership
track.movie = self
class Track(object):
TYPE_UNKNOWN = 0
TYPE_AUDIO = 1
TYPE_VIDEO = 2
TYPE_SYSTEM = 3
TYPE_HINT = 4
TYPE_TEXT = 5
TYPE_JPEG = 6
TYPE_RTP = 7
HANDLER_TYPE_SOUN = atom_type('soun')
HANDLER_TYPE_VIDE = atom_type('vide')
HANDLER_TYPE_HINT = atom_type('hint')
HANDLER_TYPE_MDIR = atom_type('mdir')
HANDLER_TYPE_TEXT = atom_type('text')
HANDLER_TYPE_TX3G = atom_type('tx3g')
HANDLER_TYPE_JPEG = atom_type('jpeg')
HANDLER_TYPE_ODSM = atom_type('odsm')
HANDLER_TYPE_SDSM = atom_type('sdsm')
def __init__(self, type=TYPE_UNKNOWN, sample_table=None, id=0,
track_duration=(), media_duration=(), language='',
dimensions=(), bt4track=None):
"""durations are tuple: (duration, timescale)
track_duration is in the timescale of the movie"""
self.bt4owner = False
self.movie_timescale = -1 # invalid on purpose
if bt4track is None:
self.bt4owner = True
self.bt4track = lb4.AP4_Track_Create(c_int(type),
sample_table.bt4table,
Ap4UI32(id),
Ap4UI32(track_duration[1]),
Ap4UI32(track_duration[0]),
Ap4UI32(media_duration[1]),
Ap4UI32(media_duration[0]),
c_char_p(language),
Ap4UI32(width),
Ap4UI32(height))
self.movie_timescale = track_duration[1]
sample_table.bt4owner = False # change the ownership
else:
self.bt4track = bt4track
def __del__(self):
if self.bt4owner:
lb4.AP4_Track_Destroy(self.bt4track)
@property
def type(self):
return lb4.AP4_Track_GetType(self.bt4track)
@property
def handler_type(self):
return lb4.AP4_Track_GetHandlerType(self.bt4track)
@property
def id(self):
return lb4.AP4_Track_GetId(self.bt4track)
@property
def media_duration(self):
return (lb4.AP4_Track_GetMediaDuration(self.bt4track),
lb4.AP4_Track_GetMediaTimeScale(self.bt4track))
@property
def duration(self):
if self.movie_timescale == -1:
# get it from the movie
self.movie_timescale = self.movie.duration[1]
return (lb4.AP4_Track_GetDuration(self.bt4track), self.movie_timescale)
@property
def language(self):
f = lb4.AP4_Track_GetLanguage
f.restype = c_char_p
return f(self.bt4track)
@property
def sample_count(self):
return lb4.AP4_Track_GetSampleCount(self.bt4track)
def sample(self, index):
if index<0 or index>=self.sample_count:
raise IndexError()
bt4sample = lb4.AP4_Sample_CreateEmpty()
f = lb4.AP4_Track_GetSample
f.restype = check_result
try:
f(self.bt4track, index, bt4sample)
except Exception, e:
lb4.AP4_Sample_Destroy(bt4sample) # prevents a leak
raise e
return Sample(bt4sample=bt4sample)
def sample_iterator(self, start=0, end=-1):
current = start
end = end if end != -1 else self.sample_count
while current<end:
yield self.sample(current)
current += 1
def set_movie_timescale(self, timescale):
f = lb4.AP4_Track_SetMovieTimeScale
f.restype = check_result
f(self.bt4track, Ap4UI32(timescale))
def sample_index_for_timestamp_ms(self, ts):
result = Ap4Ordinal()
f = lb4.AP4_Track_GetSampleIndexForTimeStampMs
f.restype = check_result
f(self.bt4track, Ap4TimeStamp(ts), byref(result))
return result.value
def sample_description(self, index):
bt4desc = lb4.AP4_Track_GetSampleDescription(self.bt4track,
Ap4Ordinal(index))
if bt4desc == 0:
raise IndexError()
sampledesc_type = lb4.AP4_SampleDescription_GetType(bt4desc)
track_type = self.type
if sampledesc_type == SampleDescription.TYPE_AVC:
result = AvcSampleDescription(bt4desc=bt4desc)
elif sampledesc_type == SampleDescription.TYPE_MPEG:
if track_type == Track.TYPE_AUDIO:
result = MpegAudioSampleDescription(bt4desc=bt4desc)
elif track_type == Track.TYPE_VIDEO:
result = MpegVideoSampleDescription(bt4desc=bt4desc)
elif track_type == Track.TYPE_MPEG:
result = MpegSystemSampleDescription(bt4desc=bt4desc)
else:
result = MpegSampleDescription(bt4desc=bt4desc)
else:
if track_type == Track.TYPE_AUDIO:
result = GenericAudioSampleDescription(bt4desc=bt4desc)
elif track_type == Track.TYPE_VIDEO:
result = GenericVideoSampleDescription(bt4desc=bt4desc)
else:
result = SampleDescription(bt4desc)
result.track = self # add a reference
return result
class Sample(object):
def __init__(self, data_stream=None, offset=0, size=0, desc_index=0,
dts=0, cts_offset=0, is_sync=False, bt4sample=None):
if bt4sample is None:
raise NotImplementedError()
else:
self.bt4sample = bt4sample
def __del__(self):
# always the owner of the sample
lb4.AP4_Sample_Destroy(self.bt4sample)
@property
def data(self):
bt4buffer = lb4.AP4_DataBuffer_Create(self.size)
f = lb4.AP4_Sample_ReadData
f.restype = check_result
try:
f(self.bt4sample, bt4buffer)
return string_at(lb4.AP4_DataBuffer_GetData(bt4buffer),
lb4.AP4_DataBuffer_GetDataSize(bt4buffer))
except Exception, e:
raise e
finally:
lb4.AP4_DataBuffer_Destroy(bt4buffer) # prevents a leak
@property
def offset(self):
return lb4.AP4_Sample_GetOffset(self,bt4sample)
@property
def size(self):
return lb4.AP4_Sample_GetSize(self.bt4sample)
@property
def description_index(self):
return lb4.AP4_Sample_GetDescriptionIndex(self.bt4sample)
@property
def dts(self):
return lb4.AP4_Sample_GetDts(self.bt4sample)
@property
def cts(self):
return lb4.AP4_Sample_GetCts(self.bt4sample)
@property
def is_sync(self):
v = lb4.AP4_Sample_IsSync(self.bt4sample)
return False if v==0 else True
class SampleDescription(object):
TYPE_UNKNOWN = 0
TYPE_MPEG = 1
TYPE_PROTECTED = 2
TYPE_AVC = 3
def __init__(self, bt4desc=None, bt4owner=False, **kwargs):
self.bt4desc = bt4desc
self.bt4owner = bt4owner
super(SampleDescription, self).__init__(**kwargs)
def __del__(self):
if self.bt4owner:
lb4.AP4_SampleDescription_Destroy(self.bt4desc)
@property
def type(self):
return lb4.AP4_SampleDescription_GetType(self.bt4desc)
@property
def format(self):
return lb4.AP4_SampleDescription_GetFormat(self.bt4desc)
class AudioSampleDescription(object):
"""mixin class"""
def __init__(self, bt4audiodesc, **kwargs):
self.bt4audiodesc = bt4audiodesc
super(AudioSampleDescription, self).__init__(**kwargs)
@property
def sample_rate(self):
return lb4.AP4_AudioSampleDescription_GetSampleRate(self.bt4audiodesc)
@property
def sample_size(self):
return lb4.AP4_AudioSampleDescription_GetSampleSize(self.bt4audiodesc)
@property
def channel_count(self):
return lb4.AP4_AudioSampleDescription_GetChannelCount(self.bt4audiodesc)
class VideoSampleDescription(object):
"""mixin class"""
def __init__(self, bt4videodesc, **kwargs):
self.bt4videodesc = bt4videodesc
super(VideoSampleDescription, self).__init__(**kwargs)
@property
def width(self):
return lb4.AP4_VideoSampleDescription_GetWidth(self.bt4videodesc)
@property
def height(self):
return lb4.AP4_VideoSampleDescription_GetHeight(self.bt4videodesc)
@property
def depth(self):
return lb4.AP4_VideoSampleDescription_GetDepth(self.bt4videodesc)
@property
def compressor_name(self):
f = lb4.AP4_VideoSampleDescription_GetCompressorName
f.restype = c_char_p
return f(self.bt4videodesc)
class MpegSampleDescription(SampleDescription):
STREAM_TYPE_FORBIDDEN = 0x00
STREAM_TYPE_OD = 0x01
STREAM_TYPE_CR = 0x02
STREAM_TYPE_BIFS = 0x03
STREAM_TYPE_VISUAL = 0x04
STREAM_TYPE_AUDIO = 0x05
STREAM_TYPE_MPEG7 = 0x06
STREAM_TYPE_IPMP = 0x07
STREAM_TYPE_OCI = 0x08
STREAM_TYPE_MPEGJ = 0x09
STREAM_TYPE_TEXT = 0x0D
OTI_MPEG4_SYSTEM = 0x01
OTI_MPEG4_SYSTEM_COR = 0x02
OTI_MPEG4_TEXT = 0x08
OTI_MPEG4_VISUAL = 0x20
OTI_MPEG4_AUDIO = 0x40
OTI_MPEG2_VISUAL_SIMPLE = 0x60
OTI_MPEG2_VISUAL_MAIN = 0x61
OTI_MPEG2_VISUAL_SNR = 0x62
OTI_MPEG2_VISUAL_SPATIAL = 0x63
OTI_MPEG2_VISUAL_HIGH = 0x64
OTI_MPEG2_VISUAL_422 = 0x65
OTI_MPEG2_AAC_AUDIO_MAIN = 0x66
OTI_MPEG2_AAC_AUDIO_LC = 0x67
OTI_MPEG2_AAC_AUDIO_SSRP = 0x68
OTI_MPEG2_PART3_AUDIO = 0x69
OTI_MPEG1_VISUAL = 0x6A
OTI_MPEG1_AUDIO = 0x6B
OTI_JPEG = 0x6C
def __init__(self, bt4mpegdesc, **kwargs):
self.bt4mpegdesc = bt4mpegdesc
super(MpegSampleDescription, self).__init__(**kwargs)
@property
def stream_type(self):
return lb4.AP4_MpegSampleDescription_GetStreamType(self.bt4mpegdesc)
@property
def object_type_id(self):
return lb4.AP4_MpegSampleDescription_GetObjectTypeId(self.bt4mpegdesc)
@property
def buffer_size(self):
return lb4.AP4_MpegSampleDescription_GetBufferSize(self.bt4mpegdesc)
@property
def max_bitrate(self):
return lb4.AP4_MpegSampleDescription_GetMaxBitrate(self.bt4mpegdesc)
@property
def avg_bitrate(self):
return lb4.AP4_MpegSampleDescription_GetAvgBitrate(self.bt4mpegdesc)
@property
def decoder_info(self):
bt4buf = lb4.AP4_MpegSampleDescription_GetDecoderInfo(self.bt4mpegdesc)
return string_at(lb4.AP4_DataBuffer_GetData(bt4buf),
lb4.AP4_DataBuffer_GetDataSize(bt4buf))
class GenericAudioSampleDescription(SampleDescription,
AudioSampleDescription):
def __init__(self, bt4desc):
bt4audiodesc = lb4.AP4_SampleDescription_AsAudio(bt4desc)
super(GenericAudioSampleDescription, self).__init__(bt4desc=bt4desc,
bt4audiodesc=bt4audiodesc)
class GenericVideoSampleDescription(SampleDescription,
VideoSampleDescription):
def __init__(self, bt4desc):
bt4videodesc = lb4.AP4_SampleDescription_AsVideo(bt4desc)
super(GenericVideoSampleDescription, self).__init__(bt4desc=bt4desc,
bt4videodesc=bt4videodesc)
def avc_profile_name(profile):
f = lb4.AP4_AvcSampleDescription_GetProfileName
f.restype = c_char_p
return f(profile)
class AvcSampleDescription(SampleDescription, VideoSampleDescription):
def __init__(self, bt4desc):
bt4videodesc = lb4.AP4_SampleDescription_AsVideo(bt4desc)
super(AvcSampleDescription, self).__init__(bt4desc=bt4desc,
bt4videodesc=bt4videodesc)
self.bt4avcdesc = lb4.AP4_SampleDescription_AsAvc(bt4desc)
@property
def config_version(self):
return lb4.AP4_AvcSampleDescription_GetConfigurationVersion(self.bt4avcdesc)
@property
def profile(self):
return lb4.AP4_AvcSampleDescription_GetProfile(self.bt4avcdesc)
@property
def level(self):
return lb4.AP4_AvcSampleDescription_GetLevel(self.bt4avcdesc)
@property
def profile_compatibility(self):
return lb4.AP4_AvcSampleDescription_GetProfileCompatibility(self.bt4avcdesc)
@property
def nalu_length_size(self):
return lb4.AP4_AvcSampleDescription_GetNaluLengthSize(self.bt4avcdesc)
@property
def sequence_params(self):
result = []
count = lb4.AP4_AvcSampleDescription_GetSequenceParameterCount(self.bt4avcdesc)
for i in xrange(count):
bt4buf = lb4.AP4_AvcSampleDescription_GetSequenceParameter(self.bt4avcdesc, i)
result += [string_at(lb4.AP4_DataBuffer_GetData(bt4buf),
lb4.AP4_DataBuffer_GetDataSize(bt4buf))]
return result
@property
def picture_params(self):
result = []
count = lb4.AP4_AvcSampleDescription_GetPictureParametersCount(self.bt4avcdesc)
for i in xrange(count):
bt4buf = lb4.AP4_AvcSampleDescription_GetPictureParameter(self.bt4avcdesc, i)
result += [string_at(AP4_DataBuffer_GetData(bt4buf),
AP4_DataBuffer_GetDataSize(bt4buf))]
return result
class MpegSystemSampleDescription(MpegSampleDescription):
def __init__(self, bt4desc):
bt4mpegdesc = lb4.AP4_SampleDescription_AsMpeg(bt4desc)
super(MpegSystemSampleDescription, self).__init__(bt4desc=bt4desc,
bt4mpegdesc=bt4mpegdesc)
def mpeg_audio_object_type_string(type):
f = lb4.AP4_MpegAudioSampleDescription_GetMpegAudioObjectTypeString
f.restype = c_char_p
return f(type)
class MpegAudioSampleDescription(MpegSampleDescription,
AudioSampleDescription) :
MPEG4_AUDIO_OBJECT_TYPE_AAC_MAIN = 1 # AAC Main Profile
MPEG4_AUDIO_OBJECT_TYPE_AAC_LC = 2 # AAC Low Complexity
MPEG4_AUDIO_OBJECT_TYPE_AAC_SSR = 3 # AAC Scalable Sample Rate
MPEG4_AUDIO_OBJECT_TYPE_AAC_LTP = 4 # AAC Long Term Predictor
MPEG4_AUDIO_OBJECT_TYPE_SBR = 5 # Spectral Band Replication
MPEG4_AUDIO_OBJECT_TYPE_AAC_SCALABLE = 6 # AAC Scalable
MPEG4_AUDIO_OBJECT_TYPE_TWINVQ = 7 # Twin VQ
MPEG4_AUDIO_OBJECT_TYPE_ER_AAC_LC = 17 # Error Resilient AAC Low Complexity
MPEG4_AUDIO_OBJECT_TYPE_ER_AAC_LTP = 19 # Error Resilient AAC Long Term Prediction
MPEG4_AUDIO_OBJECT_TYPE_ER_AAC_SCALABLE = 20 # Error Resilient AAC Scalable
MPEG4_AUDIO_OBJECT_TYPE_ER_TWINVQ = 21 # Error Resilient Twin VQ
MPEG4_AUDIO_OBJECT_TYPE_ER_BSAC = 22 # Error Resilient Bit Sliced Arithmetic Coding
MPEG4_AUDIO_OBJECT_TYPE_ER_AAC_LD = 23 # Error Resilient AAC Low Delay
MPEG4_AUDIO_OBJECT_TYPE_LAYER_1 = 32 # MPEG Layer 1
MPEG4_AUDIO_OBJECT_TYPE_LAYER_2 = 33 # MPEG Layer 2
MPEG4_AUDIO_OBJECT_TYPE_LAYER_3 = 34 # MPEG Layer 3
def __init__(self, bt4desc):
bt4audiodesc = lb4.AP4_SampleDescription_AsAudio(bt4desc)
bt4mpegdesc = lb4.AP4_SampleDescription_AsMpeg(bt4desc)
super(MpegAudioSampleDescription, self).__init__(bt4desc=bt4desc,
bt4audiodesc=bt4audiodesc,
bt4mpegdesc=bt4mpegdesc)
self.bt4mpegaudiodesc = lb4.AP4_SampleDescription_AsMpegAudio(bt4desc)
@property
def mpeg4_audio_object_type(self):
return lb4.AP4_MpegAudioSampleDescription_GetMpeg4AudioObjecType(self.bt4mpegaudiodesc)
class MpegVideoSampleDescription(MpegSampleDescription,
VideoSampleDescription):
def __init__(self, bt4desc):
bt4videodesc = lb4.AP4_SampleDescription_AsVideo(bt4desc)
bt4mpegdesc = lb4.AP4_SampleDescription_AsMpeg(bt4desc)
super(MpegVideoSampleDescription, self).__init__(bt4desc=bt4desc,
bt4mpegdesc=bt4mpegdesc,
bt4videodesc=bt4videodesc)