diff --git a/aaf2/__init__.py b/aaf2/__init__.py new file mode 100644 index 0000000..92fd20b --- /dev/null +++ b/aaf2/__init__.py @@ -0,0 +1,13 @@ +from .file import AAFFile as open +from . import dictionary +from . import content +from . import misc +from . import mobs +from . import mobslots +from . import components +from . import essence + +__version__ = "1.4.0" +__author__ = "Mark Reid" +__author_email__ = "mindmark@gmail.com" +__license__ = "MIT" diff --git a/aaf2/ama.py b/aaf2/ama.py new file mode 100644 index 0000000..22a6e2f --- /dev/null +++ b/aaf2/ama.py @@ -0,0 +1,502 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, +) + +import os +import sys +from .rational import AAFRational +from . import video, audio, mxf +from .auid import AUID + +import struct + +MediaContainerGUID = { + "Generic": (AUID("b22697a2-3442-44e8-bb8f-7a1cd290ebf1"), + ('.3g2', '.3gp', '.aac', '.au', '.avi', '.bmp', '.dv', '.gif', + '.jfif', '.jpeg', '.jpg', '.m4a', '.mid', '.moov', '.mov', + '.movie', '.mp2', '.mp3', '.mp4', '.mpa', '.mpe', '.mpeg', + '.mpg', '.png', '.psd', '.qt', '.tif', '.tiff',)), + "AVCHD": (AUID("f37d624b307d4ef59bebc539046cad54"), + ('.mts', '.m2ts',)), + "ImageSequencer": (AUID("4964178d-b3d5-485f-8e98-beb89d92a5f4"), + ('.dpx',)), + "CanonRaw": (AUID("0f299461-ee19-459f-8ae6-93e65c76a892"), + ('.rmf',)), + "WaveAiff": (AUID("3711d3cc-62d0-49d7-b0ae-c118101d1a16"), + ('.wav', '.wave', '.bwf', '.aif', '.aiff', '.aifc', '.cdda',)), + "MXF": (AUID("60eb8921-2a02-4406-891c-d9b6a6ae0645"), + ('.mxf',)), + "QuickTime": (AUID("781f84b7-b989-4534-8a07-c595cb9a6fb8"), + ('.mov', '.mp4', '.m4v', '.mpg', '.mpe', '.mpeg', '.3gp', '.3g2', + '.qt', '.moov', '.movie', '.avi', '.mp2', '.mp3', '.m4a', '.wav', + '.aiff', '.aif', '.au', '.aac', '.mid', '.mpa', '.gif', '.jpg', + '.jpeg', '.jfif', '.tif', '.tiff', '.png', '.bmp', '.psd', '.dv')), +} + + +def get_wave_fmt(path): + """ + Returns a bytearray of the WAVE RIFF header and fmt + chunk for a `WAVEDescriptor` `Summary` + """ + with open(path, 'rb') as file: + if file.read(4) != b"RIFF": + return None + data_size = file.read(4) # container size + if file.read(4) != b"WAVE": + return None + while True: + chunkid = file.read(4) + sizebuf = file.read(4) + if len(sizebuf) < 4 or len(chunkid) < 4: + return None + size = struct.unpack(b'L", sizebuf)[0] + if chunkid != b"COMM": + if size % 2 == 1: + seek = size + 1 + else: + seek = size + file.seek(seek, 1) + else: + return bytearray(b"FORM" + data_size + signature + chunkid + sizebuf + file.read(size)) + + +def create_network_locator(f, absolute_path): + n = f.create.NetworkLocator() + if sys.version_info[0] < 3: + import urllib + n['URLString'].value = 'file://' + urllib.pathname2url(absolute_path) + else: + import pathlib + n['URLString'].value = pathlib.Path(absolute_path).as_uri() + + return n + +class FormatInfo: + """ + Provides convenient access to commonly-used datums + """ + + def __init__(self, metadata): + self.metadata = metadata + + @property + def streams(self): + for stream in self.metadata['streams']: + yield StreamInfo(stream) + + @property + def first_sound_stream(self): + return next((stream for stream in self.streams if stream.is_sound), None) + + + @property + def first_picture_stream(self): + return next((stream for stream in self.streams if stream.is_picture), None) + + def create_descriptor(self, f, path): + if self.metadata['format']['format_name'] in ('wav',): + return self.create_wav_descriptor(f, path) + + if self.metadata['format']['format_name'] in ('aiff',): + return self.create_aifc_descriptor(f,path) + + elif self.metadata['format']['format_long_name'] == 'QuickTime / MOV': + return self.create_multistream_descriptor(f, path) + else: + return None + + @property + def container_guid(self): + if self.metadata['format']['format_name'] in ('wav',): + return MediaContainerGUID['WaveAiff'][0] + + # if self.metadata['format']['format_long_name'] == 'QuickTime / MOV': + # return MediaContainerGUID['QuickTime'][0] + + # just using the generic appears to work + return MediaContainerGUID['Generic'][0] + + @property + def edit_rate(self): + """ + :return: The edit rate of the first picture stream, or if there are none, the first sound stream. + """ + pix = self.first_picture_stream + if pix is None: + return self.first_sound_stream.edit_rate + else: + return pix.edit_rate + + @property + def length(self): + """ + :return: The length of the first picture stream, or if there are none, the first sound stream. + """ + pix = self.first_picture_stream + if pix is None: + return self.first_sound_stream.length + else: + return pix.length + + def create_wav_descriptor(self, f, path): + d = f.create.WAVEDescriptor() + stream = self.first_sound_stream + d['SampleRate'].value = stream.edit_rate + d['Summary'].value = get_wave_fmt(path) + d['Length'].value = stream.length + d['ContainerFormat'].value = f.dictionary.lookup_containerdef("AAF") + d['Locator'].append(create_network_locator(f, path)) + return d + + def create_aifc_descriptor(self, f, path): + d = f.create.AIFCDescriptor() + stream = self.first_sound_stream + d['SampleRate'].value = stream.edit_rate + d['Summary'].value = get_aifc_fmt(path) + d['Length'].value = stream.length + d['ContainerFormat'].value = f.dictionary.lookup_containerdef("AAF") + d['Locator'].append(create_network_locator(f, path)) + return d + + def coalesce_descriptors(self, f, descriptors, path): + if len(descriptors) > 1: + desc = f.create.MultipleDescriptor() + desc['Length'].value = self.length + desc['SampleRate'].value = self.edit_rate + desc['MediaContainerGUID'].value = self.container_guid + desc['Locator'].append(create_network_locator(f, path)) + desc['FileDescriptors'].value = descriptors + return desc + else: + return descriptors[0] + + def create_multistream_descriptor(self, f, path): + descriptor_list = [] + + for stream in self.streams: + if stream.is_picture: + desc = stream.create_video_descriptor(f) + descriptor_list.append(desc) + desc['Locator'].append(create_network_locator(f, path)) + + elif stream.is_sound: + desc = stream.create_pcm_descriptor(f) + descriptor_list.append(desc) + desc['Locator'].append(create_network_locator(f, path)) + + return self.coalesce_descriptors(f, descriptor_list, path) + + +class StreamInfo: + def __init__(self, metadata): + self.metadata = metadata + + @property + def codec_type(self): + return self.metadata['codec_type'] + + @property + def codec_name(self): + return self.metadata['codec_name'] + + @property + def is_sound(self): + return self.codec_type == 'audio' + + @property + def is_picture(self): + return self.codec_type == 'video' + + @property + def edit_rate(self): + if self.is_sound: + return AAFRational(self.metadata['sample_rate']) + elif self.is_picture: + return AAFRational(self.metadata['avg_frame_rate']) + + @property + def length(self): + if self.is_sound: + return int(self.metadata['duration_ts']) + elif self.is_picture: + return int(self.metadata['nb_frames']) + + @property + def physical_track_count(self): + if self.is_sound: + return self.metadata['channels'] + + def create_pcm_descriptor(self, f): + if not self.is_sound: + return None + + d = f.create.PCMDescriptor() + d['SampleRate'].value = self.edit_rate + d['AudioSamplingRate'].value = self.edit_rate + + d['Channels'].value = self.physical_track_count + d['AverageBPS'].value = int(self.metadata['bit_rate']) + + bit_depth, block_align = audio.audio_format_sizes.get(self.metadata['sample_fmt'], (0, 0)) + + d['QuantizationBits'].value = bit_depth + d['BlockAlign'].value = block_align + + d['Length'].value = self.length + + d['Compression'].value = AUID('04020202-0000-0000-060e-2b3404010101') + return d + + def pixel_sizes(self): + if not self.is_picture: + return None + + pix_fmt = self.metadata['pix_fmt'] + h_samp = 2 + v_samp = 2 + + depth = 8 + if pix_fmt.count('420'): + h_samp = 2 + v_samp = 2 + elif pix_fmt.count('422'): + h_samp = 2 + v_samp = 1 + elif pix_fmt.count('444'): + h_samp = 1 + v_samp = 1 + + for i in [8, 10, 12, 16]: + if pix_fmt.count("p%d" % i): + depth = i + break + + return (depth, h_samp, v_samp) + + def get_avc_compression(self): + if not self.is_picture: + return None + + profile = self.metadata.get('profile', None) + key = 'CompressedPicture' + if profile == "Baseline": + key = 'AVCBaselineUnconstrained' + elif profile == "Constrained Baseline": + key = 'AVCConstrainedBaselineUnconstrained' + elif profile == "Main": + key = 'AVCMainUnconstrained' + elif profile == "Extended": + key = 'AVCExtendedUnconstrained' + elif profile == "High": + key = 'AVCHighUnconstrained' + elif profile == "High 10": + key = 'AVCHigh10Unconstrained' + elif profile == "High 10 Intra": + key = 'AVCHigh10IntraUnconstrained' + elif profile == "High 4:2:2": + key = 'AVCHigh422Unconstrained' + elif profile == "High 4:2:2 Intra": + key = 'AVCHigh422IntraUnconstrained' + elif profile == "High 4:4:4": + # key = 'AVCHigh444IntraUnconstrained' + key = 'CompressedPicture' + elif profile == "High 4:4:4 Predictive": + # key = 'AVCHigh444PredictiveUnconstrained' + key = 'CompressedPicture' + elif profile == "High 4:4:4 Intra": + # key = 'AVCHigh444IntraUnconstrained' + key = 'CompressedPicture' + elif profile == 'CAVLC 4:4:4': + # key = 'AVCCAVLC444IntraUnconstrained' + key = 'CompressedPicture' + + return video.compression_ids[key] + + def get_compression(self): + if not self.is_picture: + return None + + codec_name = self.metadata.get('codec_name', None) + if codec_name == 'mjpeg': + return video.compression_ids['mjpeg'] + if codec_name == 'h264': + return self.get_avc_compression() + + return video.compression_ids['CompressedPicture'] + + def create_video_descriptor(self, f): + if not self.is_picture: + return None + + d = f.create.CDCIDescriptor() + + depth, h_samp, v_samp = self.pixel_sizes() + + width = self.metadata['width'] + height = self.metadata['height'] + + aspect_ratio = "%d/%d" % (width, height) + + d['ComponentWidth'].value = depth + d['HorizontalSubsampling'].value = h_samp + d['VerticalSubsampling'].value = v_samp + d['FrameLayout'].value = 'FullFrame' + + d['VideoLineMap'].value = [0, 0] + # d['VideoLineMap'].value = [42, 0] + d['ImageAspectRatio'].value = aspect_ratio + + d['StoredWidth'].value = width + d['StoredHeight'].value = height + d['SampleRate'].value = self.metadata['avg_frame_rate'] + compression = self.get_compression() + + d['Compression'].value = compression + + # d['ResolutionID'].value = 2900 + d['Length'].value = int(self.length) + + return d + + +def create_media_link(f, path, metadata): + """ + Create an essence linked to external media and all obligatory mobs and data structures required by + the edit spec. + + The returned :class:`aaf.mobs.MasterMob` will have one slot for each video stream and each audio channel + in the file at `path`. + + Example: The linked file is a Quicktime movie with picture and a stereo audio track. This function will create a + SourceMob with three slots, one picture slot, and two sound slots, for audio channels one and two respectively. + The function will also create a derivation SourceMob, linked to these slots. + + :param f: The :class:`aaf.File` to add this link to + :param path: A path recognizable to `os.path` + :param metadata: Pre-fetched media description (in the form of a dictionary) + from "ffprobe -show_format -show_streams" + :return: A `aaf.mobs.MasterMob` linked to the file at link. + """ + + def tape_mob_for_format(name, format_info): + tape_mob = f.create.SourceMob() + tape_mob.name = name + picture = format_info.first_picture_stream + if picture is not None: + pix_slot = tape_mob.create_picture_slot(edit_rate=picture.edit_rate) + pix_slot.segment.length = picture.length + tape_source_clip = f.create.SourceClip(media_kind='picture') + tape_source_clip.length = picture.length + pix_slot.segment.components.append(tape_source_clip) + + sound = format_info.first_sound_stream + if sound is not None: + for channel in range(sound.physical_track_count): + sound_slot = tape_mob.create_sound_slot(edit_rate=sound.edit_rate) + sound_slot.segment.length = sound.length + tape_source_clip = f.create.SourceClip(media_kind='sound') + tape_source_clip.length = sound.length + sound_slot.segment.components.append(tape_source_clip) + # not setting PhysicalTrackNumber here because we didn't before + + f.content.mobs.append(tape_mob) + tape_mob.descriptor = f.create.ImportDescriptor() + + return tape_mob + + def append_source_to_mob_as_new_slots(from_mob, to_mob): + sound_physical_track = 1 + for from_slot in from_mob.slots: + slot_kind = from_slot.media_kind + if slot_kind in ("Picture", "Sound"): + from_clip = from_mob.create_source_clip(slot_id=from_slot.slot_id, media_kind=slot_kind) + to_slot = to_mob.create_empty_sequence_slot(edit_rate=from_slot.edit_rate, + media_kind=from_clip.media_kind) + to_slot.segment.components.append(from_clip) + + if slot_kind == 'Sound': + to_slot['PhysicalTrackNumber'].value = sound_physical_track + sound_physical_track += 1 + + def source_mob_from_tape_mob(name, tape_mob): + source_mob = f.create.SourceMob() + source_mob.name = name + + append_source_to_mob_as_new_slots(from_mob=tape_mob, to_mob=source_mob) + f.content.mobs.append(source_mob) + + return source_mob + + def master_mob_from_source_mob(name, source_mob): + master_mob = f.create.MasterMob() + master_mob.name = name + + append_source_to_mob_as_new_slots(from_mob=source_mob, to_mob=master_mob) + + f.content.mobs.append(master_mob) + + return master_mob + + def create_mobs(name, format_info): + tmob = tape_mob_for_format(name + ' ', format_info) + smob = source_mob_from_tape_mob(name + ' ', tmob) + mmob = master_mob_from_source_mob(name, smob) + + if format_info.first_picture_stream is not None: + # MC Quicktime plugin will error if this is not set + smob.comments['Video'] = format_info.first_picture_stream.codec_name + + return mmob, smob, tmob + + basename = os.path.basename(path) + name, ext = os.path.splitext(basename) + + if ext == '.mxf' or ext == '.MXF': + m = mxf.MXFFile(path) + m.ama = True + m.dump() + return m.link(f) + + format_info = FormatInfo(metadata) + source_descriptor = format_info.create_descriptor(f, path) + + if source_descriptor is None: + return None + + master_mob, source_mob, tape_mob = create_mobs(name, format_info) + source_mob.descriptor = source_descriptor + return master_mob, source_mob, tape_mob diff --git a/aaf2/audio.py b/aaf2/audio.py new file mode 100644 index 0000000..62a3d43 --- /dev/null +++ b/aaf2/audio.py @@ -0,0 +1,65 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +import wave +import struct + +pcm_profiles = { +'pcm_32000_s16le' : {'sample_format' : 'pcm_s16le', 'sample_rate' : 32000}, +'pcm_32000_s24le' : {'sample_format' : 'pcm_s24le', 'sample_rate' : 32000}, + +'pcm_44100_s16le' : {'sample_format' : 'pcm_s16le', 'sample_rate' : 44100}, +'pcm_44100_s24le' : {'sample_format' : 'pcm_s24le', 'sample_rate' : 44100}, + +'pcm_48000_s16le' : {'sample_format' : 'pcm_s16le', 'sample_rate' : 48000}, +'pcm_48000_s24le' : {'sample_format' : 'pcm_s24le', 'sample_rate' : 48000}, +} + +audio_format_sizes = { +'s16' : (16, 2), +'s32' : (32, 4), +'flt' : (32, 4), +'dbl' : (64, 8), +'u8p' : (8, 1), +'s16p' : (16, 2), +'s32p' : (32, 4), +'fltp' : (32, 4), +'dblp' : (64, 8), +'s64' : (64, 8), +'s64p' : (64, 8), +} + +WAVE_EXTENSIBLE_PCM=0xFFFE + + +class WaveReader(wave.Wave_read): + def __init__(self, f): + self._blockalign = None + # can't use super in OldStyle 2.7 class + wave.Wave_read.__init__(self, f) + + def _read_fmt_chunk(self, chunk): + + # added support for wave extensible + + wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from(b' self.dir.byte_size: + # logging.debug("overseek %d bytes, padding with zeros" % (offset - self.dir.byte_size)) + self.pos = self.dir.byte_size + bytes_left = offset - self.dir.byte_size + min_seek_size = self.storage.sector_size * 4 + while bytes_left: + bytes_to_write = min(min_seek_size, offset - self.dir.byte_size) + zeros = bytearray(bytes_to_write) + self.write(zeros) + bytes_left -= bytes_to_write + + self.pos = offset + return offset + + def is_mini_stream(self): + if self.dir.type == 'root storage': + return False + return self.dir.byte_size < self.storage.min_stream_max_size + + def sector_size(self): + if self.is_mini_stream(): + return self.storage.mini_stream_sector_size + else: + return self.storage.sector_size + + def sector_offset(self): + return self.pos % self.sector_size() + + def sector_index(self): + return self.pos // self.sector_size() + + def read(self, n=-1): + + byte_size = self.dir.byte_size + if n == -1: + bytes_to_read = max(0, byte_size - self.tell()) + else: + bytes_to_read = max(0, min(n, byte_size - self.tell())) + + result = bytearray(bytes_to_read) + mv = memoryview(result) + + is_mini_stream = byte_size < self.storage.min_stream_max_size + full_sector_size = self.storage.sector_size + mini_sector_size = self.storage.mini_stream_sector_size + mini_stream_chain = self.storage.mini_stream_chain + read_sector_data = self.storage.read_sector_data + sector_data = None + prev_sid = -1 + + if is_mini_stream: + mini_fat_index = self.pos // mini_sector_size + mini_sector_offset = self.pos % mini_sector_size + sector_size = mini_sector_size + else: + index = self.pos // full_sector_size + start_offset = self.pos % full_sector_size + sector_size = full_sector_size + + while bytes_to_read > 0: + + # inlined on purpose this loop runs alot + if is_mini_stream: + mini_stream_sid = self.fat_chain[mini_fat_index] + mini_stream_pos = (mini_stream_sid * mini_sector_size) + mini_sector_offset + + index = mini_stream_pos // full_sector_size + sid_offset = mini_stream_pos % full_sector_size + + sid = mini_stream_chain[index] + sector_offset = mini_sector_offset + + mini_sector_offset = 0 + mini_fat_index += 1 + + if sid != prev_sid: + sector_data = read_sector_data(sid) + prev_sid = sid + + else: + sid = self.fat_chain[index] + sector_offset = start_offset + sid_offset = start_offset + + index += 1 + start_offset = 0 + + sector_data = read_sector_data(sid) + + bytes_can_read = min(bytes_to_read, sector_size - sector_offset) + assert bytes_can_read > 0 + + mv[:bytes_can_read] = sector_data[sid_offset:sid_offset+bytes_can_read] + + self.pos += bytes_can_read + mv = mv[bytes_can_read:] + + bytes_to_read -= bytes_can_read + + return result + + def allocate(self, byte_size): + + minifat = self.is_mini_stream() + realloc_data = None + orig_pos = None + # convert from minifat to fat + if minifat and byte_size >= self.storage.min_stream_max_size: + # logging.debug("converting stream for minifat to fat") + orig_pos = self.pos + self.seek(0) + realloc_data = self.read() + assert len(realloc_data) == self.dir.byte_size + self.storage.free_fat_chain(self.dir.sector_id, True) + self.dir.sector_id = None + minifat = False + self.fat_chain = [] + + self.dir.byte_size = byte_size + sector_size = self.sector_size() + sector_count = (byte_size + sector_size - 1) // sector_size + + current_sects= len(self.fat_chain) + # logging.debug("%d bytes requires %d sectors at %d has %d" % (byte_size, sector_count, self.sector_size(), current_sects)) + + while len(self.fat_chain) < sector_count: + last_sector_id = self.fat_chain[-1] if self.fat_chain else None + sid = self.storage.fat_chain_append(last_sector_id, minifat) + self.fat_chain.append(sid) + if self.dir.sector_id is None: + self.dir.sector_id = sid + + if realloc_data is not None: + self.pos = 0 + self.write(realloc_data) + self.pos = min(orig_pos, len(realloc_data)) + + def write(self, data): + data_size = len(data) + current_size = self.dir.byte_size + new_size = max(self.tell() + data_size, current_size) + if new_size > current_size: + self.allocate(new_size) + + mv = memoryview(data) + + is_mini_stream = self.dir.byte_size < self.storage.min_stream_max_size + full_sector_size = self.storage.sector_size + mini_sector_size = self.storage.mini_stream_sector_size + mini_stream_chain = self.storage.mini_stream_chain + sector_cache = self.storage.sector_cache + f = self.storage.f + + if is_mini_stream: + mini_fat_index = self.pos // mini_sector_size + mini_sector_offset = self.pos % mini_sector_size + sector_size = mini_sector_size + else: + index = self.pos // full_sector_size + sid_offset = self.pos % full_sector_size + sector_size = full_sector_size + + while data_size > 0: + + # inlined on purpose this method can get called alot + if is_mini_stream: + mini_stream_sid = self.fat_chain[mini_fat_index] + mini_stream_pos = (mini_stream_sid * mini_sector_size) + mini_sector_offset + + index = mini_stream_pos // full_sector_size + sid_offset = mini_stream_pos % full_sector_size + + sid = mini_stream_chain[index] + + sector_offset = mini_sector_offset + seek_pos = ((sid + 1) * full_sector_size) + sid_offset + + mini_fat_index += 1 + mini_sector_offset = 0 + + else: + sid = self.fat_chain[index] + sector_offset = sid_offset + + seek_pos = ((sid + 1) * full_sector_size) + sid_offset + + index += 1 + sid_offset = 0 + + byte_writeable = min(len(mv), sector_size - sector_offset) + assert byte_writeable > 0 + + if sid in sector_cache: + del sector_cache[sid] + + f.seek(seek_pos) + f.write(mv[:byte_writeable]) + self.pos += byte_writeable + + mv = mv[byte_writeable:] + data_size -= byte_writeable + + assert self.pos <= self.dir.byte_size + + def truncate(self, size=None): + # print("trunc", self.dir.path()) + if size is None: + size = self.pos + + current_byte_size = self.dir.byte_size + is_mini_stream = self.is_mini_stream() + full_sector_size = self.storage.sector_size + mini_sector_size = self.storage.mini_stream_sector_size + + # free the stream + if size == 0: + self.storage.free_fat_chain(self.dir.sector_id, is_mini_stream) + self.pos = 0 + self.dir.sector_id = None + self.dir.byte_size = 0 + self.fat_chain = [] + return + + + # grown the stream + if size > current_byte_size: + self.allocate(size) + return + + # shrink to mini stream + if size < self.storage.min_stream_max_size and not is_mini_stream and self.dir.type != 'root storage': + orig_pos = self.pos + self.pos = 0 + + realloc_data = self.read(size) + self.storage.free_fat_chain(self.dir.sector_id, False) + + self.pos = 0 + self.dir.sector_id = None + self.dir.byte_size = 0 + self.fat_chain = [] + + self.write(realloc_data) + self.pos = min(orig_pos, size) + assert self.dir.byte_size == size + return + + if is_mini_stream: + sector_size = mini_sector_size + fat_table = self.storage.minifat + else: + sector_size = full_sector_size + fat_table = self.storage.fat + + sector_count = (size + sector_size - 1) // sector_size + + if len(self.fat_chain) > sector_count: + last_sector_id = self.fat_chain[sector_count-1] + self.storage.free_fat_chain(self.fat_chain[sector_count], is_mini_stream) + fat_table[last_sector_id] = ENDOFCHAIN + self.fat_chain = self.fat_chain[:sector_count] + + self.dir.byte_size = size + self.pos = min(self.pos, size) + + def close(self): + pass + + +def is_red(entry): + if (entry is not None) and entry.red: + return True + return False + +def is_not_red(entry): + return not is_red(entry) + +def is_parent_of(parent, entry): + return parent[0] is entry or parent[1] is entry + +def validate_rbtree(root): + if root is None: + return 1 + + left = root.left() + right = root.right() + + if is_red(root): + if is_red(left) or is_red(right): + print("Red violation {}".format(root.path())) + # raise CompoundFileBinaryError() + + lh = validate_rbtree(left) + rh = validate_rbtree(right) + + if left is not None and left >= root: + raise CompoundFileBinaryError("Binary tree violation" ) + + if right is not None and right <= root: + raise CompoundFileBinaryError("Binary tree violation") + + # Black height mismatch + # cannot gerentee all aaf Implementions use rbtree + # if lh != 0 and rh != 0 and lh != rh: + # print(lh, rh) + # raise CompoundFileBinaryError("Black violation {}".format(root.path())) + + if lh != 0 and rh != 0: + return lh if is_red(root) else lh + 1 + else: + return 0 + +def jsw_single(root, direction): + other_side = 1 - direction + + new_root = root[other_side] + + root[other_side] = new_root[direction] + new_root[direction] = root + + root.red = True + new_root.red = False + + return new_root + +def jsw_double(root, direction): + other_side = 1 - direction + root[other_side] = jsw_single(root[other_side], other_side) + return jsw_single(root, direction) + +def find_entry_parent(root, entry, max_depth): + parent = None + node = root + count = 0 + while node is not None and count < max_depth: + if node is entry: + return parent + + direction = 0 if entry < node else 1 + parent = node + node = node[direction] + count += 1 + + raise CompoundFileBinaryError("Max Depth Exceeded") + +def get_entry_path(root, entry, max_depth): + parent = None + node = root + count = 0 + path = [] + while node is not None and count < max_depth: + + path.append(node) + if node is entry: + break + direction = 0 if entry < node else 1 + parent = node + node = node[direction] + count += 1 + + return path + + +class DirEntry(object): + __slots__ = ('storage', 'dir_id', 'parent', 'data', '_name', '__weakref__') + + def __init__(self, storage, dir_id, data=None): + self.storage = storage + + self.parent = None + if data is None: + self.data = bytearray(128) + # setting dir_id to None disable mark_modified + self.dir_id = None + self.left_id = None + self.right_id = None + self.child_id = None + self.sector_id = None + else: + self.data = data + + # mark modified will now work + self.dir_id = dir_id + self._name = sentinel + + @property + def name(self): + if self._name is not sentinel: + return self._name + name_size = unpack_u16le_from(self.data, 64) + assert name_size <= 64 + name = decode_utf16le(self.data[:name_size]) + self._name = name + return name + + @name.setter + def name(self, value): + name_data = value.encode("utf-16le") + name_size = len(name_data) + assert name_size <= 64 + self._name = value + self.data[:name_size] = name_data + pad = 64 - name_size + for i in range(pad): + self.data[name_size +i] = 0 + + # includes null terminator? should re-verify this + struct.pack_into(str(' 128: + self.storage.write_modified_dir_entries() + + def __lt__(self, other): + if isinstance(other, DirEntry): + other = other.name + + if len(self.name) == len(other): + # compare not case senstive + return self.name.upper() < other.upper() + else: + # shorter names are always less then + return len(self.name) < len(other) + + def __le__(self, other): + if self == other: + return True + return self < other + + def __gt__(self, other): + return other < self + + def __ge__(self, other): + if self == other: + return True + return self > other + + def __eq__(self, other): + if other is None: + return False + + if isinstance(other, DirEntry): + other = other.name + if len(self.name) == len(other): + return self.name.upper() == other.upper() + return False + + def __getitem__(self, index): + return self.left() if index == 0 else self.right() + + def __setitem__(self, index, value): + if value is None: + dir_id = None + else: + dir_id = value.dir_id + + if index == 0: + self.left_id = dir_id + else: + self.right_id = dir_id + + def left(self): + return self.storage.read_dir_entry(self.left_id, self.parent) + + def right(self): + return self.storage.read_dir_entry(self.right_id, self.parent) + + def child(self): + return self.storage.read_dir_entry(self.child_id, self) + + def add_child(self, entry): + entry.parent = self + entry.color = 'black' + child = self.child() + if child is None: + self.child_id = entry.dir_id + else: + # child.insert_old(entry) + + # make sure entry is part of cache + # or insert might now work correctly + self.storage.dir_cache[entry.dir_id] = entry + self.insert(entry) + + if self.dir_id in self.storage.children_cache: + self.storage.children_cache[self.dir_id][entry.name] = entry + + def insert(self, entry): + """ + Inserts entry into child folder tree. + Trys to mantains a balanced red black tree. + Technique is base on topdown insert approach in described in + https://eternallyconfuzzled.com/red-black-trees-c-the-most-common-balanced-binary-search-tree + """ + + dir_per_sector = self.storage.sector_size // 128 + max_dirs_entries = self.storage.dir_sector_count * dir_per_sector + + head = DirEntry(self.storage, None) # False tree root + head.red = True + entry.red = True + + grand_grand_grand_parent = None + grand_grand_parent = head + grand_parent = None + parent = None + direction = 0 + last = 0 + + node = self.child() + self.child_id = None + grand_grand_parent.right_id = node.dir_id + + assert node + count = 0 + + while count < max_dirs_entries: + + if node is None: + node = entry + parent[direction] = node + + elif is_red(node[0]) and is_red(node[1]): + # Color flip + node.red = True + node[0].red = False + node[1].red = False + + # Fix red violations + if is_red(node) and is_red(parent): + if grand_grand_parent[0] is grand_parent: + direction2 = 0 + elif grand_grand_parent[1] is grand_parent: + direction2 = 1 + else: + raise CompoundFileBinaryError() + + if node is parent[last]: + grand_grand_parent[direction2] = jsw_single(grand_parent, 1 - last) + # restore parent references + # NOTE: Example implementation doesn't do this + grand_parent = grand_grand_parent + grand_grand_parent = grand_grand_grand_parent + + # assert is_parent_of(parent, node) + # assert is_parent_of(grand_parent, parent) + + elif node is parent[1-last]: + grand_grand_parent[direction2] = jsw_double(grand_parent, 1 - last) + # restore parent references + # NOTE: Example implementation doesn't do this + parent = grand_grand_parent + if parent is head: + grand_parent = None + else: + grand_parent = grand_grand_grand_parent + grand_grand_parent = None + + # assert is_parent_of(parent, node) + # if grand_parent is not None: + # assert is_parent_of(grand_parent, parent) + else: + # can this happen? + raise CompoundFileBinaryError() + + # node has been inserted + if node is entry: + break + + last = direction + direction = 0 if entry < node else 1 + + if grand_grand_parent is not None: + grand_grand_grand_parent = grand_grand_parent + + if grand_parent is not None: + grand_grand_parent = grand_parent + + grand_parent = parent + parent = node + node = node[direction] + + assert is_parent_of(parent, node) + if grand_parent: + assert is_parent_of(grand_parent, parent) + if grand_grand_parent: + assert is_parent_of(grand_grand_parent, grand_parent) + + count += 1 + + if count >= max_dirs_entries: + raise CompoundFileBinaryError("max dir entries limit reached") + + # update root of tree as it could have changed + self.child_id = head.right_id + self.child().red = False + + def pop(self): + """ + remove self from self.parent folder binary search tree. + Tries to maintain a balanced red black tree. + Technique is base on topdown remove approach in described in + https://eternallyconfuzzled.com/red-black-trees-c-the-most-common-balanced-binary-search-tree + """ + entry = self + + dir_per_sector = self.storage.sector_size // 128 + max_dirs_entries = self.storage.dir_sector_count * dir_per_sector + count = 0 + + head = DirEntry(self.storage, None) # False tree root + head.red = True + head.name = "" # NOTE: any name will be less then this + node = head + node[1] = self.parent.child() + grand_parent = None + parent = None + entry_parent = None + entry_grand_parent = None + direction = 1 + found = None + + # This keeps going until predecessor is found, even if entry is found + while node[direction] is not None and count < max_dirs_entries: + + last = direction + grand_parent = parent + parent = node + node = node[direction] + + # The trick here is after entry is found + # this will continue to be used to find the predecessor of entry. + # its quite clever! + direction = int(node < entry) + + if node is entry: + # store the grand parent because the parent of entry + # can change during rebalance below + if grand_parent is None: + entry_grand_parent = head + else: + entry_grand_parent = grand_parent + found = node + + # Push the red node down + if is_not_red(node) and is_not_red(node[direction]): + + if is_red(node[1 - direction]): + parent[last] = jsw_single(node, direction) + parent = parent[last] + assert is_parent_of(parent, node) + + elif is_not_red(node[1 - direction]): + sibling = parent[1 - direction] + if sibling is not None: + + if is_not_red(sibling[1 - last]) and is_not_red(sibling[last]): + # Color flip + parent.red = False + sibling.red = True + node.red = True + else: + if grand_parent[0] == parent: + direction2 = 0 + elif grand_parent[1] == parent: + direction2 = 1 + else: + # can this happen? + raise CompoundFileBinaryError() + + if is_red(sibling[last]): + grand_parent[direction2] = jsw_double(parent, last) + + elif is_red(sibling[1 - last]): + grand_parent[direction2] = jsw_single(parent, last) + + # Ensure correct coloring + node.red = True + grand_parent[direction2].red = True; + grand_parent[direction2][0].red = False; + grand_parent[direction2][1].red = False; + + assert is_parent_of(parent, node) + + count += 1 + + assert found + if count >= max_dirs_entries: + raise CompoundFileBinaryError("max dir entries limit reached") + + # entry parent could have changed during rebalance + max_search_depth = 4 # grand_parent -> parent -> node + entry_parent = find_entry_parent(entry_grand_parent, entry, max_search_depth) + + if entry_parent[0] is entry: + entry_direction = 0 + elif entry_parent[1] is entry: + entry_direction = 1 + else: + raise CompoundFileBinaryError("Unable to find entry parent") + + # node is the predecessor. node will be removed and entry will be replaced + if node is not entry: + # remove the predecessor + parent[parent[1] is node] = node[node[0] is None] + + # replace entry with its predecessor + node[0] = entry[0] + node[1] = entry[1] + node.red = entry.red + + entry_parent[entry_direction] = node + else: + entry_side = int(entry[0] is None) + entry_parent[entry_direction] = entry[entry_side] + + # update root of tree as it could have changed + self.parent.child_id = head.right_id + if self.parent.child_id: + self.parent.child().red = False + + # clear from cache + if self.parent.dir_id in self.storage.children_cache: + del self.storage.children_cache[self.parent.dir_id][entry.name] + + # clear parent and left and right + self.left_id = None + self.right_id = None + self.parent = None + + def rebalance_children_tree(self): + + children = self.listdir() + self.child_id = None + random.shuffle(children) + for c in children: + c.left_id = None + c.right_id = None + self.add_child(c) + + + def path(self): + path = [] + parent = self + while parent: + name = parent.name + if name == "Root Entry": + break + path.append(parent.name) + parent= parent.parent + return '/' + '/'.join(reversed(path)) + + def open(self, mode='r'): + if self.type != 'stream': + raise TypeError("can only open streams") + return self.storage.open(self, mode) + + def isdir(self): + return self.type in ('storage', 'root storage') + + def isroot(self): + return self.type == 'root storage' + + def listdir(self): + return self.storage.listdir(self) + + def makedir(self, relative_path, class_id = None): + if not self.isdir(): + raise TypeError("can only add a DirEntry to a storage type") + sep = '/' + if self.isroot(): + sep = '' + + path = self.path() + sep + relative_path + return self.storage.makedir(path, class_id) + + def isfile(self): + return self.type == 'stream' + + def get(self, name, default=None): + dir_dict = self.storage.listdir_dict(self) + return dir_dict.get(name, default) + + def touch(self, name): + item = self.get(name, None) + if item: + return item + + sep = '/' + if self.isroot(): + sep = '' + + path = self.path() + sep + name + return self.storage.create_dir_entry(path, 'stream', None) + + def write(self): + f = self.storage.f + f.seek(self.storage.dir_entry_pos(self.dir_id)) + f.write(self.data) + + def read(self): + f = self.storage.f + f.seek(self.storage.dir_entry_pos(self.dir_id)) + f.readinto(self.data) + + def __repr__(self): + return self.name + +def extend_sid_table(f, table, byte_size): + n = byte_size // 4 + if isinstance(f, io.RawIOBase): + table.fromfile(f, n) + elif hasattr(table, 'frombytes'): + table.frombytes(f.read(byte_size)) + else: + # try deprecated from string + table.fromstring(f.read(byte_size)) + +class CompoundFileBinary(object): + def __init__(self, file_object, mode='rb', sector_size=4096): + + self.f = file_object + + self.difat = [[]] + self.fat = array(str('I')) + self.fat_freelist = [] + + self.minifat = array(str('I')) + self.minifat_freelist = [] + + self.difat_chain = [] + self.minifat_chain = [] + self.dir_fat_chain = [] + + self.mini_stream_chain = [] + + self.modified = {} + + self.sector_cache = LRUCacheDict() + self.dir_cache = weakref.WeakValueDictionary() + self.children_cache = LRUCacheDict() + self.dir_freelist = [] + + self.debug_grow = False + self.is_open = True + + if isinstance(self.f, BytesIO): + self.mode = 'wb+' + else: + self.mode = mode + + if self.mode in ("r", "r+", "rb", 'rb+'): + + self.read_header() + self.read_fat() + mini_stream_byte_size = self.read_minifat() + + # create dir_fat_chain and read root dir entry + self.dir_fat_chain = self.get_fat_chain(self.dir_sector_start) + if len(self.dir_fat_chain) != self.dir_sector_count: + logging.info("read dir_sector_count missmatch, using fat chain length") + self.dir_sector_count = len(self.dir_fat_chain) + + logging.debug("read %d dir sectors" % len(self.dir_fat_chain)) + self.root = self.read_dir_entry(0) + self.dir_cache[0] = self.root + + # create mini stream fat chain + if self.minifat_sector_count: + self.mini_stream_chain = self.get_fat_chain(self.root.sector_id) + + if self.root.sector_id is not None and mini_stream_byte_size != self.root.byte_size: + message = "mini stream size missmatch: %d != %d, using size from minifat" + logging.warn(message % (self.root.byte_size, mini_stream_byte_size)) + else: + self.setup_empty(sector_size) + self.write_header() + + logging.debug("pos: %d" % self.f.tell()) + + logging.debug("writing root dir sector") + self.root.write() + self.f.write(bytearray(self.sector_size - 128)) + self.write_fat() + + def close(self): + if self.mode in ("r", "rb"): + return + + # calculate mini stream size + if self.root.sector_id is not None: + # I cannot find this documented anywhere but the size of the mini stream + # is the size up to the last mini sector is uses. Not the total Non FREESECT's. + # If self.root.byte_size is not set correctly the some applications will crash hard... + + # find last non-free sect + for i,v in enumerate(reversed(self.minifat)): + if v != FREESECT: + break + + last_used_sector_id = len(self.minifat) - i + mini_stream_byte_size = (last_used_sector_id * self.mini_stream_sector_size) + self.root.byte_size = mini_stream_byte_size + + # Truncate ministream + s = Stream(self, self.root, 'rw') + s.truncate(mini_stream_byte_size) + + self.write_header() + self.write_difat() + self.write_fat() + self.write_minifat() + self.write_dir_entries() + + # Truncate file to the last free sector + for i,v in enumerate(reversed(self.fat)): + if v != FREESECT: + break + + last_used_sector_id = len(self.fat) - i + pos = (last_used_sector_id + 1) * self.sector_size + self.f.seek(pos) + self.f.truncate() + + self.is_open = False + + def setup_empty(self, sector_size): + + if sector_size == 4096: + self.class_id = auid.AUID("0d010201-0200-0000-060e-2b3403020101") + elif sector_size == 512: + self.class_id = auid.AUID("42464141-000d-4d4f-060e-2b34010101ff") + else: + raise ValueError("sector size must be 4096 or 512") + + self.major_version = 4 + self.minor_version = 62 + + self.byte_order = "le" + + self.sector_size = sector_size + self.mini_stream_sector_size = 64 + + self.dir_sector_count = 1 + self.fat_sector_count = 1 + self.dir_sector_start = 0 + + self.transaction_signature = 1 + self.min_stream_max_size = 4096 + + self.minifat_sector_start = FREESECT + self.minifat_sector_count = 0 + + self.difat_sector_start = FREESECT + self.difat_sector_count = 0 + + self.difat = [[]] + for i in range(109): + self.difat[0].append(FREESECT) + + self.difat[0][0] = 1 + + for i in range(self.sector_size // 4): + self.fat.append(FREESECT) + if i > 1: + self.fat_freelist.append(i) + + self.fat[0] = ENDOFCHAIN # end of dir chain + self.fat[self.difat[0][0]] = FATSECT + + self.root = DirEntry(self, 0) + self.root.name = 'Root Entry' + self.root.sector_id = None + self.root.type = 'root storage' + self.root.class_id = auid.AUID("b3b398a5-1c90-11d4-8053-080036210804") + + self.dir_cache[0] = self.root + + self.dir_fat_chain = [0] + + # raise NotImplementedError("mode: %s supported not implemented" % self.f.mode) + + def write_header(self): + logging.debug("writiing header") + f = self.f + f.seek(0) + f.write(b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1') # Magic + f.write(self.class_id.bytes_le) + write_u16le(f, self.minor_version) + write_u16le(f, self.major_version) + write_u16le(f, 0xFFFE) # byte order le + write_u16le(f, int(math.log(self.sector_size, 2))) + write_u16le(f, int(math.log(self.mini_stream_sector_size, 2))) + f.write(b'\0' * 6) # skip reserved + + write_u32le(f, self.dir_sector_count) + write_u32le(f, self.fat_sector_count) + write_u32le(f, self.dir_sector_start) + write_u32le(f, self.transaction_signature) + write_u32le(f, self.min_stream_max_size) + + write_u32le(f, self.minifat_sector_start) + write_u32le(f, self.minifat_sector_count) + + write_u32le(f, self.difat_sector_start) + write_u32le(f, self.difat_sector_count) + + for i in range(109): + write_u32le(f, self.difat[0][i]) + + for i in range(self.sector_size - f.tell()): + f.write(b'\0') + + + def read_header(self): + + f = self.f + f.seek(0) + + magic = f.read(8) + # logging.debug("magic: %s" % magic.encode("hex")) + logging.debug("magic: %s" % str([magic])) + + # clsid = f.read(16) + # logging.debug("clsid: %s" % clsid.encode("hex")) + self.class_id = auid.AUID(bytes_le=f.read(16)) + logging.debug("clsid: %s" % str(self.class_id)) + + self.minor_version = read_u16le(f) + logging.debug("minor_version: %d" % self.minor_version) + + self.major_version = read_u16le(f) + logging.debug("major_version: %d" % self.major_version) + + byte_order = read_u16le(f) + if byte_order == 0xFFFE: + self.byte_order = 'le' + else: + raise NotImplementedError("endian format:0x%X not supported" % byte_order) + + logging.debug("byte_order: %s" % self.byte_order) + + size = read_u16le(f) + self.sector_size = pow(2, size) + logging.debug("sector_size: %d -> %d" % (size, self.sector_size)) + + size = read_u16le(f) + self.mini_stream_sector_size = pow(2, size) + logging.debug("mini_stream_sector_size: %d -> %d" % (size, self.mini_stream_sector_size)) + + if not self.sector_size in (4096, 512): + raise NotImplementedError("unsupported sector size: %d" % self.sector_size) + if self.mini_stream_sector_size != 64: + raise NotImplementedError("unsupported mini sector size: %d" % self.mini_stream_sector_size) + + f.read(6) # skip reserved + + self.dir_sector_count = read_u32le(f) + logging.debug("dir_sector_count: %d" % self.dir_sector_count) + + self.fat_sector_count = read_u32le(f) + logging.debug("fat_sector_count: %d" % self.fat_sector_count) + + self.dir_sector_start = read_u32le(f) + logging.debug("dir_sector_start: %d" % self.dir_sector_start) + + self.transaction_signature = read_u32le(f) + logging.debug("transaction_signature: %d" % self.transaction_signature) + + self.min_stream_max_size = read_u32le(f) + logging.debug("min_stream_max_size: %d" % self.min_stream_max_size) + + self.minifat_sector_start = read_u32le(f) + logging.debug("minifat_sector_start: %d" % self.minifat_sector_start) + + self.minifat_sector_count = read_u32le(f) + logging.debug("minifat_sector_count: %d" % self.minifat_sector_count) + + self.difat_sector_start = read_u32le(f) + logging.debug("difat_sector_start: %d" % self.difat_sector_start) + + self.difat_sector_count = read_u32le(f) + logging.debug("difat_sector_count: %d" % self.difat_sector_count) + + self.difat = [[]] + + logging.debug("reading header difat at %d" % f.tell()) + for i in range(109): + item = read_u32le(f) + # item = fat_sector_types.get(item, item) + self.difat[0].append(item) + + sectors_left = self.difat_sector_count + + sid = self.difat_sector_start + + # reading difat sectors + while sectors_left: + logging.debug("reading difat sid: %d", sid) + sector_type = fat_sector_types.get(sid, sid) + if not isinstance(sector_type, int): + break + + self.difat_chain.append(sid) + f.seek((sid + 1) * self.sector_size) + difat = [] + for i in range( (self.sector_size // 4)): + item = read_u32le(f) + difat.append(item) + self.difat.append(difat) + + sid = difat[-1] + logging.debug("next difat: %d" % sid) + sectors_left -= 1 + + def iter_difat(self): + for i, sid in enumerate(self.difat[0]): + yield 0, i, sid + + t = 1 + for item in self.difat[1:]: + for i, sid in enumerate(item[:-1]): + yield t, i, sid + t+=1 + + + def write_difat(self): + f = self.f + # write header entries + f.seek(76) + + logging.debug("writing header difat") + for i in range(109): + write_u32le(f, self.difat[0][i]) + + for i in range(self.sector_size - f.tell()): + f.write(b'\0') + + if self.difat_sector_count == 0: + return + + sid = self.difat_sector_start + assert len(self.difat[1:]) == self.difat_sector_count + for table in self.difat[1:]: + + sector_type = fat_sector_types.get(sid, sid) + if not isinstance(sector_type, int): + raise IOError("bad difat sector type") + + pos = (sid + 1) * self.sector_size + logging.debug("writing difat to sid: %d at: %d" % (sid,pos)) + f.seek(pos) + for i in range(self.sector_size // 4): + write_u32le(f, table[i]) + + sid = table[-1] + + def read_fat(self): + f = self.f + self.fat = array(str('I')) + sector_count = 0 + fat_sectors = [] + for t, i, sid in self.iter_difat(): + + sector_type = fat_sector_types.get(sid, sid) + if not isinstance(sector_type, int): + continue + + fat_sectors.append(sid) + + # len(fat_sectors),self.fat_sector_count + # assert len(fat_sectors) == self.fat_sector_count + if len(fat_sectors) != self.fat_sector_count: + logging.warn("fat sector count missmatch difat: %d header: %d" % (len(fat_sectors), self.fat_sector_count)) + self.fat_sector_count = len(fat_sectors) + + for sid in fat_sectors: + pos = (sid + 1) * self.sector_size + f.seek(pos) + extend_sid_table(f, self.fat, self.sector_size) + sector_count += 1 + + if sys.byteorder == 'big': + self.fat.byteswap() + + for i,v in enumerate(self.fat): + if v == FREESECT: + self.fat_freelist.append(i) + + logging.debug("read %d fat sectors ", sector_count) + + if self.sector_size == 4096 and len(self.fat) > RANGELOCKSECT: + if self.fat[RANGELOCKSECT] != ENDOFCHAIN: + logging.warn("range lock sector has data") + + # logging.debug("fat: %s" % str(pretty_sectors(self.fat))) + + def write_fat(self): + logging.debug("writing fat") + f = self.f + sector_count = 0 + + assert len(self.fat)*4 % self.sector_size == 0 + + fat_sectors = [] + + for t, i, sid in self.iter_difat(): + sector_type = fat_sector_types.get(sid, sid) + if not isinstance(sector_type, int): + continue + fat_sectors.append(sid) + + # check that the difat has enough entries to hold the current fat + assert len(fat_sectors) == len(self.fat)*4 // self.sector_size + + element_count = self.sector_size // 4 + fat_table_struct = Struct(str('<%dI' % element_count)) + for i, sid in enumerate(fat_sectors): + + # logging.debug("writing fat to sid: %d" % sid) + f.seek((sid + 1) * self.sector_size) + start = i * element_count + end = start + element_count + f.write(fat_table_struct.pack(*self.fat[start:end])) + + def read_minifat(self): + f = self.f + sector_count = 0 + self.minifat = array(str('I')) + + for sid in self.get_fat_chain(self.minifat_sector_start): + self.minifat_chain.append(sid) + f.seek((sid + 1) * self.sector_size) + extend_sid_table(f, self.minifat, self.sector_size) + sector_count += 1 + + if sys.byteorder == 'big': + self.minifat.byteswap() + + # mini_stream_byte_size = 0 + last_used_sector = 0 + for i,v in enumerate(self.minifat): + if v == FREESECT: + self.minifat_freelist.append(i) + else: + last_used_sector = i + # mini_stream_byte_size += self.mini_stream_sector_size + + mini_stream_byte_size = ((last_used_sector+1) * self.mini_stream_sector_size) + + # for i, sect in enumerate(pretty_sectors(self.minifat)): + # print(i, sect) + + logging.debug("read %d mini fat sectors", sector_count) + return mini_stream_byte_size + + def write_minifat(self): + f = self.f + sector_count = 0 + + element_count = self.sector_size // 4 + fat_table_struct = Struct(str('<%dI' % element_count)) + + for i, sid in enumerate(self.get_fat_chain(self.minifat_sector_start)): + pos = (sid + 1) * self.sector_size + f.seek(pos) + start = i * element_count + end = start + element_count + f.write(fat_table_struct.pack(*self.minifat[start:end])) + + def write_modified_dir_entries(self): + + f = self.f + for dir_id in sorted(self.modified): + entry = self.modified[dir_id] + stream_pos = entry.dir_id * 128 + chain_index = stream_pos // self.sector_size + sid_offset = stream_pos % self.sector_size + sid = self.dir_fat_chain[chain_index] + + pos = ((sid + 1) * self.sector_size) + sid_offset + + f.seek(pos) + # force black everything + # entry.data[67] = 0x01 + f.write(entry.data) + + # invalidate sector + if sid in self.sector_cache: + del self.sector_cache[sid] + + self.modified = {} + + def write_dir_entries(self): + self.write_modified_dir_entries() + + # clear empty DirEntrys + empty_dir = bytearray(128) + f = self.f + + self.dir_freelist.sort() + for dir_id in self.dir_freelist: + + stream_pos = dir_id * 128 + chain_index = stream_pos // self.sector_size + sid_offset = stream_pos % self.sector_size + sid = self.dir_fat_chain[chain_index] + + pos = ((sid + 1) * self.sector_size) + sid_offset + + f.seek(pos) + f.write(empty_dir) + + def next_free_minifat_sect(self): + + idx_per_sect = self.sector_size // self.mini_stream_sector_size + stream_sects = len(self.mini_stream_chain) * idx_per_sect + + if self.minifat_freelist: + i = self.minifat_freelist.pop(0) + assert self.minifat[i] == FREESECT + if i+1 > stream_sects: + self.mini_stream_grow() + return i + + # if we got here need to add additional fat + sid = self.next_free_sect() + # logging.warn("growing minifat to sid %d" % sid) + + idx_start = len(self.minifat) + idx_end = idx_start + self.sector_size // 4 + + self.minifat.extend([FREESECT for i in range(idx_start, idx_end)]) + self.minifat_freelist.extend([i for i in range(idx_start, idx_end)]) + + if self.minifat_sector_count == 0: + self.minifat_sector_count = 1 + self.minifat_sector_start = sid + else: + self.minifat_sector_count += 1 + self.fat[self.minifat_chain[-1]] = sid + + self.minifat_chain.append(sid) + self.fat[sid] = ENDOFCHAIN + + return self.next_free_minifat_sect() + + def next_free_sect(self): + + if self.fat_freelist: + # print("using fat free list") + i = self.fat_freelist.pop(0) + assert self.fat[i] == FREESECT + + # Handle Range Lock Sector + if i == RANGELOCKSECT and self.sector_size == 4096: + self.fat[i] = ENDOFCHAIN + logging.warning("range lock sector in fat freelist, marking ENDOFCHAIN") + return self.next_free_sect() + return i + + # if we got here need to add additional fat + # logging.debug("fat full, growing") + + difat_table = None + difat_index = None + + for t, i, v in self.iter_difat(): + if v == FREESECT: + difat_table = t + difat_index = i + break + + new_difat_sect = None + if difat_index is None: + new_difat_sect = len(self.fat) + 1 + logging.debug("adding new difat to sid: %d" % new_difat_sect) + if self.difat_sector_count == 0: + self.difat_sector_start = new_difat_sect + self.difat_sector_count = 1 + else: + self.difat[-1][-1] = new_difat_sect + self.difat_sector_count += 1 + + # add difat table + difat = [] + for i in range(self.sector_size // 4): + difat.append(FREESECT) + + difat[-1] == ENDOFCHAIN + self.difat.append(difat) + + for t, i, v in self.iter_difat(): + if v == FREESECT: + difat_table = t + difat_index = i + break + + new_fat_sect = len(self.fat) + # logging.debug("adding new fat to sid: %d" % new_fat_sect) + + self.difat[difat_table][difat_index] = new_fat_sect + + # grow fat entries + idx_start = len(self.fat) + idx_end = idx_start + (self.sector_size // 4) + + self.fat.extend([FREESECT for i in range(self.sector_size // 4)]) + + non_free_sids = set([new_fat_sect, new_difat_sect]) + + # Handle Range Lock Sector + # The range lock sector is the sector + # that covers file offsets 0x7FFFFF00-0x7FFFFFFF in the file + if RANGELOCKSECT < idx_end and RANGELOCKSECT > idx_start and self.sector_size == 4096: + non_free_sids.add(RANGELOCKSECT) + logging.debug("adding range lock") + self.fat[RANGELOCKSECT] = ENDOFCHAIN + + freelist = [i for i in range(idx_start, idx_end) if i not in non_free_sids] + + self.fat_freelist.extend(freelist) + + self.fat[new_fat_sect] = FATSECT + self.fat_sector_count += 1 + + if not new_difat_sect is None: + self.fat[new_difat_sect] = DIFSECT + + return self.next_free_sect() + + def read_sector_data(self, sid): + + sector_data = self.sector_cache.get(sid, None) + if sector_data is not None: + return sector_data + else: + pos = (sid + 1) * self.sector_size + self.f.seek(pos) + sector_data = bytearray(self.sector_size) + #NOTE: if requested sector doesn't exist or + # is truncated will pad with zeros, expected behaviour + bytes_read = self.f.readinto(sector_data) + self.sector_cache[sid] = sector_data + return sector_data + + def get_sid_offset(self, abs_pos): + sid, sid_offset = divmod(abs_pos, self.sector_size) + return sid-1, sid_offset + + def dir_entry_sid_offset(self, dir_id): + stream_pos = dir_id * 128 + chain_index, sid_offset = divmod(stream_pos, self.sector_size) + sid = self.dir_fat_chain[chain_index] + return sid, sid_offset + + def dir_entry_pos(self, dir_id): + sid, sid_offset = self.dir_entry_sid_offset(dir_id) + pos = ((sid + 1) * self.sector_size) + sid_offset + return pos + + def read_dir_entry(self, dir_id, parent = None): + if dir_id is None: + return None + + entry = self.dir_cache.get(dir_id, None) + if entry is not None: + return entry + + # print("reading", dir_id) + + # assert not dir_id in self.dir_freelist + + stream_pos = dir_id * 128 + chain_index = stream_pos // self.sector_size + sid_offset = stream_pos % self.sector_size + sid = self.dir_fat_chain[chain_index] + + sector_data = self.read_sector_data(sid) + + data= bytearray(sector_data[sid_offset:sid_offset+128]) + entry = DirEntry(self, dir_id, data=data) + + entry.parent = parent + self.dir_cache[dir_id] = entry + return entry + + def clear_sector(self, sid): + sector_pos = (sid + 1) * self.sector_size + self.f.seek(sector_pos) + # for i in range(self.sector_size): + self.f.write(bytearray(self.sector_size)) + + def next_free_dir_id(self): + + # use free list first + if self.dir_freelist: + return self.dir_freelist.pop(0) + + f = self.f + + sect = self.fat_chain_append(self.dir_fat_chain[-1]) + + self.dir_fat_chain.append(sect) + self.dir_sector_count += 1 + + first_dir_id = (len(self.dir_fat_chain) - 1) * self.sector_size // 128 + last_dir_id = first_dir_id + (self.sector_size // 128) + self.dir_freelist.extend(range(first_dir_id, last_dir_id)) + + return self.next_free_dir_id() + + def get_fat_chain(self, start_sid, minifat=False): + fat = self.fat + fat_name = "FAT" + if minifat: + fat = self.minifat + fat_name = "MINIFAT" + + # Floyd's Tortoise and Hare cycle-finding algorithm + a = start_sid + b = start_sid + sectors = [] + + if start_sid in (None, ENDOFCHAIN, FREESECT, DIFSECT, FATSECT): + return [] + + while b != ENDOFCHAIN: + sectors.append(b) + b = fat[b] + if a != ENDOFCHAIN: + a = fat[a] + if a != ENDOFCHAIN: + a = fat[a] + if a == b: + raise CompoundFileBinaryError('cyclic %s fat chain found starting at %d' % (fat_name, start_sid)) + + return sectors + + def mini_stream_grow(self): + sid = self.next_free_sect() + # logging.debug("adding to mini stream fat sid: %d" % sid) + if not self.mini_stream_chain: + self.mini_stream_chain = [sid] + self.root.sector_id = sid + else: + self.fat[self.mini_stream_chain[-1]] = sid + self.mini_stream_chain.append(sid) + + self.fat[sid] = ENDOFCHAIN + + def fat_chain_append(self, start_sid, minifat=False): + + if minifat: + sect = self.next_free_minifat_sect() + # logging.debug("creating new mini sector: %d" % sect) + fat = self.minifat + else: + sect = self.next_free_sect() + # logging.debug("creating new sector: %d" % sect) + fat = self.fat + + if start_sid is None: + fat[sect] = ENDOFCHAIN + else: + fat_chain = self.get_fat_chain(start_sid, minifat) + assert fat_chain + fat[fat_chain[-1]] = sect + fat[sect] = ENDOFCHAIN + + return sect + + def free_fat_chain(self, start_sid, minifat=False): + fat =self.fat + if minifat: + fat = self.minifat + + for sid in self.get_fat_chain(start_sid, minifat): + fat[sid] = FREESECT + if minifat: + self.minifat_freelist.insert(0, sid) + else: + self.fat_freelist.insert(0, sid) + + + def create_dir_entry(self, path, dir_type='storage', class_id=None): + + if self.exists(path): + raise ValueError("%s already exists" % path) + + dirname = os.path.dirname(path) + basename = os.path.basename(path) + + root = self.find(dirname) + + if root is None: + raise ValueError("parent dirname does not exist: %s" % dirname) + + if not root.type in ('storage', 'root storage'): + raise ValueError("can not add entry to non storage type") + + dir_id = self.next_free_dir_id() + logging.debug("next dir id %d" % dir_id) + + entry = DirEntry(self, dir_id) + entry.name = basename + entry.type = dir_type + entry.class_id = class_id + + root.add_child(entry) + self.dir_cache[dir_id] = entry + + return entry + + def free_dir_entry(self, entry): + + # add freelist + self.dir_freelist.append(entry.dir_id) + + # remove from dir caches + if entry.dir_id in self.dir_cache: + del self.dir_cache[entry.dir_id] + + if entry.dir_id in self.children_cache: + del self.children_cache[entry.dir_id] + + if entry.dir_id in self.modified: + del self.modified[entry.dir_id] + + entry.dir_id = None + + + def remove(self, path): + """ + Removes both streams and storage DirEntry types from file. + storage type entries need to be empty dirs. + """ + + entry = self.find(path) + + if not entry: + raise ValueError("%s does not exists" % path) + + if entry.type == 'root storage': + raise ValueError("can no remove root entry") + + if entry.type == "storage" and not entry.child_id is None: + raise ValueError("storage contains children") + + entry.pop() + + # remove stream data + if entry.type == "stream": + self.free_fat_chain(entry.sector_id, entry.byte_size < self.min_stream_max_size) + + self.free_dir_entry(entry) + + + def rmtree(self, path): + """ + Removes directory structure, similar to shutil.rmtree. + """ + for root, storage, streams in self.walk(path, topdown=False): + + for item in streams: + self.free_fat_chain(item.sector_id, item.byte_size < self.min_stream_max_size) + self.free_dir_entry(item) + + for item in storage: + self.free_dir_entry(item) + + root.child_id = None + + # remove root item + self.remove(path) + + + def listdir(self, path = None): + """ + Return a list containing the ``DirEntry`` objects in the directory + given by path. + """ + + result = self.listdir_dict(path) + return result.values() + + def listdir_dict(self, path = None): + """ + Return a dict containing the ``DirEntry`` objects in the directory + given by path with name of the dir as key. + """ + + if path is None: + path = self.root + + root = self.find(path) + if root is None: + raise ValueError("unable to find dir: %s" % str(path)) + + if not root.isdir(): + raise ValueError("can only list storage types") + + children = self.children_cache.get(root.dir_id, None) + if children is not None: + return children + + child = root.child() + + result = {} + if not child: + self.children_cache[root.dir_id] = result + return result + + dir_per_sector = self.sector_size // 128 + max_dirs_entries = self.dir_sector_count * dir_per_sector + + stack = deque([child]) + count = 0 + + while stack: + current = stack.pop() + result[current.name] = current + count += 1 + + if count > max_dirs_entries: + raise CompoundFileBinaryError("corrupt folder structure") + + left = current.left() + if left: + stack.append(left) + right = current.right() + if right: + stack.append(right) + + self.children_cache[root.dir_id] = result + return result + + def find(self, path): + """ + find a ``DirEntry`` located at *path*. Returns ``None`` if path + does not exist. + """ + + if isinstance(path, DirEntry): + return path + + if path == "/": + return self.root + + split_path = path.lstrip('/').split("/") + + i = 0 + root = self.root + + while True: + + children = self.listdir_dict(root) + match = children.get(split_path[i], None) + + if match: + if i == len(split_path) - 1: + return match + root = match + i += 1 + else: + return None + + def walk(self, path = None, topdown=True): + """ + Similar to :func:`os.walk`, yeields a 3-tuple ``(root, storage_items, stream_items)`` + """ + + if path is None: + path = self.root + + root = self.find(path) + + if not root.isdir(): + raise ValueError("can only walk storage types") + + if not root.child_id: + return + + if topdown: + storage_items = [] + stream_items = [] + + for item in self.listdir(root): + if item.isdir(): + storage_items.append(item) + else: + stream_items.append(item) + + yield root, storage_items, stream_items + + for item in storage_items: + for root, storage_items, stream_items in self.walk(item): + yield root, storage_items, stream_items + else: + + def topdown_visit_node(root): + storage_items = [] + stream_items = [] + for item in self.listdir(root): + if item.isdir(): + for sub_root, sub_storage, sub_stream in topdown_visit_node(item): + yield sub_root, sub_storage, sub_stream + + storage_items.append(item) + else: + stream_items.append(item) + + yield root, storage_items, stream_items + + for root_item, storage, stream in topdown_visit_node(root): + yield root_item, storage, stream + + def validate_directory_structure(self): + for root, storage, stream in self.walk(): + validate_rbtree(root.child()) + + + def exists(self, path): + """ + Return ``True`` if path refers to a existing path. + """ + if self.find(path) is None: + return False + return True + + def makedir(self, path, class_id=None): + """ + Create a storage DirEntry name path + """ + return self.create_dir_entry(path, dir_type='storage', class_id=class_id) + + def makedirs(self, path): + """ + Recursive storage DirEntry creation function. + """ + root = "" + + assert path.startswith('/') + p = path.strip('/') + for item in p.split('/'): + root += "/" + item + if not self.exists(root): + self.makedir(root) + + return self.find(path) + + def move(self, src, dst): + """ + Moves ``DirEntry`` from src to dst + """ + src_entry = self.find(src) + if src_entry is None: + raise ValueError("src path does not exist: %s" % src) + + if dst.endswith('/'): + dst += src_entry.name + + if self.exists(dst): + raise ValueError("dst path already exist: %s" % dst) + + if dst == '/' or src == '/': + raise ValueError("cannot overwrite root dir") + + split_path = dst.strip('/').split('/') + dst_basename = split_path[-1] + dst_dirname = '/' + '/'.join(split_path[:-1]) + + # print(dst) + # print(dst_basename, dst_dirname) + + dst_entry = self.find(dst_dirname) + if dst_entry is None: + raise ValueError("src path does not exist: %s" % dst_dirname) + + if not dst_entry.isdir(): + raise ValueError("dst dirname cannot be stream: %s" % dst_dirname) + + # src_entry.parent.remove_child(src_entry) + + src_entry.pop() + + src_entry.parent = None + src_entry.name = dst_basename + dst_entry.add_child(src_entry) + + self.children_cache[dst_entry.dir_id][src_entry.name] = src_entry + + return src_entry + + def open(self, path, mode='r'): + """Open stream, returning ``Stream`` object""" + + entry = self.find(path) + if entry is None: + if mode == 'r': + raise ValueError("stream does not exists: %s" % path) + entry = self.create_dir_entry(path, 'stream', None) + + else: + if not entry.isfile(): + raise ValueError("can only open stream type DirEntry's") + + if mode == 'w': + logging.debug("stream: %s exists, overwriting" % path) + self.free_fat_chain(entry.sector_id, entry.byte_size < self.min_stream_max_size) + entry.sector_id = None + entry.byte_size = 0 + entry.class_id = None + elif mode == 'rw': + pass + + s = Stream(self, entry, mode) + return s diff --git a/aaf2/components.py b/aaf2/components.py new file mode 100644 index 0000000..43c1c5f --- /dev/null +++ b/aaf2/components.py @@ -0,0 +1,328 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +from . import core +from . utils import register_class +from . mobid import MobID +from . dictionary import DataDef +from .auid import AUID + +class Component(core.AAFObject): + class_id = AUID("0d010101-0101-0200-060e-2b3402060101") + __slots__ = () + def __init__(self, media_kind=None, length=None): + self.media_kind = media_kind or 'picture' + self.length = length or 0 + + @property + def length(self): + return self['Length'].value + + @length.setter + def length(self, value): + self['Length'].value = value + + @property + def datadef(self): + return self['DataDefinition'].value + + @datadef.setter + def datadef(self, value): + self['DataDefinition'].value = value + + @property + def media_kind(self): + datadef = self.datadef + if datadef: + return datadef.short_name + + @media_kind.setter + def media_kind(self, value): + self.datadef = self.root.dictionary.lookup_datadef(value) + +class Segment(Component): + class_id = AUID("0d010101-0101-0300-060e-2b3402060101") + __slots__ = () + +@register_class +class Transition(Component): + class_id = AUID("0d010101-0101-1700-060e-2b3402060101") + __slots__ = () + + @property + def cutpoint(self): + return self['CutPoint'].value + + @cutpoint.setter + def cutpoint(self, value): + self['CutPoint'].value = value + +@register_class +class Sequence(Segment): + class_id = AUID("0d010101-0101-0f00-060e-2b3402060101") + __slots__ = () + + @property + def components(self): + return self['Components'] + + def component_at_time(self, edit_unit): + return self.components[self.index_at_time(edit_unit)] + + def index_at_time(self, edit_unit): + + last_component = None + last_index = None + + if edit_unit <= 0: + return 0 + + # this needs to go past target index to handle Transitions + for index, position, component in self.positions(): + + if isinstance(component, Transition): + if edit_unit >= position and edit_unit < position + component.length: + return index + + # gone past return previous + if last_component and position >= edit_unit: + return last_index + + last_component = component + last_index = index + + return last_index + + def positions(self): + length = 0 + for index, component in enumerate(self.components): + + if isinstance(component, Transition): + length -= component.length + yield (index, length, component) + else: + yield (index, length, component) + length += component.length + +@register_class +class NestedScope(Segment): + class_id = AUID("0d010101-0101-0b00-060e-2b3402060101") + __slots__ = () + + @property + def slots(self): + return self['Slots'] + +class SourceReference(Segment): + class_id = AUID("0d010101-0101-1000-060e-2b3402060101") + __slots__ = () + + @property + def mob_id(self): + return self['SourceID'].value + + @mob_id.setter + def mob_id(self, value): + self['SourceID'].value = value + + @property + def slot_id(self): + return self['SourceMobSlotID'].value + + @slot_id.setter + def slot_id(self, value): + self['SourceMobSlotID'].value = value + + @property + def mob(self): + mod_id = self.mob_id + if mod_id is None or mod_id.int == 0: + return None + return self.root.content.mobs.get(mod_id, None) + + @mob.setter + def mob(self, value): + self.mob_id = value.mob_id + + @property + def slot(self): + slot_id = self.slot_id + mob = self.mob + if mob is None or slot_id is None: + return + return mob.slot_at(slot_id) + + @slot.setter + def slot(self, value): + self.slot_id = value.slot_id + +@register_class +class SourceClip(SourceReference): + class_id = AUID("0d010101-0101-1100-060e-2b3402060101") + __slots__ = () + + def __init__(self, start=None, length=None, mob_id=None, slot_id=None, media_kind=None): + super(SourceClip, self).__init__(media_kind=media_kind, length=length) + self.start = start or 0 + self.mob_id = mob_id or MobID() + self.slot_id = slot_id or 0 + + @property + def start(self): + return self['StartTime'].value + + @start.setter + def start(self, value): + self['StartTime'].value = value + + def walk(self): + if not self.slot: + return + + segment = self.slot.segment + + if isinstance(segment, SourceClip): + yield segment + for item in segment.walk(): + yield item + + elif isinstance(segment, Sequence): + try: + clip = segment.component_at_time(self.start) + except AttributeError as e: + print(e) + else: + if isinstance(clip, SourceClip): + yield clip + for item in clip.walk(): + yield item + else: + raise NotImplementedError("Sequence returned {} not " + "implemented".format( + type(segment))) + + elif isinstance(segment, EssenceGroup): + yield segment + + elif isinstance(segment, Filler): + yield segment + + elif isinstance(segment, (OperationGroup, Pulldown)): + yield segment + + else: + raise NotImplementedError("Walking {} not implemented".format( + type(segment))) + +@register_class +class Filler(Segment): + class_id = AUID("0d010101-0101-0900-060e-2b3402060101") + __slots__ = () + +@register_class +class EssenceGroup(Segment): + class_id = AUID("0d010101-0101-0500-060e-2b3402060101") + __slots__ = () + +@register_class +class EdgeCode(Segment): + class_id = AUID("0d010101-0101-0400-060e-2b3402060101") + __slots__ = () + +@register_class +class Pulldown(Segment): + class_id = AUID("0d010101-0101-0c00-060e-2b3402060101") + __slots__ = () + +@register_class +class ScopeReference(Segment): + class_id = AUID("0d010101-0101-0d00-060e-2b3402060101") + __slots__ = () + +@register_class +class Selector(Segment): + class_id = AUID("0d010101-0101-0e00-060e-2b3402060101") + __slots__ = () + +@register_class +class Timecode(Segment): + class_id = AUID("0d010101-0101-1400-060e-2b3402060101") + __slots__ = () + + def __init__(self, fps=25, drop=False): + length = fps * 60 * 60 * 12 # 12 hours + super(Timecode, self).__init__(length=length, media_kind='Timecode') + self.start = 0 + self.fps = fps + self.drop = drop + + @property + def start(self): + return self['Start'].value + + @start.setter + def start(self, value): + self['Start'].value = value + + @property + def fps(self): + return self['FPS'].value + + @fps.setter + def fps(self, value): + self['FPS'].value = value + + @property + def drop(self): + return self['Drop'].value + + @drop.setter + def drop(self, value): + self['Drop'].value = value + +@register_class +class OperationGroup(Segment): + class_id = AUID("0d010101-0101-0a00-060e-2b3402060101") + __slots__ = () + + def __init__(self, operationdef, length=None): + super(OperationGroup, self).__init__(length=length) + self.operation = self.root.dictionary.lookup_operationdef(operationdef) + self.media_kind = self.operation.media_kind + + @property + def operation(self): + return self['Operation'].value + + @operation.setter + def operation(self, value): + self['Operation'].value = value + + @property + def parameters(self): + return self['Parameters'] + + @property + def segments(self): + return self['InputSegments'] + +class Event(Segment): + class_id = AUID("0d010101-0101-0600-060e-2b3402060101") + __slots__ = () + + def __init__(self): + super(Event, self).__init__(media_kind='DescriptiveMetadata') + +@register_class +class CommentMarker(Event): + class_id = AUID("0d010101-0101-0800-060e-2b3402060101") + __slots__ = () + +@register_class +class DescriptiveMarker(CommentMarker): + class_id = AUID("0d010101-0101-4100-060e-2b3402060101") + __slots__ = () diff --git a/aaf2/content.py b/aaf2/content.py new file mode 100644 index 0000000..911152f --- /dev/null +++ b/aaf2/content.py @@ -0,0 +1,95 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +from . import core +from .utils import register_class +from . import mobs +from . import mxf +from . import ama +from . import audio +from .auid import AUID + +@register_class +class Header(core.AAFObject): + class_id = AUID("0d010101-0101-2f00-060e-2b3402060101") + __slots__ = () + +@register_class +class ContentStorage(core.AAFObject): + """ + This object has all ``Mob`` and ``EssenceData`` objects in the file + """ + + class_id = AUID("0d010101-0101-1800-060e-2b3402060101") + __slots__ = () + + @property + def mobs(self): + """ + Access to all the ``Mobs`` objects in the aaf file. + """ + return self['Mobs'] + + def toplevel(self): + """ + Convenience generator method that yields only TopLevel :class:`aaf2.mobs.CompositionMob` objects. + """ + for mob in self.compositionmobs(): + if mob.usage == 'Usage_TopLevel': + yield mob + + def mastermobs(self): + """ + Convenience generator method that yields only :class:`aaf2.mobs.MasterMob` objects. + """ + for mob in self.mobs: + if isinstance(mob, mobs.MasterMob): + yield mob + + def compositionmobs(self): + """ + Convenience generator method that yields only :class:`aaf2.mobs.CompositionMob` objects. + """ + + for mob in self.mobs: + if isinstance(mob, mobs.CompositionMob): + yield mob + + def sourcemobs(self): + """ + Convenience generator method that yields only :class:`aaf2.mobs.SourceMob` objects. + """ + + for mob in self.mobs: + if isinstance(mob, mobs.SourceMob): + yield mob + + def link_external_mxf(self, path): + m = mxf.MXFFile(path) + if m.operation_pattern != "OPAtom": + raise Exception("can only link OPAtom mxf files") + return m.link(self.root) + + def link_external_wav(self, metadata): + """ + Create a link source MOB to a wav file, along with a corresponding master MOB and tape MOB. + + Returns a 3-tuple: a master mob, the source MOB whose essence is a WAVEDescriptor link, + and a source MOB whose essence is a TapeDescriptor. + """ + path = metadata['format']['filename'] + return self.create_ama_link(path, metadata) + + def create_ama_link(self, path, metadata): + return ama.create_media_link(self.root, path, metadata) + + @property + def essencedata(self): + """ + Access to :class:`aaf2.essence.EssenceData` objects in the aaf file. + """ + return self["EssenceData"] diff --git a/aaf2/core.py b/aaf2/core.py new file mode 100644 index 0000000..7b227cf --- /dev/null +++ b/aaf2/core.py @@ -0,0 +1,386 @@ + +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +import sys +from io import BytesIO +import weakref +import struct +import array + +from .utils import ( + read_u8, + read_u16le, + read_u32le, + write_u8, + write_u16le, + write_u32le, + safe_print, + ) +from .exceptions import AAFPropertyError, AAFAttachError +from . import properties +from .properties import property_formats +from .auid import AUID + +P_HEADER_STRUCT = struct.Struct(str('' % (s, id(self)) + + def dump(self, space=""): + + indent = " " + + for p in self.properties(): + + if isinstance(p, properties.StrongRefProperty): + safe_print(space, p.name, p.typedef) + + p.value.dump(space + indent) + + if isinstance(p, properties.StrongRefVectorProperty): + safe_print(space, p.name, p.typedef) + for obj in p.value: + safe_print(space + indent, obj) + obj.dump(space + indent*2) + continue + + if isinstance(p, properties.StrongRefSetProperty): + safe_print(space, p.name, p.typedef) + for key, obj in p.items(): + safe_print(space + indent, obj) + obj.dump(space + indent*2) + + continue + # print(space, p.name, p.typedef) + + safe_print(space, p.name, p.typedef, p.value) diff --git a/aaf2/dictionary.py b/aaf2/dictionary.py new file mode 100644 index 0000000..550a48a --- /dev/null +++ b/aaf2/dictionary.py @@ -0,0 +1,248 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +import uuid + +from .auid import AUID +from .model import datadefs +from . import core +from .utils import register_class + +def short_name(name): + for s in ('DataDef_', 'ContainerDef_'): + name = name.replace(s, "") + return name + +def lookup_def(dictionary, name, instance_type, key): + if isinstance(name, AUID): + return dictionary[key].get(name, None) + + if isinstance(name, uuid.UUID): + return dictionary[key].get(AUID(name), None) + + if isinstance(name, instance_type): + return name + + name = short_name(name).lower() + + for key, value in dictionary[key].items(): + value_name = value.short_name or '' + if name == value_name.lower(): + return value + + raise Exception("No Definition: %s" % str(name)) + +@register_class +class DefinitionObject(core.AAFObject): + class_id = AUID("0d010101-0101-1a00-060e-2b3402060101") + __slots__ = () + def __init__(self, auid=None, name=None, description=None): + super(DefinitionObject, self).__init__() + self.name = name + self.description = description + if auid: + self.auid = AUID(str(auid)) + + @property + def name(self): + return self['Name'].value + + @name.setter + def name(self, value): + self['Name'].value = value + + @property + def short_name(self): + name = self.name + if name: + return short_name(name) + + @property + def description(self): + return self['Description'].value + + @description.setter + def description(self, value): + self['Description'].value = value + + @property + def auid(self): + return self['Identification'].value + + @auid.setter + def auid(self, value): + self['Identification'].value = value + + @property + def uuid(self): + auid = self.auid + if auid is not None: + return auid.uuid + + @uuid.setter + def uuid(self, value): + self.auid = AUID(value) + + def __repr__(self): + s = "%s.%s" % (self.__class__.__module__, + self.__class__.__name__) + name = self.name + if name: + s += " %s" % name + + return '<%s %s at 0x%x>' % (s, str(self.auid), id(self)) + + @property + def unique_key(self): + return self.auid + +@register_class +class DataDef(DefinitionObject): + class_id = AUID("0d010101-0101-1b00-060e-2b3402060101") + __slots__ = () + +@register_class +class OperationDef(DefinitionObject): + class_id = AUID("0d010101-0101-1c00-060e-2b3402060101") + __slots__ = () + + @property + def datadef(self): + return self['DataDefinition'].value + + @datadef.setter + def datadef(self, value): + self['DataDefinition'].value = value + + @property + def media_kind(self): + datadef = self.datadef + if datadef: + return datadef.short_name + + @media_kind.setter + def media_kind(self, value): + self.datadef = self.root.dictionary.lookup_datadef(value) + + @property + def parameters(self): + return self['ParametersDefined'] + +@register_class +class ParameterDef(DefinitionObject): + class_id = AUID("0d010101-0101-1d00-060e-2b3402060101") + __slots__ = () + + def __init__(self, auid=None, name=None, description=None, typedef=None): + super(ParameterDef, self).__init__(auid, name, description) + if typedef: + self.typedef = self.root.dictionary.lookup_typedef(typedef) + + @property + def typedef(self): + return self['Type'].value + + @typedef.setter + def typedef(self, value): + self['Type'].value = value + +@register_class +class PluginDef(DefinitionObject): + class_id = AUID("0d010101-0101-1e00-060e-2b3402060101") + __slots__ = () + +@register_class +class CodecDef(DefinitionObject): + class_id = AUID("0d010101-0101-1f00-060e-2b3402060101") + __slots__ = () + def __init__(self, dictionary, auid=None, name=None, description=None, classdef=None, datadef_names=None): + super(CodecDef, self).__init__(auid, name, description) + if classdef: + self['FileDescriptorClass'].value = self.root.metadict.lookup_classdef(classdef) + + for d in datadef_names or []: + self['DataDefinitions'].append(dictionary.lookup_datadef(d)) + +@register_class +class ContainerDef(DefinitionObject): + class_id = AUID("0d010101-0101-2000-060e-2b3402060101") + __slots__ = () + +@register_class +class InterpolationDef(DefinitionObject): + class_id = AUID("0d010101-0101-2100-060e-2b3402060101") + __slots__ = () + +@register_class +class TaggedValueDef(DefinitionObject): + class_id = AUID("0d010101-0101-4c00-060e-2b3402060101") + __slots__ = () + +@register_class +class Dictionary(core.AAFObject): + class_id = AUID("0d010101-0101-2200-060e-2b3402060101") + __slots__ = () + def __init__(self): + super(Dictionary, self).__init__() + + for key, args in datadefs.DataDefs.items(): + d = self.root.create.DataDef(key, *args) + self['DataDefinitions'].append(d) + + for key, args in datadefs.ContainerDefs.items(): + d = self.root.create.ContainerDef(key, *args) + self['ContainerDefinitions'].append(d) + + def setup_defaults(self): + + for key, args in datadefs.CodecDefs.items(): + if len(args) > 2: + d = self.root.create.CodecDef(self, key, *args) + self['CodecDefinitions'].append(d) + + def register_def(self, defobject): + + if isinstance(defobject, DataDef): + self['DataDefinitions'].append(defobject) + elif isinstance(defobject, ContainerDef): + self['ContainerDefinitions'].append(defobject) + elif isinstance(defobject, CodecDef): + self['CodecDefinitions'].append(defobject) + elif isinstance(defobject, ParameterDef): + self['ParameterDefinitions'].append(defobject) + elif isinstance(defobject, OperationDef): + self['OperationDefinitions'].append(defobject) + elif isinstance(defobject, InterpolationDef): + self['InterpolationDefinitions'].append(defobject) + elif isinstance(defobject, TaggedValueDef): + self['TaggedValueDefinitions'].append(defobject) + else: + raise ValueError("unknown definitions type: %s" % str(type(defobject))) + + def lookup_typedef(self, name): + return self.root.metadict.lookup_typedef(name) + + def lookup_datadef(self, name): + return lookup_def(self, name, DataDef, 'DataDefinitions') + + def lookup_containerdef(self, name): + return lookup_def(self, name, ContainerDef, 'ContainerDefinitions') + + def lookup_codecdef(self, name): + return lookup_def(self, name, CodecDef, 'CodecDefinitions') + + def lookup_parameterdef(self, name): + return lookup_def(self, name, ParameterDef, 'ParameterDefinitions') + + def lookup_operationdef(self, name): + return lookup_def(self, name, OperationDef, 'OperationDefinitions') + + def lookup_interperlationdef(self, name): + return lookup_def(self, name, InterpolationDef, 'InterpolationDefinitions') + + def lookup_taggedvaluedef(self, name): + return lookup_def(self, name, TaggedValueDef, 'TaggedValueDefinitions') diff --git a/aaf2/essence.py b/aaf2/essence.py new file mode 100644 index 0000000..3cfb06c --- /dev/null +++ b/aaf2/essence.py @@ -0,0 +1,129 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) +import traceback + +from . import core +from . mobid import MobID +from .utils import register_class +from .auid import AUID + +@register_class +class EssenceData(core.AAFObject): + class_id = AUID("0d010101-0101-2300-060e-2b3402060101") + __slots__ = () + + @property + def unique_key(self): + return self.mob_id + + @property + def mob_id(self): + return self['MobID'].value + + @mob_id.setter + def mob_id(self, value): + self['MobID'].value = value + + @property + def mob(self): + mob_id = self.mob_id + if mob_id: + return self.root.content.mobs.get(mob_id, None) + return None + + @mob.setter + def mob(self, value): + self.mob_id = value.mob_id + + def open(self, mode='r'): + return self['Data'].open(mode) + +@register_class +class EssenceDescriptor(core.AAFObject): + class_id = AUID("0d010101-0101-2400-060e-2b3402060101") + __slots__ = () + + @property + def locator(self): + return self['Locator'].value + +@register_class +class FileDescriptor(EssenceDescriptor): + class_id = AUID("0d010101-0101-2500-060e-2b3402060101") + __slots__ = () + + @property + def length(self): + return self['Length'].value + @length.setter + def length(self, value): + self['Length'].value = value + +@register_class +class DigitalImageDescriptor(FileDescriptor): + class_id = AUID("0d010101-0101-2700-060e-2b3402060101") + __slots__ = () + +@register_class +class CDCIDescriptor(DigitalImageDescriptor): + class_id = AUID("0d010101-0101-2800-060e-2b3402060101") + __slots__ = () + +@register_class +class RGBADescriptor(DigitalImageDescriptor): + class_id = AUID("0d010101-0101-2900-060e-2b3402060101") + __slots__ = () + + @property + def pixel_layout(self): + return self['PixelLayout'].value + +@register_class +class TapeDescriptor(EssenceDescriptor): + class_id = AUID("0d010101-0101-2e00-060e-2b3402060101") + __slots__ = () + +@register_class +class SoundDescriptor(FileDescriptor): + class_id = AUID("0d010101-0101-4200-060e-2b3402060101") + __slots__ = () + +@register_class +class WAVEDescriptor(FileDescriptor): + # from ... https://github.com/ebu/ebu-libmxf/blob/master/tools/MXFDump/AAFMetaDictionary.h + class_id = AUID("0d010101-0101-2c00-060e-2b3402060101") + __slots__ = () + +@register_class +class AIFCDescriptor(FileDescriptor): + class_id = AUID("0d010101-0101-2600-060e-2b3402060101") + __slots__ = () + +@register_class +class DataEssenceDescriptor(FileDescriptor): + class_id = AUID("0d010101-0101-4300-060e-2b3402060101") + __slots__ = () + +@register_class +class MultipleDescriptor(FileDescriptor): + class_id = AUID("0d010101-0101-4400-060e-2b3402060101") + __slots__ = () + +@register_class +class PCMDescriptor(SoundDescriptor): + class_id = AUID("0d010101-0101-4800-060e-2b3402060101") + __slots__ = () + +@register_class +class PhysicalDescriptor(EssenceDescriptor): + class_id = AUID("0d010101-0101-4900-060e-2b3402060101") + __slots__ = () + +@register_class +class ImportDescriptor(PhysicalDescriptor): + class_id = AUID("0d010101-0101-4a00-060e-2b3402060101") + __slots__ = () diff --git a/aaf2/exceptions.py b/aaf2/exceptions.py new file mode 100644 index 0000000..4930426 --- /dev/null +++ b/aaf2/exceptions.py @@ -0,0 +1,18 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +class AAFError(Exception): + pass + +class AAFAttachError(AAFError): + pass + +class AAFPropertyError(AAFError): + pass + +class CompoundFileBinaryError(AAFError): + pass diff --git a/aaf2/file.py b/aaf2/file.py new file mode 100644 index 0000000..9554042 --- /dev/null +++ b/aaf2/file.py @@ -0,0 +1,358 @@ + +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) +from io import BytesIO +import io +import traceback +import os +from uuid import uuid4 +import sys +import datetime +import weakref +from .utils import ( + read_u8, + read_u16le, + read_u32le, + write_u8, + write_u16le, + write_u32le, + ) + +from .auid import AUID +from .cfb import (CompoundFileBinary, DirEntry) +from .core import AAFObject +from .metadict import MetaDictionary +from .cache import LRUCacheDict + +class AAFFactory(object): + + def __init__(self, root): + self.root = root + self.class_name = None + + def __getattr__(self, name): + self.class_name = name + return self.create_instance + + def from_name(self, name, *args, **kwargs): + + classdef = self.root.metadict.lookup_classdef(name) + if classdef is None: + raise ValueError("no class found with name: %s" % name) + + if not classdef.concrete: + raise ValueError("cannot initialize abstract class: %s" % name) + + classobj = self.root.metadict.lookup_class(name) + + # obj = classobj(None, *args, **kwargs) + obj = classobj.__new__(classobj) + obj.root = self.root + obj.__init__(*args, **kwargs) + + # if a helper class is not found set class_id + if type(obj) is AAFObject: + obj.class_id = classdef.auid + + return obj + + def create_instance(self, *args, **kwargs): + return self.from_name(self.class_name, *args, **kwargs) + +class AAFObjectManager(object): + + def __init__(self, root): + self.root = root + self.path_cache = weakref.WeakValueDictionary() + self.lru_cache = LRUCacheDict() + # to hold onto modified objects + self.modified = {} + + def create_temp_dir(self): + return self.root.cfb.makedirs("/tmp/" + str(uuid4()).replace('-', '/')) + + def remove_temp(self): + if self.root.cfb.exists("/tmp"): + self.root.cfb.rmtree("/tmp") + + def add_modified(self, obj): + if self.root.mode == 'rb': + raise ValueError("cannot modify read only file") + + path = obj.dir.path() + self.modified[path] = obj + self[path] = obj + + def pop(self, path, default=None): + cached_obj = self.path_cache.pop(path, default) + modified_obj = self.modified.pop(path, default) + if path in self.lru_cache: + del self.lru_cache[path] + + return modified_obj or cached_obj + + def __setitem__(self, key, value): + self.lru_cache[key] = value + self.path_cache[key] = value + + def read_object(self, path): + if isinstance(path, DirEntry): + dir_entry = path + path = dir_entry.path() + obj = self.path_cache.get(path, None) + if obj is not None: + return obj + else: + obj = self.path_cache.get(path, None) + if obj is not None: + return obj + + dir_entry = self.root.cfb.find(path) + + if dir_entry is None: + raise ValueError("cannot find path: %s" % path) + + obj_class = self.root.metadict.lookup_class(dir_entry.class_id) + + # NOTE: objects read from file do not run __init__ + obj = obj_class.__new__(obj_class) + obj.root = self.root + obj.dir = dir_entry + if obj_class is AAFObject: + obj.class_id = dir_entry.class_id + obj.read_properties() + + self[path] = obj + # obj.dump() + return obj + + def write_objects(self): + written = [] + for path, obj in self.modified.items(): + try: + obj.write_properties() + written.append(path) + except: + print("failed to write: %s %s" % (str(path), str(obj))) + raise + + # no longer need to be in modified + for path in written: + self.modified.pop(path) + +class AAFFile(object): + """ + AAF File Object. This is the entry point object for most of the API. + This object is designed to be like python's native open function. + It is recommended to create this object with the `aaf.open` alias. + It is also highly recommended to use the with statement. + + For example. Opening existing AAF file readonly:: + + with aaf.open('/path/to/aaf_file.aaf', 'r') as f: + + Opening new AAF file overwriting existing one:: + + with aaf.open('/path/to/aaf_file.aaf', 'w') as f: + + Opening existing AAF in read and write:: + + with aaf.open('/path/to/aaf_file.aaf', 'rw') as f: + + Opening in memory BytesIO file:: + + with aaf.open() as f: + """ + + def __init__(self, path=None, mode='r', sector_size=4096, extensions=True, buffering=io.DEFAULT_BUFFER_SIZE): + + if mode in ('r', 'rb'): + mode = 'rb' + elif mode in ('r+', 'rb+', 'rw'): + mode = 'rb+' + elif mode in ('w', 'w+', 'wb+'): + mode = 'wb+' + else: + raise ValueError("invalid mode: %s" % mode) + self.mode = mode + if path is None: + self.mode = 'wb+' + self.f = BytesIO() + else: + self.f = io.open(path, mode, buffering=buffering) + + self.cfb = CompoundFileBinary(self.f, self.mode, sector_size=sector_size) + self.weakref_table = [] + self.manager = AAFObjectManager(self) + self.create = AAFFactory(self) + self.is_open = True + + if self.mode in ("rb", "rb+"): + self.read_reference_properties() + self.metadict = MetaDictionary(self) + self.metadict.dir = self.cfb.find('/MetaDictionary-1') + self.manager['/MetaDictionary-1'] = self.metadict + self.root = self.manager.read_object("/") + self.metadict.read_properties() + + elif self.mode in ("wb+",): + self.setup_empty() + + if extensions and self.writeable: + self.metadict.register_extensions() + + @property + def header(self): + """ + :class:`aaf2.content.Header` object for AAF file. + """ + header_pid = 0x02 + return self.root.property_entries[header_pid].value + + @property + def content(self): + """ + :class:`aaf2.content.ContentStorage` object for AAF File. This has the Mob and EssenceData objects. + """ + return self.header['Content'].value + + @property + def dictionary(self): + """ + :class:`aaf2.dictionary.Dictionary` for AAF file. The dictionary property has DefinitionObject objects. + """ + return self.header['Dictionary'].value + + def setup_empty(self): + now = datetime.datetime.now() + self.metadict = MetaDictionary(self) + self.root = self.create.Root() + self.root.attach(self.cfb.find("/")) + self.root['MetaDictionary'].value = self.metadict + self.root['Header'].value = self.create.Header() + + self.header['Dictionary'].value = self.create.Dictionary() + self.dictionary.setup_defaults() + + self.header['Content'].value = self.create.ContentStorage() + self.header['OperationalPattern'].value = AUID("0d011201-0100-0000-060e-2b3404010105") + self.header['ObjectModelVersion'].value = 1 + self.header['Version'].value = {u'major': 1, u'minor': 1} + + i = self.create.Identification() + i['ProductName'].value = "PyAAF" + i['CompanyName'].value = "CompanyName" + i['ProductVersionString'].value = '2.0.0' + i['ProductID'].value = AUID("97e04c67-dbe6-4d11-bcd7-3a3a4253a2ef") + i['Date'].value = now + i['Platform'].value = sys.platform + i['GenerationAUID'].value = uuid4() + + self.header['IdentificationList'].value = [i] + self.header['LastModified'].value = now + self.header['ByteOrder'].value = 0x4949 + + self.content['Mobs'].value = [] + + @property + def writeable(self): + return self.mode in ("wb+", "rb+") + + def resovle_weakref(self, index, ref_pid, ref): + parent, p = self.weakref_prop(index) + return p[ref] + + def weakref_prop(self, index): + path = self.weakref_table[index] + root = self.root + + for pid in path[:-1]: + p = root.property_entries[pid] + root = p.value + + p = root.property_entries[path[-1]] + return root, p + + def weakref_index(self, pid_path): + + if pid_path in self.weakref_table: + index = self.weakref_table.index(pid_path) + else: + index = len(self.weakref_table) + self.weakref_table.append(pid_path) + + return index + + def read_reference_properties(self): + s = self.cfb.open("/referenced properties") + f = io.BytesIO(s.read()) + + byte_order = read_u8(f) + if byte_order != 0x4c: + raise NotImplementedError("be byteorder") + + path_count = read_u16le(f) + pid_count = read_u32le(f) + + self.weakref_table = [] + path = [] + for i in range(pid_count): + pid = read_u16le(f) + if pid != 0: + path.append(pid) + else: + self.weakref_table.append(path) + path = [] + assert len(self.weakref_table) == path_count + + def write_reference_properties(self): + f = self.cfb.open("/referenced properties", 'w') + byte_order = 0x4c + path_count = len(self.weakref_table) + pid_count = 0 + for path in self.weakref_table: + pid_count += len(path) + pid_count += 1 # null byte + + write_u8(f, byte_order) + write_u16le(f, path_count) + write_u32le(f, pid_count) + for path in self.weakref_table: + for pid in path: + write_u16le(f, pid) + write_u16le(f, 0) # null terminated + + def __exit__(self, exc_type, exc_value, traceback): + if (exc_type is None and exc_value is None and traceback is None): + self.close() + + def __enter__(self): + return self + + def dump(self): + self.root.dump() + + def save(self): + """ + Writes current changes to disk and flushes modified objects in the + AAFObjectManager + """ + if self.mode in ("wb+", 'rb+'): + if not self.is_open: + raise IOError("file closed") + self.write_reference_properties() + self.manager.write_objects() + + def close(self): + """ + Close the file. A closed file cannot be read or written any more. + """ + self.save() + self.manager.remove_temp() + self.cfb.close() + self.is_open = False + self.f.close() diff --git a/aaf2/metadict.py b/aaf2/metadict.py new file mode 100644 index 0000000..5b5043c --- /dev/null +++ b/aaf2/metadict.py @@ -0,0 +1,516 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) +import traceback +from io import BytesIO +from .exceptions import AAFPropertyError +from . import types +from .model import classdefs +from .model import typedefs as base_typedefs +from . import properties +from .auid import AUID + +from . import core +from .utils import (register_class, read_u16le, decode_utf16le, + encode_utf16le, encode_u16le, str2auid, + AAFClaseID_dict, AAFClassName_dict) + +PID_NAME = 0x0006 +PID_AUID = 0x0005 +PID_TYPE = 0x000B +PID_OPTIONAL = 0x000C +PID_PID = 0x000D +PID_UNIQUE = 0x000E + +PID_PARENT = 0x0008 +PID_PROPERTIES = 0x0009 +PID_CONCRETE = 0x000A + +sentinel = object() + +@register_class +class PropertyDef(core.AAFObject): + class_id = AUID("0d010101-0202-0000-060e-2b3402060101") + __slots__ = ('_typedef_id', '_auid', '_property_name') + + def __new__(cls, root=None, name=None, auid=None, pid=None, typedef=None, optional=None, unique=None): + self = super(PropertyDef, cls).__new__(cls) + self.root = root + self._typedef_id = None + self._auid = None + self._property_name = name + if root: + properties.add_string_property(self, PID_NAME, name) + properties.add_bool_property(self, PID_OPTIONAL, optional) + properties.add_bool_property(self, PID_UNIQUE, unique) + properties.add_u16le_property(self, PID_PID, pid) + properties.add_auid_property(self, PID_AUID, auid) + properties.add_auid_property(self, PID_TYPE, typedef) + + return self + + @property + def property_name(self): + if self._property_name: + return self._property_name + + p = self.property_entries.get(PID_NAME) + self._property_name = decode_utf16le(p.data) + return self._property_name + + @property_name.setter + def property_name(self, value): + self.property_entries[PID_NAME].data = encode_utf16le(value) + self._property_name = value + + @property + def unique_key(self): + return self.auid + + @property + def unique(self): + if PID_UNIQUE in self.property_entries: + return self.property_entries[PID_UNIQUE].data == b"\x01" + return False + + @property + def pid(self): + return read_u16le(BytesIO(self.property_entries[PID_PID].data)) + + @pid.setter + def pid(self, value): + self.property_entries[PID_PID].data = encode_u16le(value) + + @property + def auid(self): + if self._auid: + return self._auid + p = self.property_entries.get(PID_AUID) + self._auid = AUID(bytes_le=p.data) + return self._auid + + @property + def uuid(self): + return self.auid.uuid + + @property + def optional(self): + return self.property_entries[PID_OPTIONAL].data == b"\x01" + + @property + def typedef_id(self): + if self._typedef_id: + return self._typedef_id + + p = self.property_entries.get(PID_TYPE) + self._typedef_id = AUID(bytes_le=p.data) + return self._typedef_id + + @property + def typedef(self): + type_id = self.typedef_id + if type_id: + return self.root.metadict.lookup_typedef(type_id) + + @property + def store_format(self): + return self.typedef.store_format + + def __repr__(self): + return "<%s PropertyDef" % self.property_name + +@register_class +class ClassDef(core.AAFObject): + class_id = AUID("0d010101-0201-0000-060e-2b3402060101") + __slots__ = ('propertydef_by_pid') + + def __new__(cls, root=None, name=None, class_auid=None, parent_auid=None, concrete=None): + self = super(ClassDef, cls).__new__(cls) + self.root = root + self.propertydef_by_pid = {} + if root: + properties.add_string_property(self, PID_NAME, name) + properties.add_auid_property(self, PID_AUID, class_auid) + properties.add_bool_property(self, PID_CONCRETE, concrete) + + # reference self auid if no parent + properties.add_classdef_weakref_property(self, PID_PARENT, parent_auid or class_auid) + properties.add_strongref_set_property(self, PID_PROPERTIES, "Properties", PID_AUID) + + return self + + def __eq__(self, other): + self.auid == other.auid + + @property + def auid(self): + data = self.property_entries[PID_AUID].data + if data is not None: + return AUID(bytes_le=self.property_entries[PID_AUID].data) + + @property + def uuid(self): + return self.auid.uuid + + @property + def concrete(self): + return self.property_entries[PID_CONCRETE].data == b"\x01" + + @property + def class_name(self): + data = self.property_entries[PID_NAME].data + if data is not None: + return decode_utf16le(data) + + @property + def unique_key(self): + return self.auid + + @property + def unique_key_pid(self): + for p in self.all_propertydefs(): + if p.unique: + return p.pid + + # Parameter work around + # Uses the DefinitionObject Identification PID + if self.isinstance(self.root.metadict.lookup_classdef(AUID("0d010101-0101-3c00-060e-2b3402060101"))): + return 0x1B01 + + @property + def unique_key_size(self): + mob_classdef = self.root.metadict.lookup_classdef(AUID("0d010101-0101-3400-060e-2b3402060101")) + essencedata_classdef = self.root.metadict.lookup_classdef(AUID("0d010101-0101-2300-060e-2b3402060101")) + if self.isinstance(mob_classdef) or self.isinstance(essencedata_classdef): + return 32 + return 16 + + def isinstance(self, other): + + if self.auid == other.auid: + return True + + for classdef in other.relatives(): + if classdef.auid == self.auid: + return True + return False + + @property + def name(self): + return "ClassDefinition" + + @property + def classdef(self): + return self.root.metadict.classdefs_by_name['ClassDefinition'] + + @property + def parent_id(self): + if PID_PARENT in self.property_entries: + return self.property_entries[PID_PARENT].ref + + @property + def parent(self): + parent_id = self.parent_id + + if parent_id == self.auid: + return None + + return self.root.metadict.classdefs_by_auid.get(parent_id, None) + + @property + def propertydefs(self): + if PID_PROPERTIES in self.property_entries: + return self.property_entries[PID_PROPERTIES].values() + return [] + + def register_propertydef(self, name, property_auid, pid, typedef, optional, unique=False): + + typedef = str2auid(typedef) + property_auid = str2auid(property_auid) + if isinstance(typedef, AUID): + typedef_auid = typedef + else: + typedef = self.root.metadict.lookup_typedef(typedef) + typedef_auid = typedef.auid + + # check its not already defined + if property_auid in self.property_entries[PID_PROPERTIES].references: + return self.property_entries[PID_PROPERTIES].get(property_auid) + + # None signifies Dynamically allocated Local Tags + # I think this is similar to MXF where pids > 0x8000 < 0xFFFF + # are extensions pids + if pid is None: + pid = self.root.metadict.next_free_pid() + + p = PropertyDef(self.root, name, property_auid, pid, typedef_auid, optional, unique) + # # this is done low level to avoid recursion errors + properties.add2set(self, PID_PROPERTIES, p.auid, p) + self.propertydef_by_pid[pid] = p + return p + + def relatives(self): + root = self + while root: + yield root + root = root.parent + + def all_propertydefs(self): + for classdef in self.relatives(): + + if not classdef: + break + # raise Exception(self.class_name) + + for p in classdef.propertydefs: + yield p + + def get_propertydef_from_pid(self, pid, default=None): + + for classdef in self.relatives(): + if not classdef: + break + p = classdef.propertydef_by_pid.get(pid, sentinel) + + if p is sentinel: + continue + return p + + return default + + def __repr__(self): + return "<%s %s>" % (self.class_name, "ClassDef") + +root_classes = { +'Root' : ('b3b398a5-1c90-11d4-8053-080036210804', None, True, { + "Header" : ('0d010301-0102-0100-060e-2b3401010102', 0x0002, '05022800-0000-0000-060E-2B3401040101', False, False), + "MetaDictionary" : ('0d010301-0101-0100-060e-2b3401010102', 0x0001, '05022700-0000-0000-060E-2B3401040101', False, False), +}) +} + +root_types = { +"HeaderStrongRefence" : ('05022800-0000-0000-060E-2B3401040101', "0d010101-0101-2f00-060e-2b3402060101"), +"MetaDictionaryStrongReference" : ('05022700-0000-0000-060E-2B3401040101', "0d010101-0225-0000-060e-2b3402060101"), +} + +PID_CLASSDEFS = 0x0003 +PID_TYPEDEFS = 0x0004 + +@register_class +class MetaDictionary(core.AAFObject): + class_id = AUID("0d010101-0225-0000-060e-2b3402060101") + def __init__(self, root): + super(MetaDictionary, self).__init__(root) + + self.root = root + properties.add_strongref_set_property(self, PID_CLASSDEFS, "ClassDefinitions", PID_AUID) + properties.add_strongref_set_property(self, PID_TYPEDEFS, "TypeDefinitions", PID_AUID) + + self.classdefs_by_name = {} + self.classdefs_by_auid = {} + self.typedefs_by_name = {} + self.typedefs_by_auid = {} + + self.typedefs_classes = {} + + self.local_pids = set() + self.next_pid = 0xFFFF + + for name, args in root_classes.items(): + self.register_classdef(name, *args) + + for name, args in classdefs.classdefs.items(): + self.register_classdef(name, *args) + + for alias, name in classdefs.aliases.items(): + self.classdefs_by_name[alias]= self.classdefs_by_name[name] + + # setup typedefs + self.register_typedef_model({'strongrefs': root_types}) + self.register_typedef_model(base_typedefs.__dict__) + + def register_typedef_model(self, typedef_model): + + for cat, classobj in types.categories.items(): + for name, args in typedef_model.get(cat, {}).items(): + + if not isinstance(args, (tuple,list)): + args = [args] + + if cat == "extenums": + if name in self.typedefs_by_name: + t = self.typedefs_by_name[name] + for element_value, element_name in args[1].items(): + t.register_element(element_name, AUID(element_value)) + else: + t = types.TypeDefExtEnum(self.root, name, *args) + properties.add2set(self, PID_TYPEDEFS, t.auid, t) + elif cat == "enums": + if name in self.typedefs_by_name: + t = self.typedefs_by_name[name] + for element_value, element_name in args[2].items(): + t.register_element(element_name, element_value) + else: + t = types.TypeDefEnum(self.root, name, *args) + properties.add2set(self, PID_TYPEDEFS, t.auid, t) + else: + + t = classobj(self.root, name, *args) + properties.add2set(self, PID_TYPEDEFS, t.auid, t) + + self.typedefs_by_name[name] = t + self.typedefs_by_auid[t.auid] = t + self.typedefs_classes[t.auid] = t.__class__ + + def register_extensions(self): + from .model.ext import classdefs as ext_classdefs + from .model.ext import typedefs as ext_typedefs + + for name, args in ext_classdefs.classdefs.items(): + self.register_classdef(name, *args) + + for alias, name in ext_classdefs.aliases.items(): + self.classdefs_by_name[alias]= self.classdefs_by_name[name] + + self.register_typedef_model(ext_typedefs.__dict__) + + def register_classdef(self, name, class_auid, parent, concrete, propertydefs=None): + if not isinstance(class_auid, AUID): + class_auid = AUID(class_auid) + + parent = str2auid(parent) + + if parent is None: + parent_auid = None + elif isinstance(parent, AUID): + parent_auid = parent + else: + parent_classdef = self.lookup_classdef(parent) + parent_auid = parent_classdef.auid + + if name in self.classdefs_by_name: + c = self.classdefs_by_name[name] + elif class_auid in self.classdefs_by_auid: + c = self.classdefs_by_name[class_auid] + else: + c = ClassDef(self.root, name, class_auid, parent_auid, concrete) + + if propertydefs: + for prop_name, prop_args in propertydefs.items(): + pid = prop_args[1] + if pid and pid >= 0x8000: + assert pid not in self.local_pids + self.local_pids.add(pid) + c.register_propertydef(prop_name, *prop_args) + + self.classdefs_by_name[name] = c + self.classdefs_by_auid[c.auid] = c + + # Root is not include in the data model for some reason + if c.class_name != "Root": + properties.add2set(self, PID_CLASSDEFS, c.auid, c) + return c + + def lookup_class(self, class_id): + aaf_class = AAFClaseID_dict.get(class_id, None) + if aaf_class: + return aaf_class + + aaf_class = AAFClassName_dict.get(class_id, None) + if aaf_class: + return aaf_class + + aaf_class = self.typedefs_classes.get(class_id, None) + if aaf_class: + return aaf_class + + return core.AAFObject + + def lookup_typedef(self, t): + if isinstance(t, types.TypeDef): + return t + + t = str2auid(t) + if isinstance(t, AUID): + return self.typedefs_by_auid.get(t, None) + return self.typedefs_by_name.get(t, None) + + def lookup_classdef(self, t): + if isinstance(t, ClassDef): + return t + t = str2auid(t) + if isinstance(t, AUID): + return self.classdefs_by_auid.get(t, None) + return self.classdefs_by_name.get(t, None) + + @property + def classdef(self): + return self.classdefs_by_name['MetaDictionary'] + + def next_free_pid(self): + + while self.next_pid in self.local_pids: + self.next_pid -= 1 + if self.next_pid < 0x8000: + raise ValueError("ran out of local pids") + + result = self.next_pid + self.next_pid -= 1 + + self.local_pids.add(result) + return result + + + def read_properties(self): + super(MetaDictionary, self).read_properties() + for key, typedef in self['TypeDefinitions'].items(): + self.typedefs_by_name[typedef.type_name] = typedef + self.typedefs_by_auid[typedef.auid] = typedef + + # reset local pids + self.local_pids = set() + for key, classdef in self['ClassDefinitions'].items(): + self.classdefs_by_name[classdef.class_name] = classdef + self.classdefs_by_auid[classdef.auid] = classdef + propertydef_by_pid = {} + for pdef in classdef['Properties'].values(): + if pdef.pid >= 0x8000: + self.local_pids.add(pdef.pid) + + propertydef_by_pid[pdef.pid] = pdef + + classdef.propertydef_by_pid = propertydef_by_pid + + + # add typedefs not defined by file data model + if self.root.writeable: + for key, typedef in self.typedefs_by_auid.items(): + # skip root typedefs + if key in (AUID('05022800-0000-0000-060E-2B3401040101'), + AUID('05022700-0000-0000-060E-2B3401040101')): + # print("skipping root typedef") + continue + + if key not in self['TypeDefinitions']: + self['TypeDefinitions'].append(typedef) + + # add classdefs not defined by file data model + if self.root.writeable: + for key, classdef in self.classdefs_by_auid.items(): + # skip root classdefs + if key in (AUID('b3b398a5-1c90-11d4-8053-080036210804'), ): + # print("skipping root") + continue + + if key not in self['ClassDefinitions']: + + # handle any conflicting dynamic pids + for pdef in classdef['Properties'].values(): + if pdef.pid >= 0x8000: + if pdef.pid in self.local_pids: + pdef.pid = self.next_free_pid() + + self['ClassDefinitions'].append(classdef) diff --git a/aaf2/misc.py b/aaf2/misc.py new file mode 100644 index 0000000..5ae5e31 --- /dev/null +++ b/aaf2/misc.py @@ -0,0 +1,560 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) +from datetime import datetime +import io + +from . import core +from . utils import register_class +from .auid import AUID +from .rational import AAFRational + + +ConstantInterp = AUID("5b6c85a5-0ede-11d3-80a9-006008143e6f") +LinearInterp = AUID("5b6c85a4-0ede-11d3-80a9-006008143e6f") +BezierInterpolator = AUID("df394eda-6ac6-4566-8dbe-f28b0bdd781a") +CubicInterpolator = AUID("a04a5439-8a0e-4cb7-975f-a5b255866883") + +class TaggedValueHelper(object): + def __init__(self, poperty_vector): + self.p = poperty_vector + + def get(self, key, default=None): + for item in self.p: + if item['Name'].value == key: + return item + return default + + def __contains__(self, key): + return not self.get(key, None) is None + + def __getitem__(self, key): + p = self.get(key, None) + if p: + return p['Value'].value + + raise IndexError(key) + + def items(self): + for item in self.p: + yield item['Name'].value, item["Value"].value + + def append(self, value): + self.p.append(value) + + def __setitem__(self, key, value): + tag = self.get(key, None) + if tag is None: + tag = self.p.parent.root.create.TaggedValue() + tag['Name'].value = key + self.p.append(tag) + + tag['Value'].value = value + +@register_class +class TaggedValue(core.AAFObject): + class_id = AUID("0d010101-0101-3f00-060e-2b3402060101") + __slots__ = () + + def __init__(self, name=None, value=None, value_typedef=None): + self.name = name + if value is not None: + self.encode_value(value, value_typedef) + + @property + def name(self): + return self['Name'].value + + @name.setter + def name(self, value): + self['Name'].value = value + + @property + def value(self): + return self['Value'].value + + @value.setter + def value(self, value): + self['Value'].value = value + + @property + def value_typedef(self): + if self['Value'].data: + return self['Value'].typedef.decode_typedef(self['Value'].data) + + def encode_value(self, value, value_typedef=None): + if value_typedef is None: + self.value = value + return + self['Value'].add_pid_entry() + self['Value'].data = self['Value'].typedef.encode(value, value_typedef) + self['Value'].mark_modified() + + def __repr__(self): + s = "%s.%s" % (self.__class__.__module__, + self.__class__.__name__) + + value_typedef = self.value_typedef + if value_typedef: + s += ' %s' % repr(value_typedef) + + name = self.name + if name: + s += ' %s' % name + + s += " = " + repr(self.value) + + return '<%s at 0x%x>' % (s, id(self)) + +class Parameter(core.AAFObject): + class_id = AUID("0d010101-0101-3c00-060e-2b3402060101") + __slots__ = () + + @property + def auid(self): + return self['Definition'].value + @auid.setter + def auid(self, value): + self['Definition'].value = value + + @property + def parameterdef(self): + return self.root.dictionary.lookup_parameterdef(self['Definition'].value) + + @parameterdef.setter + def parameterdef(self, value): + self['Definition'].value = value.auid + + @property + def name(self): + return self.parameterdef.name + + @property + def unique_property(self): + return self['Definition'] + + @property + def unique_key(self): + return self.unique_property.value + + def __repr__(self): + s = "%s.%s" % (self.__class__.__module__, + self.__class__.__name__) + + parameterdef = self.parameterdef + if parameterdef: + s += ' %s' % parameterdef.name + + return '<%s at 0x%x>' % (s, id(self)) + +@register_class +class ConstantValue(Parameter): + class_id = AUID("0d010101-0101-3d00-060e-2b3402060101") + __slots__ = () + def __init__(self, parameterdef=None, value=None): + super(ConstantValue, self).__init__() + + if parameterdef is not None: + self.parameterdef = self.root.dictionary.lookup_parameterdef(parameterdef) + + if value is not None: + if parameterdef is None: + raise ValueError("need parameterdef to initialize value") + self.value = value + + @property + def typedef(self): + return self.parameterdef.typedef + + def value_at(self, t): + return self.value + + @property + def value(self): + return self['Value'].value + + @value.setter + def value(self, value): + self['Value'].add_pid_entry() + indirect_typedef = self['Value'].typedef + parameter_typdef = self.typedef + self['Value'].data = indirect_typedef.encode(value, parameter_typdef) + self['Value'].mark_modified() + +def lerp(p0, p1, t): + value = ((1.0 - t) * p0) + (t * p1) + return value + +def cubic_bezier(p0, p1, p2, p3, t): + value = pow(1.0 - t, 3.0) * p0 + \ + pow(1.0 - t, 2.0) * 3.0 * t * p1 + \ + (1.0 - t) * 3.0 * t * t * p2 + \ + t * t * t * p3 + return value + +def cubic_bezier_interpolate(p0, p1, p2, p3, t): + + t_len = p3[0] - p0[0] + t_diff = t - p0[0] + t_mix = t_diff/t_len + + guess_t = t_mix + + # approximate the correct t value, + # not sure if this correct + # its slow but seems to work.. + for i in range(20): + x = cubic_bezier(p0[0], p1[0], p2[0], p3[0], guess_t) + if x == t: + break + offset = x - t + guess_t -= offset/t_len + + if guess_t < 0: + guess_t = 0 + if guess_t > 1.0: + guess_t = 1.0 + + # print(" ", offset) + + y = cubic_bezier(p0[1], p1[1], p2[1], p3[1], guess_t) + # print(t, x, y) + + return y + +def sign_no_zero(v): + if 0 < v: + return -1 + return 1 + +def calculate_tangent(p0, p1, p2, in_tangent=False): + + # Note: + # This code was a lot of guess work + # MC docs refer to spline as "Natural Spline" or "Cardinal Spline" + # and AAF files export it with CubicInterpolator definition + # MC has some wacky way of calculating the y coord of tangents + + # in_tangent <---- x ----> out_tangent + + x = p1[0] + y = p1[1] + + px = p0[0] + nx = p2[0] + + py = p0[1] + ny = p2[1] + + if in_tangent: + tan_x = 0.4 * (x - px) + else: + tan_x = 0.4 * (nx - x) + + slope = (ny - py) / (nx - px) + prev_slope = (y - py) / (x - px) + next_slope = (ny - y) / (nx - x) + + if sign_no_zero(prev_slope) != sign_no_zero(next_slope) or sign_no_zero(slope) != sign_no_zero(next_slope): + tan_y = 0 + else: + height = abs(ny - py) + + h1 = abs(ny - y) + h2 = abs(y - py) + + # took me ages to figure this out + scale = min(h1, h2) / height * 2.0 + tan_y = scale * slope * tan_x + + if in_tangent: + tan_x *= -1.0 + tan_y *= -1.0 + + return (tan_x, tan_y) + +def cubic_interpolate(p0, p1, p2, p3, t): + + tan_x0, tan_y0 = calculate_tangent(p0, p1, p2, False) + tan_x1, tan_y1 = calculate_tangent(p1, p2, p3, True) + + p0 = p1 + p3 = p2 + + p1 = (p0[0] + tan_x0, p0[1] + tan_y0) + p2 = (p3[0] + tan_x1, p3[1] + tan_y1) + + return cubic_bezier_interpolate(p0, p1, p2, p3, t) + + +def mc_trapezoidal_integrate(f, a, b, n=5): + # this attempts to integrate a function the same way MC does. + # for most correct results abs(a-b) == 0.5 and n = 5 + + h = float(b - a) / n + pv = f(a-h) + result = 0 + for i in range(n): + v = f(a + (i * h)) + result += (v + pv) * h * 0.5 + pv = v + return result + +def integrate_iter(speed_map, start, end): + pos = 0 + for i in range(start, end-1): + t = float(i) + pos += mc_trapezoidal_integrate(speed_map.value_at, t-0.5, t) + yield t, pos + t += 0.5 + pos += mc_trapezoidal_integrate(speed_map.value_at, t-0.5, t) + yield t, pos + + t += 0.5 + pos += mc_trapezoidal_integrate(speed_map.value_at, t-0.5, t) + yield t, pos + +def generate_offset_map(speed_map, start=0, end=None): + pointlist = speed_map['PointList'] + + # first speed map key frame is the zero point + # of the offset map curve + first = pointlist.value[0] + center = int(first.time) + if end is None: + last = pointlist.value[-1] + end = int(last.time) + + if start > end: + raise ValueError("start needs to be less then end") + + time = [] + value = [] + offset_index = None + + inter_start = min(start, center) + inter_end = max(center, end+1) + + for i, (t,v) in enumerate(integrate_iter(speed_map, inter_start, inter_end)): + time.append(t) + value.append(v) + + if t == center: + offset_index = i + + center_offset = value[offset_index] + + # not really sure what this base frame offset is about + # but appears to contain the how much calculation is off... + center_offset -= first.base_frame + + result = [] + for i, t in enumerate(time): + if t > end: + break + + if t >= start: + v = value[i] - center_offset + result.append((t, v)) + + return result + +@register_class +class VaryingValue(Parameter): + class_id = AUID("0d010101-0101-3e00-060e-2b3402060101") + __slots__ = () + def __init__(self, parameterdef=None, interperlationdef=None): + super(VaryingValue, self).__init__() + if parameterdef: + self.parameterdef = self.root.dictionary.lookup_parameterdef(parameterdef) + + if interperlationdef: + self.interpolationdef = self.root.dictionary.lookup_interperlationdef(interperlationdef) + + @property + def interpolationdef(self): + return self['Interpolation'].value + @interpolationdef.setter + def interpolationdef(self, value): + self['Interpolation'].value = value + + # TODO: Deprecate this + @property + def interpolation(self): + return self['Interpolation'].value + @interpolationdef.setter + def interpolation(self, value): + self['Interpolation'].value = value + + @property + def typedef(self): + return self.parameterdef.typedef + + def add_keyframe(self, time, value, edit_hint=None): + cp = self.root.create.ControlPoint() + cp.time = time + + cp['Value'].add_pid_entry() + cp['Value'].data = cp['Value'].typedef.encode(value, self.typedef) + cp['Value'].mark_modified() + + if edit_hint: + cp['EditHint'].value = edit_hint + + pointlist = self['PointList'] + + if len(pointlist) > 0: + index = self.nearest_index(time) + nearest = pointlist[index] + if float(nearest.time) == float(time): + pointlist[index] = cp + else: + pointlist.insert(index+1, cp) + else: + pointlist.append(cp) + + return cp + + def value_at(self, t): + t = float(t) + + index = self.nearest_index(t) + pointlist = self['PointList'] + + p1 = pointlist[index] + + # clamp if t outside of range + if t < p1.time or index + 1 >= len(pointlist): + return float(p1.value) + + if self.interpolationdef.auid == ConstantInterp: + return float(p1.value) + + p2 = pointlist[index+1] + + if self.interpolationdef.auid == LinearInterp: + t_len = float(p2.time) - float(p1.time) + t_diff = t - float(p1.time) + t_mix = t_diff/t_len + + v0 = float(p1.value) + v1 = float(p2.value) + return lerp(v0, v1, t_mix) + + elif self.interpolationdef.auid == BezierInterpolator: + t0 = float(p1.time) + v0 = float(p1.value) + + t3 = float(p2.time) + v3 = float(p2.value) + + tangents = p1.tangents[1] + t1 = t0 + tangents[0] + v1 = v0 + tangents[1] + + tangents = p2.tangents[0] + t2 = t3 + tangents[0] + v2 = v3 + tangents[1] + + return cubic_bezier_interpolate((t0, v0), + (t1, v1), + (t2, v2), + (t3, v3), t) + + elif self.interpolationdef.auid == CubicInterpolator: + + t1 = float(p1.time) + v1 = float(p1.value) + + t2 = float(p2.time) + v2 = float(p2.value) + + if index - 1 >= 0: + p0 = pointlist[index - 1] + t0 = float(p0.time) + v0 = float(p0.value) + else: + t0 = t1 - ((t2 - t1) * 0.5) + v0 = v1 + + if index + 2 < len(pointlist): + p3 = pointlist[index + 2] + t3 = float(p3.time) + v3 = float(p3.value) + else: + t3 = t2 + ((t2 - t1) * 0.5) + v3 = v2 + + return cubic_interpolate((t0, v0), + (t1, v1), + (t2, v2), + (t3, v3), t) + + else: + + raise NotImplementedError("Interpolation not implemented for %s %s" % + (self.interpolationdef.name, str(self.interpolationdef.auid))) + + def nearest_index(self, t): + """ + binary search for index of point.time <= t + """ + pointlist = self['PointList'] + start = 0 + end = len(pointlist) - 1 + while True: + if end < start: + return max(0, end) + + m = (start + end) // 2 + p = pointlist[m] + if p.time < t: + start = m + 1 + elif p.time > t: + end = m - 1 + else: + return m + +@register_class +class ControlPoint(core.AAFObject): + class_id = AUID("0d010101-0101-1900-060e-2b3402060101") + __slots__ = () + + @property + def time(self): + return float(self['Time'].value) + + @time.setter + def time(self, value): + self['Time'].value = value + + @property + def value(self): + return float(self['Value'].value) + + @value.setter + def value(self, value): + self['Value'].value = AAFRational(value) + + @property + def point_properties(self): + props = {} + if 'ControlPointPointProperties' in self: + for p in self['ControlPointPointProperties'].value: + props[p.name] = p.value + return props + + @property + def base_frame(self): + return self.point_properties.get("PP_BASE_FRAME_U", 0) + + @property + def tangents(self): + props = self.point_properties + return [(float(props.get("PP_IN_TANGENT_POS_U", 0)), + float(props.get("PP_IN_TANGENT_VAL_U", 0))), + (float(props.get("PP_OUT_TANGENT_POS_U", 0)), + float(props.get("PP_OUT_TANGENT_VAL_U", 0)))] diff --git a/aaf2/mobid.py b/aaf2/mobid.py new file mode 100644 index 0000000..2cefa86 --- /dev/null +++ b/aaf2/mobid.py @@ -0,0 +1,476 @@ +""" + +Excerpt from SMPTE ST 330 (Focus on Basic UMID):: + + 5 General Specification + + A unique material identifier (UMID) provides for the globally unique identification of any audiovisual material. + This standard defines a dual approach through the specification of a basic UMID and an extended UMID. + The basic UMID provides a globally unique identification for audiovisual material that comprises an integer + number of one or more contiguous material units. The basic UMID has no embedded mechanism to + distinguish between individual material units within a single instance of audiovisual material. The data in the + basic UMID can be created through automatic generation. + + The extended UMID comprises the basic UMID followed immediately by a source pack that provides a + signature for material units. The source pack comprises a fixed length metadata pack of 32 bytes that + provides sufficient metadata by which source ?when, where and who (or what)? information can be identified + regardless of current ownership or status. The extended UMID also provides a mechanism to distinguish + between individual material units within a single instance of audiovisual material. + + The basic UMID is 32 bytes long and the extended UMID is 64 bytes long. + Both UMID types use the key-length-value construct defined by SMPTE ST 336. The key is a 16-byte + universal label truncated to 12 bytes. + In the case of the basic UMID, the length field has a value of 13h and the value is formed by the combination + of a material number and an instance number. + In the case of the extended UMID, the length field has a value of 33h and the value is formed by the + combination of the material and the instance numbers followed by the source pack. + All components of the UMID have a defined byte order for consistent application in storage and streaming + environments. + + The components of the basic UMID are: + 1. A 12-byte universal label, + 2. A 1-byte length value, + 3. A 3-byte instance number, and + 4. A 16-byte material number. + + The combination of the instance and material numbers can be treated as a dumb number. + Note: The material number does not indicate the status of the material (such as copy number) or its representation + (such as the compression kind). The material number can be identical in copies and in different representations of + the material. The purpose of the instance number is to separately identify different representations or instances of + audiovisual material. Thus, for example, a high-resolution picture and a thumbnail can both have the same + material number because they both represent the same picture but, because they are different instances, they will + have different instance numbers for the different representations. Guidance for the consistent application of new + material numbers and instance numbers is given in SMPTE RP 205. + + + UMID univeral label (SMPTELabel) + + Byte No. Description Value (hex) Meaning + ---------------------------------------------------------------------------------------- + 1 Object identifier 06h Universal label start + 2 Label size 0Ah 12-byte Universal label + 3 Designation: ISO 2Bh ISO registered + 4 Designation: SMPTE 34h SMPTE registered + 5 Registry category 01h Dictionaries + 6 Specific category 01h Metadata dictionaries + 7 Structure 01h Dictionary standard (SMPTE ST 335) + 8 Version number 05h Version of the metadata dictionary (defined in SMPTE RP 210) + 9 Class 01h Identifiers and locators + 10 Subclass 01h Globally unique identifiers + 11 Material type XXh See Section 6.1.2.1 + 12 Number creation method YYh See Section 6.1.2.2 + + 6.1.2.1 - Meterial type identification + + Byte 11 of the UL shall define the material type being identified using one of the values defined in Table 2. + The use of material types '01h', '02h', '03h' and '04h' shall be deprecated for use in implementations using + this revised standard. These values are preserved only for compatibility with systems implemented using + SMPTE ST 330:2000# + + Table 2 + + Byte value Meaning Examples and notes + -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 01h picture material Deprecated + 02h audio material Deprecated + 03h data material Deprecated + 04h other material Deprecated (originally not only picture, audio, or data material, but also a combination of material types) + 05h single picture component e.g. Y component + 06h Two or more picture components in a single container e.g. interleaved Y, Cb and Cr components + 08h single audio component e.g mono audio + 09h two or more audio components in a single container e.g. AES3 audio pair + 0Bh single auxiliary (or data) component e.g. sub-titles only + 0Ch two or more auxiliary (or data) components in a single container e.g. multiple sub-titles streams in different languages + 0Dh mixed group of components in a single container e.g. video & stereo audio pair + 0Fh material type is not identified + + 6.1.2.2 Number creation method identification + + Byte 12 of the UL shall define the method by which the material and instance numbers are created. This byte + is divided into top and bottom nibbles for the purpose of this definition. + The top nibble shall occupy the 4 most significant bits (MSBs) of the byte and the value shall be used to + define the method of material number creation. The values used by this nibble shall be limited to the range 0 + to 7h so that byte 12 conforms to the ASN.1 BER short form coding rules used by SMPTE ST 298. + The methods of material number generation shall be as defined in table 3 and the specification of the each + method shall be as defined in Annex A. + Note: New material number generation methods can be added by amendment or revision of this document. Each + addition will provide the proposed value (within the range of values currently identified as "Reserved but not + defined") for inclusion in Table 3 together with the supporting definition to be added to Annex A. + + Table 3 - Identification of material number generation method:: + + Value (hex) Method + ------------------------------------------------------------- + 0 No defined method + 1 SMPTE method + 2 UUID/UL method + 3 Masked method + 4 IEEE 1394 network method + 5~7 Reserved but not defined + + +Notes from Pixar 10/30/17 + +Final note of discussion with Avid engineers how the top nibble in the 12th byte in the SMPTELabel should be set. +(In the past we always had it set to 00, i.e. "no defined method", we had some confusion about how to set it when using a uuid for the material) + +Avid Engineer: + +"The specification of number creation identification is very clear about using the 4 MSBs for the material +number so I am pretty sure that the numbers in the sub-titles of Annex A (e.g. 02h) should not be interpreted literally as values of byte 12. +20h is the correct value of the byte 12 for UL/UUID method/No defined method. + +By the way, I don't see anything wrong with setting byte 12 to 00h (No defined method / No defined method)" + +We at Pixar decided to set the byte to 20h, since it (even if already very minimal) completely eliminates the possibility to collide with any +MOB ID created by our old MOB ID generation algorithm. +""" + +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +import uuid +import struct +from .utils import (int_from_bytes, bytes_from_int, unpack_u16le_from, unpack_u32le_from) +from . import auid + +MOBID_STRUCT = struct.Struct(str(''.join(( '<', + '12B', # UInt8Array12 SMPTELabel 0 + 'B', # UInt8 length 12 + 'B', # UInt8 instanceHigh 13 + 'B', # UInt8 instanceMid 14 + 'B', # UInt8 instanceLow 15 + 'I', # UInt32 Data1 16 + 'H', # UInt16 Data2 20 + 'H', # UInt16 Data3 22 + '8B', # UInt8Array8 Data4 24 + )))) + +def UniqueMobID(): + m = MobID() # Description Meaning + m.SMPTELabel = [0x06, # Object identifier Universal label start + 0x0a, # Label size 12-byte Universal label + 0x2b, # Designation: ISO ISO registered + 0x34, # Designation: SMPTE SMPTE registered + 0x01, # Registry category Dictionaries + 0x01, # Specific category Metadata dictionaries + 0x01, # Structure Dictionary standard (SMPTE ST 335) + 0x05, # Version number Version of the metadata dictionary (defined in SMPTE RP 210) + 0x01, # Class Identifiers and locators + 0x01, # Subclass Globally unique identifiers + 0x0f, # Material type See Section 6.1.2.1 + 0x20 # Number creation method See Section 6.1.2.2 # Using UUID/UL method, Note 10/30/17 - matching pixar recommendation + ] + m.length = 0x13 # Length, 13h = Basic UMID, 33h = Extended UMID + m.instanceHigh = 0x00 + m.instanceMid = 0x00 + m.instanceLow = 0x00 + m.material = uuid.uuid4() # 16 byte material slot, filled with uuid according to RFC4122 + return m + +class MobID(object): + __slots__ = ('bytes_le') + def __init__(self, mobid=None, bytes_le=None, int=None): + + if bytes_le: + self.bytes_le = bytearray(bytes_le) + else: + self.bytes_le = bytearray(32) + + if mobid is not None: + self.urn = mobid + + if int is not None: + self.int = int + + @staticmethod + def new(): + """ + Static method for generating unique MobIDs. Uses uuid.uuid4() for generation. + """ + return UniqueMobID() + + @property + def material(self): + """ + MobID material representation as a UUID + """ + return auid.AUID(bytes_le=self.bytes_le[16:]) + + @material.setter + def material(self, value): + self.bytes_le[16:] = value.bytes_le + + @property + def SMPTELabel(self): + return self.bytes_le[0:12] + + @SMPTELabel.setter + def SMPTELabel(self, value): + struct.pack_into(str('12B'), self.bytes_le, 0, *value) + + @property + def length(self): + return self.bytes_le[12] + + @length.setter + def length(self, value): + self.bytes_le[12] = value + + @property + def instanceHigh(self): + return self.bytes_le[13] + + @instanceHigh.setter + def instanceHigh(self, value): + self.bytes_le[13] = value + + @property + def instanceMid(self): + return self.bytes_le[14] + + @instanceMid.setter + def instanceMid(self, value): + self.bytes_le[14] = value + + @property + def instanceLow(self): + return self.bytes_le[15] + + @instanceLow.setter + def instanceLow(self, value): + self.bytes_le[15] = value + + @property + def Data1(self): + return unpack_u32le_from(self.bytes_le, 16) + + @Data1.setter + def Data1(self, value): + struct.pack_into(str(' other.int + return NotImplemented + + def __ge__(self, other): + if isinstance(other, MobID): + return self.int >= other.int + return NotImplemented + + def __hash__(self): + return hash(bytes(self.bytes_le)) + + @property + def urn(self): + """ + MobID Uniform Resource Name representation. + https://en.wikipedia.org/wiki/Uniform_Resource_Name + """ + + SMPTELabel = self.SMPTELabel + Data4 = self.Data4 + + # handle case UMIDs where the material number is half swapped + if (SMPTELabel[11] == 0x00 and + Data4[0] == 0x06 and Data4[1] == 0x0E and + Data4[2] == 0x2B and Data4[3] == 0x34 and + Data4[4] == 0x7F and Data4[5] == 0x7F): + + # print("case 1") + f = "urn:smpte:umid:%02x%02x%02x%02x.%02x%02x%02x%02x.%02x%02x%02x%02x." + \ + "%02x" + \ + "%02x%02x%02x." + \ + "%02x%02x%02x%02x.%02x%02x%02x%02x.%08x.%04x%04x" + + return f % ( + SMPTELabel[0], SMPTELabel[1], SMPTELabel[2], SMPTELabel[3], + SMPTELabel[4], SMPTELabel[5], SMPTELabel[6], SMPTELabel[7], + SMPTELabel[8], SMPTELabel[9], SMPTELabel[10], SMPTELabel[11], + self.length, + self.instanceHigh, self.instanceMid, self.instanceLow, + Data4[0], Data4[1], Data4[2], Data4[3], + Data4[4], Data4[5], Data4[6], Data4[7], + self.Data1, self.Data2, self.Data3) + else: + # print("case 2") + f = "urn:smpte:umid:%02x%02x%02x%02x.%02x%02x%02x%02x.%02x%02x%02x%02x." + \ + "%02x" + \ + "%02x%02x%02x." + \ + "%08x.%04x%04x.%02x%02x%02x%02x.%02x%02x%02x%02x" + + return f % ( + SMPTELabel[0], SMPTELabel[1], SMPTELabel[2], SMPTELabel[3], + SMPTELabel[4], SMPTELabel[5], SMPTELabel[6], SMPTELabel[7], + SMPTELabel[8], SMPTELabel[9], SMPTELabel[10], SMPTELabel[11], + self.length, + self.instanceHigh, self.instanceMid, self.instanceLow, + self.Data1, self.Data2, self.Data3, + Data4[0], Data4[1], Data4[2], Data4[3], + Data4[4], Data4[5], Data4[6], Data4[7]) + + @urn.setter + def urn(self, value): + s = str(value).lower() + for item in ("urn:smpte:umid:", ".", '-', '0x'): + s = s.replace(item, '') + assert len(s) == 64 + + SMPTELabel = [0 for i in range(12)] + start = 0 + for i in range(12): + end = start + 2 + v = s[start:end] + SMPTELabel[i] = int(v, 16) + start = end + self.SMPTELabel = SMPTELabel + self.length = int(s[24:26], 16) + self.instanceHigh = int(s[26:28], 16) + self.instanceMid = int(s[28:30], 16) + self.instanceLow = int(s[30:32], 16) + + start = 32 + data = [0 for i in range(6)] + for i in range(6): + end = start + 2 + v = s[start:end] + data[i] = int(v, 16) + start = end + # print(s[32:start]) + if (SMPTELabel[11] == 0x00 and + data[0] == 0x06 and data[1] == 0x0E and + data[2] == 0x2B and data[3] == 0x34 and + data[4] == 0x7F and data[5] == 0x7F): + + start = 32 + data4 = [0 for i in range(8)] + for i in range(8): + end = start + 2 + v = s[start:end] + data4[i] = int(v, 16) + start = end + + self.Data4 = data4 + self.Data1 = int(s[48:56], 16) + self.Data2 = int(s[56:60], 16) + self.Data3 = int(s[60:64], 16) + + else: + self.Data1 = int(s[32:40], 16) + self.Data2 = int(s[40:44], 16) + self.Data3 = int(s[44:48], 16) + + start = 48 + data4 = [0 for i in range(8)] + for i in range(8): + end = start + 2 + v = s[start:end] + data4[i] = int(v, 16) + start = end + self.Data4 = data4 + + def __repr__(self): + return str(self.urn) + +if __name__ == "__main__": + + t = "urn:smpte:umid:060a2b34.01010101.01010f00.13000000.060e2b34.7f7f2a80.4fa5c20f.4e301e50" + m = MobID(t) + print(t) + print(m) + assert str(m) == t diff --git a/aaf2/mobs.py b/aaf2/mobs.py new file mode 100644 index 0000000..e535394 --- /dev/null +++ b/aaf2/mobs.py @@ -0,0 +1,419 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +from datetime import datetime +import io + +from . import core +from . mobid import MobID +from . utils import register_class, rescale +from . misc import TaggedValueHelper +from . import essence +from . import video +from . import audio +from .rational import AAFRational +from .auid import AUID + +@register_class +class Mob(core.AAFObject): + """ + Base Class for All Mob Objects + """ + + class_id = AUID("0d010101-0101-3400-060e-2b3402060101") + __slots__ = () + + def __init__(self, name=None): + self.name = name or "Mob" + self.mob_id = MobID.new() + + now = datetime.now() + self['CreationTime'].value = now + self['LastModified'].value = now + + self['Slots'].value = [] + + @property + def unique_key(self): + return self.mob_id + + @property + def name(self): + return self['Name'].value + + @name.setter + def name(self, value): + self['Name'].value = value + + @property + def mob_id(self): + """ + The unique Mob ID associated with this mob. Get Returns :class:`aaf2.mobid.MobID` Object + """ + return self['MobID'].value + + @mob_id.setter + def mob_id(self, value): + self['MobID'].value = value + + @property + def usage(self): + return self['UsageCode'].value + @usage.setter + def usage(self, value): + self['UsageCode'].value = value + + @property + def comments(self): + return TaggedValueHelper(self['UserComments']) + + @property + def slots(self): + return self['Slots'] + + def slot_at(self, slot_id): + for slot in self.slots: + if slot.slot_id == slot_id: + return slot + raise IndexError("No SlotID: %s" % str(slot_id)) + + def create_timeline_slot(self, edit_rate, slot_id=None): + slots = [slot.slot_id for slot in self.slots] + slots.sort() + if slot_id is None: + start = 1 + if slots and slots[0] == 0: + start = 0 + + for i, e in enumerate(slots + [None], start): + if i != e: + slot_id = i + elif slot_id in slots: + raise ValueError("slot id: %d already exists" % slot_id) + + slot = self.root.create.TimelineMobSlot(slot_id, edit_rate=edit_rate) + self.slots.append(slot) + return slot + + def create_empty_sequence_slot(self, edit_rate, slot_id=None, media_kind=None): + """ + Create an empty timeline slot and sets its segment to a new, empty + `aaf2.components.Sequence` component. Timeline slots are for continuous, + monotonically-changing media, like picture and sound. + """ + slot = self.create_timeline_slot(edit_rate, slot_id) + sequence = self.root.create.Sequence(media_kind=media_kind) + sequence['Components'].value = [] + slot.segment = sequence + return slot + + def create_picture_slot(self, edit_rate=25): + """ + Create an empty timeline slot, with the 'picture' media kind, and sets + its segment to a new, empty `aaf2.components.Sequence` component. + """ + return self.create_empty_sequence_slot(edit_rate, media_kind="picture") + + def create_sound_slot(self, edit_rate=25): + """ + Create an empty timeline slot, with the 'sound' media kind, and sets + its segment to a new, empty `aaf2.components.Sequence` component. + """ + return self.create_empty_sequence_slot(edit_rate, media_kind="sound") + + def create_source_clip(self, slot_id=None, start=None, length=None, media_kind=None): + """ + Create a SourceClip of Mobs slot with `slot_id`. If no length given the default + length will be the full length of slots segment minius `start`. + Returns :class:`aaf2.components.SourceClip` Object + """ + source_slot = self.slot_at(slot_id) + if not media_kind: + media_kind = source_slot.media_kind + + clip = self.root.create.SourceClip(media_kind=media_kind) + clip.mob = self + clip.slot = source_slot + clip.start = start or 0 + clip.length = length or max(source_slot.length - clip.start, 0) + return clip + + def __repr__(self): + s = "%s.%s" % (self.__class__.__module__, + self.__class__.__name__) + s += ' "%s" %s' % (self.name or "", str(self.mob_id)) + + return '<%s at 0x%x>' % (s, id(self)) + +@register_class +class CompositionMob(Mob): + class_id = AUID("0d010101-0101-3500-060e-2b3402060101") + __slots__ = () + +@register_class +class MasterMob(Mob): + class_id = AUID("0d010101-0101-3600-060e-2b3402060101") + __slots__ = () + + def import_dnxhd_essence(self, path, edit_rate, tape=None): + """ + Import video essence from raw DNxHD/DNxHR stream + """ + + # create sourceMob and essencedata + source_mob = self.root.create.SourceMob("%s.PHYS" % self.name) + self.root.content.mobs.append(source_mob) + + # import the essencedata + source_slot = source_mob.import_dnxhd_essence(path, edit_rate, tape) + + # create slot and clip that references source_mob slot + slot = self.create_timeline_slot(edit_rate=edit_rate) + slot.segment = source_mob.create_source_clip(source_slot.slot_id, media_kind='picture') + + # set clip length + slot.segment.length = source_slot.segment.length + return slot + + def import_audio_essence(self, path, edit_rate=None, tape=None): + """ + Import audio essence from wav file + """ + + # create sourceMob and essencedata + source_mob = self.root.create.SourceMob("%s.PHYS" % self.name) + self.root.content.mobs.append(source_mob) + + source_slot = source_mob.import_audio_essence(path, edit_rate, tape) + + # create slot and clip that references source_mob slot + edit_rate = edit_rate or source_slot.edit_rate + slot = self.create_timeline_slot(edit_rate=edit_rate) + slot.segment = source_mob.create_source_clip(source_slot.slot_id, media_kind='sound') + + # set clip length + slot.segment.length = source_slot.segment.length + return slot + +@register_class +class SourceMob(Mob): + class_id = AUID("0d010101-0101-3700-060e-2b3402060101") + __slots__ = () + + @property + def descriptor(self): + return self['EssenceDescription'].value + @descriptor.setter + def descriptor(self, value): + self['EssenceDescription'].value = value + + def create_essence(self, edit_rate=None, media_kind='picture', slot_id=None): + # NOTE: appears like a SourceMob can only link to 1 essence and it must be slot 1 + slot = self.create_empty_slot(edit_rate=edit_rate, media_kind=media_kind, slot_id=1) + essencedata = self.root.create.EssenceData() + essencedata.mob_id = self.mob_id + self.root.content.essencedata.append(essencedata) + return essencedata, slot + + def create_empty_slot(self, edit_rate=None, media_kind='picture', slot_id=None): + + slot = self.create_timeline_slot(edit_rate, slot_id) + clip = self.root.create.SourceClip(media_kind=media_kind) + slot.segment = clip + + return slot + + def create_timecode_slot(self, edit_rate, timecode_fps, drop_frame=False): + timecode_slot = self.create_timeline_slot(edit_rate) + timecode_slot.segment = self.root.create.Timecode(timecode_fps, drop=drop_frame) + return timecode_slot + + def create_tape_slots(self, tape_name, edit_rate, timecode_fps, drop_frame=False, media_kind=None): + self.name = tape_name + self.descriptor = self.root.create.TapeDescriptor() + + slot = self.create_empty_slot(edit_rate, media_kind, slot_id=1) + slot.segment.length = int(float(AAFRational(edit_rate)) * 60 *60 * 12) # 12 hours + timecode_slot = self.create_timecode_slot(edit_rate, timecode_fps, drop_frame) + + return slot, timecode_slot + + def import_rawvideo_essence(self, path, edit_rate, width, height, pixel_layout, tape=None): + essencedata, slot = self.create_essence(edit_rate, 'picture') + + if tape: + slot.segment = tape + + # create essence descriptor + descriptor = self.root.create.RGBADescriptor() + self.descriptor = descriptor + + alignment = 8192 + + # set minimal properties + descriptor['SampleRate'].value = edit_rate + descriptor['VideoLineMap'].value = [42, 0] # Not exactly sure what linemap is + descriptor['ContainerFormat'].value = self.root.dictionary.lookup_containerdef("AAF") + + descriptor['StoredWidth'].value = width + descriptor['StoredHeight'].value = height + descriptor['ImageAlignmentFactor'].value = alignment + descriptor['PixelLayout'].value = pixel_layout + descriptor['FrameLayout'].value = "FullFrame" + descriptor['ImageAspectRatio'].value = "%d/%d" % (width, height) + + raw_frame_size_bits = 0 + for layout in pixel_layout: + raw_frame_size_bits += layout.get('Size', 0) * width * height + + raw_frame_size = (raw_frame_size_bits + 7) // 8 + frame_size = (raw_frame_size + (alignment-1)) // alignment * alignment + + descriptor['FrameSampleSize'].value = frame_size + + # open essence stream + stream = essencedata.open('w') + length = 0 + alignment_padding = bytearray(frame_size - raw_frame_size) + + with io.open(path, 'rb', buffering=io.DEFAULT_BUFFER_SIZE) as f: + while True: + data = f.read(raw_frame_size) + if not data: + break + stream.write(data) + if alignment_padding: + stream.write(alignment_padding) + length += 1 + + descriptor['Length'].value = length + descriptor['ImageSize'].value = length * frame_size + slot.segment.length = length + + return slot + + def import_dnxhd_essence(self, path, edit_rate, tape=None): + """ + Import video essence from raw DNxHD/DNxHR stream + """ + + essencedata, slot = self.create_essence(edit_rate, 'picture') + + if tape: + slot.segment = tape + + # create essence descriptor + descriptor = self.root.create.CDCIDescriptor() + self.descriptor = descriptor + + # set minimal properties + descriptor['SampleRate'].value = edit_rate + descriptor['VideoLineMap'].value = [42, 0] # Not exactly sure what linemap is + descriptor['ContainerFormat'].value = self.root.dictionary.lookup_containerdef("AAF") + dnxhd_codec_auid = AUID("8ef593f6-9521-4344-9ede-b84e8cfdc7da") + descriptor['CodecDefinition'].value = self.root.dictionary.lookup_codecdef(dnxhd_codec_auid) + + # open essence stream + stream = essencedata.open('w') + + # open input file + with io.open(path, 'rb', buffering=io.DEFAULT_BUFFER_SIZE) as f: + + cid = None + for i, packet in enumerate(video.iter_dnx_stream(f), 1): + if cid is None: + (cid, width, height, bitdepth, interlaced) = video.read_dnx_frame_header(packet) + descriptor['StoredWidth'].value = width + descriptor['StoredHeight'].value = height + descriptor['ComponentWidth'].value = bitdepth + descriptor['FrameLayout'].value = 'SeparateFields' if interlaced else 'FullFrame' + descriptor['ImageAspectRatio'].value = "%d/%d" % (width, height) + descriptor['FrameSampleSize'].value = len(packet) + descriptor['Compression'].value = video.dnx_compression_auids[cid] + descriptor['HorizontalSubsampling'].value = 2 + + stream.write(packet) + + # set descriptor and component lengths + descriptor.length = i + slot.segment.length = i + + return slot + + def import_audio_essence(self, path, edit_rate=None, tape=None): + """ + Import audio essence from wav file + """ + + # read the wav file header + a = audio.WaveReader(path) + sample_rate = a.getframerate() + channels = a.getnchannels() + sample_width = a.getsampwidth() + block_align = a.getblockalign() + length = a.getnframes() + + edit_rate = edit_rate or sample_rate + + # create essencedata + essencedata, slot = self.create_essence(edit_rate, 'sound') + if tape: + slot.segment = tape + + # create essence descriptor + descriptor = self.root.create.PCMDescriptor() + self.descriptor = descriptor + descriptor['Channels'].value = channels + descriptor['BlockAlign'].value = block_align + descriptor['SampleRate'].value = sample_rate + descriptor['AverageBPS'].value = sample_rate * channels * sample_width + descriptor['QuantizationBits'].value = sample_width * 8 + descriptor['AudioSamplingRate'].value = sample_rate + + # set lengths + descriptor.length = length + slot.segment.length = int(rescale(length, sample_rate, edit_rate)) + + stream = essencedata.open('w') + + while True: + data = a.readframes(sample_rate) + if not data: + break + stream.write(data) + + return slot + + def export_audio(self, path): + descriptor = self.descriptor + assert isinstance(descriptor, essence.PCMDescriptor) + + a = audio.wave.Wave_write(path) + try: + + channels = descriptor['Channels'].value + sample_rate = int(float(descriptor['SampleRate'].value)) + sample_size = descriptor['QuantizationBits'].value // 8 + + a.setnchannels(channels) + a.setframerate(sample_rate) + a.setsampwidth(sample_size) + + read_size = channels * int(float(sample_rate)) * sample_size + stream = self.essence.open('r') + while True: + data = stream.read(read_size) + if not data: + break + a.writeframesraw(data) + + finally: + a.close() + + @property + def essence(self): + return self.root.content.essencedata.get(self.mob_id, None) diff --git a/aaf2/mobslots.py b/aaf2/mobslots.py new file mode 100644 index 0000000..435ed85 --- /dev/null +++ b/aaf2/mobslots.py @@ -0,0 +1,140 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +from . import core +from .utils import register_class +from .auid import AUID +from . import components + +@register_class +class MobSlot(core.AAFObject): + class_id = AUID("0d010101-0101-3800-060e-2b3402060101") + __slots__ = () + + def __init__(self, slot_id=None, name=None, segment=None): + super(MobSlot, self).__init__() + self.slot_id = slot_id + self.name = name + if slot_id is not None and name is None: + self.name = "Track-%03d" % slot_id + self.segment = segment + + @property + def segment(self): + return self['Segment'].value + + @segment.setter + def segment(self, value): + self['Segment'].value = value + + @property + def name(self): + return self['SlotName'].value + + @name.setter + def name(self, value): + self['SlotName'].value = value + + @property + def datadef(self): + segment = self.segment + if segment: + return segment.datadef + + @property + def media_kind(self): + segment = self.segment + if segment: + return segment.media_kind + + @property + def slot_id(self): + return self['SlotID'].value + + @slot_id.setter + def slot_id(self, value): + self['SlotID'].value = value + + def __repr__(self): + name = self.name + slot_id = self.slot_id + s = "%s.%s" % (self.__class__.__module__, + self.__class__.__name__) + + if slot_id is not None: + s += " %d" % slot_id + if name: + s += ' "%s"' % name + + return '<%s at 0x%x>' % (s, id(self)) + + @property + def length(self): + return 0 + +@register_class +class EventMobSlot(MobSlot): + class_id = AUID( "0d010101-0101-3900-060e-2b3402060101") + __slots__ = () + + @property + def edit_rate(self): + return self['EditRate'].value + + @edit_rate.setter + def edit_rate(self, value): + self['EditRate'].value = value + +@register_class +class TimelineMobSlot(MobSlot): + class_id = AUID("0d010101-0101-3b00-060e-2b3402060101") + __slots__ = () + def __init__(self, slot_id=None, name=None, segment=None, origin=None, edit_rate=None): + super(TimelineMobSlot, self).__init__(slot_id, name, segment) + self.origin = origin or 0 + self.edit_rate = edit_rate or 25 + + @property + def origin(self): + return self['Origin'].value + @origin.setter + def origin(self, value): + self['Origin'].value = value + + @property + def edit_rate(self): + return self['EditRate'].value + + @edit_rate.setter + def edit_rate(self, value): + self['EditRate'].value = value + + @property + def length(self): + segment = self.segment + + if isinstance(segment, components.Sequence): + length = 0 + for c in segment.components: + if isinstance(c, components.Transition): + length -= c.length + else: + length += c.length + return length + + elif isinstance(segment, components.NestedScope): + length = 0 + for slot in segment.slots: + max(length, slot.length) + return length + else: + return segment.length + +@register_class +class StaticMobSlot(MobSlot): + class_id = AUID("0d010101-0101-3a00-060e-2b3402060101") + __slots__ = () diff --git a/aaf2/model/__init__.py b/aaf2/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aaf2/model/classdefs.py b/aaf2/model/classdefs.py new file mode 100644 index 0000000..bbe1e00 --- /dev/null +++ b/aaf2/model/classdefs.py @@ -0,0 +1,660 @@ +classdefs = { +"InterchangeObject" : ("0d010101-0101-0100-060e-2b3402060101", None, False, { + "ObjClass" : ("06010104-0101-0000-060e-2b3401010102", 0x0101, "05010100-0000-0000-060e-2b3401040101", False, False), + "Generation" : ("05200701-0800-0000-060e-2b3401010102", 0x0102, "01030100-0000-0000-060e-2b3401040101", True, False), + } +), +"Component" : ("0d010101-0101-0200-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + "DataDefinition" : ("04070100-0000-0000-060e-2b3401010102", 0x0201, "05010300-0000-0000-060e-2b3401040101", False, False), + "Length" : ("07020201-0103-0000-060e-2b3401010102", 0x0202, "01012002-0000-0000-060e-2b3401040101", True, False), + "KLVData" : ("03010210-0400-0000-060e-2b3401010102", 0x0203, "05060900-0000-0000-060e-2b3401040101", True, False), + "UserComments" : ("03020102-1600-0000-060e-2b3401010107", 0x0204, "05060800-0000-0000-060e-2b3401040101", True, False), + "Attributes" : ("03010210-0800-0000-060e-2b3401010107", 0x0205, "05060800-0000-0000-060e-2b3401040101", True, False), + } +), +"Segment" : ("0d010101-0101-0300-060e-2b3402060101", "0d010101-0101-0200-060e-2b3402060101", False, { + } +), +"EdgeCode" : ("0d010101-0101-0400-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "Start" : ("01040901-0000-0000-060e-2b3401010102", 0x0401, "01012001-0000-0000-060e-2b3401040101", False, False), + "FilmKind" : ("04100103-0109-0000-060e-2b3401010102", 0x0402, "0201010d-0000-0000-060e-2b3401040101", False, False), + "CodeFormat" : ("04100103-0102-0000-060e-2b3401010101", 0x0403, "0201010c-0000-0000-060e-2b3401040101", False, False), + "Header" : ("01030201-0200-0000-060e-2b3401010102", 0x0404, "04100100-0000-0000-060e-2b3401040101", True, False), + } +), +"EssenceGroup" : ("0d010101-0101-0500-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "Choices" : ("06010104-0601-0000-060e-2b3401010102", 0x0501, "05060700-0000-0000-060e-2b3401040101", False, False), + "StillFrame" : ("06010104-0208-0000-060e-2b3401010102", 0x0502, "05020800-0000-0000-060e-2b3401040101", True, False), + } +), +"Event" : ("0d010101-0101-0600-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", False, { + "Position" : ("07020103-0303-0000-060e-2b3401010102", 0x0601, "01012001-0000-0000-060e-2b3401040101", False, False), + "Comment" : ("05300404-0100-0000-060e-2b3401010102", 0x0602, "01100200-0000-0000-060e-2b3401040101", True, False), + } +), +"GPITrigger" : ("0d010101-0101-0700-060e-2b3402060101", "0d010101-0101-0600-060e-2b3402060101", True, { + "ActiveState" : ("05300401-0000-0000-060e-2b3401010101", 0x0801, "01040100-0000-0000-060e-2b3401040101", False, False), + } +), +"CommentMarker" : ("0d010101-0101-0800-060e-2b3402060101", "0d010101-0101-0600-060e-2b3402060101", True, { + "Annotation" : ("06010104-020a-0000-060e-2b3401010102", 0x0901, "05020800-0000-0000-060e-2b3401040101", True, False), + } +), +"Filler" : ("0d010101-0101-0900-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + } +), +"OperationGroup" : ("0d010101-0101-0a00-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "Operation" : ("05300506-0000-0000-060e-2b3401010102", 0x0b01, "05010700-0000-0000-060e-2b3401040101", False, False), + "InputSegments" : ("06010104-0602-0000-060e-2b3401010102", 0x0b02, "05060600-0000-0000-060e-2b3401040101", True, False), + "Parameters" : ("06010104-060a-0000-060e-2b3401010102", 0x0b03, "05060a00-0000-0000-060e-2b3401040101", True, False), + "BypassOverride" : ("0530050c-0000-0000-060e-2b3401010102", 0x0b04, "01010300-0000-0000-060e-2b3401040101", True, False), + "Rendering" : ("06010104-0206-0000-060e-2b3401010102", 0x0b05, "05020800-0000-0000-060e-2b3401040101", True, False), + } +), +"NestedScope" : ("0d010101-0101-0b00-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "Slots" : ("06010104-0607-0000-060e-2b3401010102", 0x0c01, "05060600-0000-0000-060e-2b3401040101", False, False), + } +), +"Pulldown" : ("0d010101-0101-0c00-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "InputSegment" : ("06010104-0207-0000-060e-2b3401010102", 0x0d01, "05020600-0000-0000-060e-2b3401040101", False, False), + "PulldownKind" : ("05401001-0200-0000-060e-2b3401010102", 0x0d02, "0201010b-0000-0000-060e-2b3401040101", False, False), + "PulldownDirection" : ("05401001-0100-0000-060e-2b3401010102", 0x0d03, "0201010a-0000-0000-060e-2b3401040101", False, False), + "PhaseFrame" : ("05401001-0300-0000-060e-2b3401010102", 0x0d04, "01012300-0000-0000-060e-2b3401040101", False, False), + } +), +"ScopeReference" : ("0d010101-0101-0d00-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "RelativeScope" : ("06010103-0300-0000-060e-2b3401010102", 0x0e01, "01010300-0000-0000-060e-2b3401040101", False, False), + "RelativeSlot" : ("06010103-0400-0000-060e-2b3401010102", 0x0e02, "01010300-0000-0000-060e-2b3401040101", False, False), + } +), +"Selector" : ("0d010101-0101-0e00-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "Selected" : ("06010104-0209-0000-060e-2b3401010102", 0x0f01, "05020600-0000-0000-060e-2b3401040101", False, False), + "Alternates" : ("06010104-0608-0000-060e-2b3401010102", 0x0f02, "05060600-0000-0000-060e-2b3401040101", True, False), + } +), +"Sequence" : ("0d010101-0101-0f00-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "Components" : ("06010104-0609-0000-060e-2b3401010102", 0x1001, "05060100-0000-0000-060e-2b3401040101", False, False), + } +), +"SourceReference" : ("0d010101-0101-1000-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", False, { + "SourceID" : ("06010103-0100-0000-060e-2b3401010102", 0x1101, "01030200-0000-0000-060e-2b3401040101", True, False), + "SourceMobSlotID" : ("06010103-0200-0000-060e-2b3401010102", 0x1102, "01010300-0000-0000-060e-2b3401040101", False, False), + "ChannelIDs" : ("06010103-0700-0000-060e-2b3401010107", 0x1103, "04010900-0000-0000-060e-2b3401040101", True, False), + "MonoSourceSlotIDs" : ("06010103-0800-0000-060e-2b3401010108", 0x1104, "04010900-0000-0000-060e-2b3401040101", True, False), + } +), +"SourceClip" : ("0d010101-0101-1100-060e-2b3402060101", "0d010101-0101-1000-060e-2b3402060101", True, { + "StartTime" : ("07020103-0104-0000-060e-2b3401010102", 0x1201, "01012001-0000-0000-060e-2b3401040101", True, False), + "FadeInLength" : ("07020201-0105-0200-060e-2b3401010102", 0x1202, "01012002-0000-0000-060e-2b3401040101", True, False), + "FadeInType" : ("05300501-0000-0000-060e-2b3401010101", 0x1203, "02010107-0000-0000-060e-2b3401040101", True, False), + "FadeOutLength" : ("07020201-0105-0300-060e-2b3401010102", 0x1204, "01012002-0000-0000-060e-2b3401040101", True, False), + "FadeOutType" : ("05300502-0000-0000-060e-2b3401010101", 0x1205, "02010107-0000-0000-060e-2b3401040101", True, False), + } +), +"TextClip" : ("0d010101-0101-1200-060e-2b3402060101", "0d010101-0101-1000-060e-2b3402060101", False, { + } +), +"HTMLClip" : ("0d010101-0101-1300-060e-2b3402060101", "0d010101-0101-1200-060e-2b3402060101", True, { + "BeginAnchor" : ("05300601-0100-0000-060e-2b3401010102", 0x1401, "01100200-0000-0000-060e-2b3401040101", True, False), + "EndAnchor" : ("05300602-0100-0000-060e-2b3401010102", 0x1402, "01100200-0000-0000-060e-2b3401040101", True, False), + } +), +"Timecode" : ("0d010101-0101-1400-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "Start" : ("07020103-0105-0000-060e-2b3401010102", 0x1501, "01012001-0000-0000-060e-2b3401040101", False, False), + "FPS" : ("04040101-0206-0000-060e-2b3401010102", 0x1502, "01010200-0000-0000-060e-2b3401040101", False, False), + "Drop" : ("04040101-0500-0000-060e-2b3401010101", 0x1503, "01040100-0000-0000-060e-2b3401040101", False, False), + } +), +"TimecodeStream" : ("0d010101-0101-1500-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", False, { + "SampleRate" : ("04040101-0201-0000-060e-2b3401010102", 0x1601, "03010100-0000-0000-060e-2b3401040101", False, False), + "Source" : ("04070300-0000-0000-060e-2b3401010102", 0x1602, "04100200-0000-0000-060e-2b3401040101", False, False), + "SourceType" : ("04040201-0000-0000-060e-2b3401010101", 0x1603, "02010109-0000-0000-060e-2b3401040101", False, False), + } +), +"TimecodeStream12M" : ("0d010101-0101-1600-060e-2b3402060101", "0d010101-0101-1500-060e-2b3402060101", True, { + "IncludeSync" : ("04040101-0400-0000-060e-2b3401010101", 0x1701, "01040100-0000-0000-060e-2b3401040101", False, False), + } +), +"Transition" : ("0d010101-0101-1700-060e-2b3402060101", "0d010101-0101-0200-060e-2b3402060101", True, { + "OperationGroup" : ("06010104-0205-0000-060e-2b3401010102", 0x1801, "05020500-0000-0000-060e-2b3401040101", False, False), + "CutPoint" : ("07020103-0106-0000-060e-2b3401010102", 0x1802, "01012001-0000-0000-060e-2b3401040101", False, False), + } +), +"ContentStorage" : ("0d010101-0101-1800-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "Mobs" : ("06010104-0501-0000-060e-2b3401010102", 0x1901, "05050700-0000-0000-060e-2b3401040101", False, False), + "EssenceData" : ("06010104-0502-0000-060e-2b3401010102", 0x1902, "05050500-0000-0000-060e-2b3401040101", True, False), + } +), +"ControlPoint" : ("0d010101-0101-1900-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "Value" : ("0530050d-0000-0000-060e-2b3401010102", 0x1a02, "04100300-0000-0000-060e-2b3401040101", False, False), + "Time" : ("07020103-1002-0100-060e-2b3401010102", 0x1a03, "03010100-0000-0000-060e-2b3401040101", False, False), + "EditHint" : ("05300508-0000-0000-060e-2b3401010102", 0x1a04, "02010106-0000-0000-060e-2b3401040101", True, False), + } +), +"DefinitionObject" : ("0d010101-0101-1a00-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + "Identification" : ("01011503-0000-0000-060e-2b3401010102", 0x1b01, "01030100-0000-0000-060e-2b3401040101", False, True), + "Name" : ("01070102-0301-0000-060e-2b3401010102", 0x1b02, "01100200-0000-0000-060e-2b3401040101", False, False), + "Description" : ("03020301-0201-0000-060e-2b3401010102", 0x1b03, "01100200-0000-0000-060e-2b3401040101", True, False), + } +), +"DataDefinition" : ("0d010101-0101-1b00-060e-2b3402060101", "0d010101-0101-1a00-060e-2b3402060101", True, { + } +), +"OperationDefinition" : ("0d010101-0101-1c00-060e-2b3402060101", "0d010101-0101-1a00-060e-2b3402060101", True, { + "DataDefinition" : ("05300509-0000-0000-060e-2b3401010102", 0x1e01, "05010300-0000-0000-060e-2b3401040101", False, False), + "IsTimeWarp" : ("05300503-0000-0000-060e-2b3401010101", 0x1e02, "01040100-0000-0000-060e-2b3401040101", True, False), + "DegradeTo" : ("06010104-0401-0000-060e-2b3401010102", 0x1e03, "05040100-0000-0000-060e-2b3401040101", True, False), + "OperationCategory" : ("0530050a-0000-0000-060e-2b3401010102", 0x1e06, "02020101-0000-0000-060e-2b3401040101", True, False), + "NumberInputs" : ("05300504-0000-0000-060e-2b3401010101", 0x1e07, "01010700-0000-0000-060e-2b3401040101", False, False), + "Bypass" : ("05300505-0000-0000-060e-2b3401010101", 0x1e08, "01010300-0000-0000-060e-2b3401040101", True, False), + "ParametersDefined" : ("06010104-0302-0000-060e-2b3401010102", 0x1e09, "05030e00-0000-0000-060e-2b3401040101", True, False), + } +), +"ParameterDefinition" : ("0d010101-0101-1d00-060e-2b3402060101", "0d010101-0101-1a00-060e-2b3402060101", True, { + "Type" : ("06010104-0106-0000-060e-2b3401010102", 0x1f01, "05010900-0000-0000-060e-2b3401040101", False, False), + "DisplayUnits" : ("0530050b-0100-0000-060e-2b3401010102", 0x1f03, "01100200-0000-0000-060e-2b3401040101", True, False), + } +), +"PluginDefinition" : ("0d010101-0101-1e00-060e-2b3402060101", "0d010101-0101-1a00-060e-2b3402060101", True, { + "PluginCategory" : ("05200901-0000-0000-060e-2b3401010102", 0x2203, "02020103-0000-0000-060e-2b3401040101", False, False), + "VersionNumber" : ("03030301-0300-0000-060e-2b3401010102", 0x2204, "03010300-0000-0000-060e-2b3401040101", False, False), + "VersionString" : ("03030301-0201-0000-060e-2b3401010102", 0x2205, "01100200-0000-0000-060e-2b3401040101", True, False), + "Manufacturer" : ("010a0101-0101-0000-060e-2b3401010102", 0x2206, "01100200-0000-0000-060e-2b3401040101", True, False), + "ManufacturerInfo" : ("06010104-020b-0000-060e-2b3401010102", 0x2207, "05020400-0000-0000-060e-2b3401040101", True, False), + "ManufacturerID" : ("010a0101-0300-0000-060e-2b3401010102", 0x2208, "01030100-0000-0000-060e-2b3401040101", True, False), + "Platform" : ("05200902-0000-0000-060e-2b3401010102", 0x2209, "01030100-0000-0000-060e-2b3401040101", True, False), + "MinPlatformVersion" : ("05200903-0000-0000-060e-2b3401010102", 0x220a, "03010300-0000-0000-060e-2b3401040101", True, False), + "MaxPlatformVersion" : ("05200904-0000-0000-060e-2b3401010102", 0x220b, "03010300-0000-0000-060e-2b3401040101", True, False), + "Engine" : ("05200905-0000-0000-060e-2b3401010102", 0x220c, "01030100-0000-0000-060e-2b3401040101", True, False), + "MinEngineVersion" : ("05200906-0000-0000-060e-2b3401010102", 0x220d, "03010300-0000-0000-060e-2b3401040101", True, False), + "MaxEngineVersion" : ("05200907-0000-0000-060e-2b3401010102", 0x220e, "03010300-0000-0000-060e-2b3401040101", True, False), + "PluginAPI" : ("05200908-0000-0000-060e-2b3401010102", 0x220f, "01030100-0000-0000-060e-2b3401040101", True, False), + "MinPluginAPI" : ("05200909-0000-0000-060e-2b3401010102", 0x2210, "03010300-0000-0000-060e-2b3401040101", True, False), + "MaxPluginAPI" : ("0520090a-0000-0000-060e-2b3401010102", 0x2211, "03010300-0000-0000-060e-2b3401040101", True, False), + "SoftwareOnly" : ("0520090b-0000-0000-060e-2b3401010102", 0x2212, "01040100-0000-0000-060e-2b3401040101", True, False), + "Accelerator" : ("0520090c-0000-0000-060e-2b3401010102", 0x2213, "01040100-0000-0000-060e-2b3401040101", True, False), + "Locators" : ("0520090d-0000-0000-060e-2b3401010102", 0x2214, "05060400-0000-0000-060e-2b3401040101", True, False), + "Authentication" : ("0520090e-0000-0000-060e-2b3401010102", 0x2215, "01040100-0000-0000-060e-2b3401040101", True, False), + "DefinitionObject" : ("0520090f-0000-0000-060e-2b3401010102", 0x2216, "01030100-0000-0000-060e-2b3401040101", True, False), + } +), +"CodecDefinition" : ("0d010101-0101-1f00-060e-2b3402060101", "0d010101-0101-1a00-060e-2b3402060101", True, { + "FileDescriptorClass" : ("06010104-0107-0000-060e-2b3401010102", 0x2301, "05010100-0000-0000-060e-2b3401040101", False, False), + "DataDefinitions" : ("06010104-0301-0000-060e-2b3401010102", 0x2302, "05040300-0000-0000-060e-2b3401040101", False, False), + } +), +"ContainerDefinition" : ("0d010101-0101-2000-060e-2b3402060101", "0d010101-0101-1a00-060e-2b3402060101", True, { + "EssenceIsIdentified" : ("03010201-0300-0000-060e-2b3401010101", 0x2401, "01040100-0000-0000-060e-2b3401040101", True, False), + } +), +"InterpolationDefinition" : ("0d010101-0101-2100-060e-2b3402060101", "0d010101-0101-1a00-060e-2b3402060101", True, { + } +), +"Dictionary" : ("0d010101-0101-2200-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "OperationDefinitions" : ("06010104-0503-0000-060e-2b3401010102", 0x2603, "05050800-0000-0000-060e-2b3401040101", True, False), + "ParameterDefinitions" : ("06010104-0504-0000-060e-2b3401010102", 0x2604, "05050900-0000-0000-060e-2b3401040101", True, False), + "DataDefinitions" : ("06010104-0505-0000-060e-2b3401010102", 0x2605, "05050400-0000-0000-060e-2b3401040101", True, False), + "PluginDefinitions" : ("06010104-0506-0000-060e-2b3401010102", 0x2606, "05050a00-0000-0000-060e-2b3401040101", True, False), + "CodecDefinitions" : ("06010104-0507-0000-060e-2b3401010102", 0x2607, "05050200-0000-0000-060e-2b3401040101", True, False), + "ContainerDefinitions" : ("06010104-0508-0000-060e-2b3401010102", 0x2608, "05050300-0000-0000-060e-2b3401040101", True, False), + "InterpolationDefinitions": ("06010104-0509-0000-060e-2b3401010102", 0x2609, "05050600-0000-0000-060e-2b3401040101", True, False), + "KLVDataDefinitions" : ("06010104-050a-0000-060e-2b3401010107", 0x260a, "05050d00-0000-0000-060e-2b3401040101", True, False), + "TaggedValueDefinitions": ("06010104-050b-0000-060e-2b3401010107", 0x260b, "05050e00-0000-0000-060e-2b3401040101", True, False), + } +), +"EssenceData" : ("0d010101-0101-2300-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "MobID" : ("06010106-0100-0000-060e-2b3401010102", 0x2701, "01030200-0000-0000-060e-2b3401040101", False, True), + "Data" : ("04070200-0000-0000-060e-2b3401010102", 0x2702, "04100200-0000-0000-060e-2b3401040101", False, False), + "SampleIndex" : ("06010102-0100-0000-060e-2b3401010102", 0x2b01, "04100200-0000-0000-060e-2b3401040101", True, False), + } +), +"EssenceDescriptor" : ("0d010101-0101-2400-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + "Locator" : ("06010104-0603-0000-060e-2b3401010102", 0x2f01, "05060400-0000-0000-060e-2b3401040101", True, False), + } +), +"FileDescriptor" : ("0d010101-0101-2500-060e-2b3402060101", "0d010101-0101-2400-060e-2b3402060101", False, { + "SampleRate" : ("04060101-0000-0000-060e-2b3401010101", 0x3001, "03010100-0000-0000-060e-2b3401040101", False, False), + "Length" : ("04060102-0000-0000-060e-2b3401010101", 0x3002, "01012002-0000-0000-060e-2b3401040101", False, False), + "ContainerFormat" : ("06010104-0102-0000-060e-2b3401010102", 0x3004, "05010200-0000-0000-060e-2b3401040101", True, False), + "CodecDefinition" : ("06010104-0103-0000-060e-2b3401010102", 0x3005, "05010b00-0000-0000-060e-2b3401040101", True, False), + "LinkedSlotID" : ("06010103-0500-0000-060e-2b3401010105", 0x3006, "01010300-0000-0000-060e-2b3401040101", True, False), + } +), +"AIFCDescriptor" : ("0d010101-0101-2600-060e-2b3402060101", "0d010101-0101-2500-060e-2b3402060101", True, { + "Summary" : ("03030302-0200-0000-060e-2b3401010102", 0x3101, "04100100-0000-0000-060e-2b3401040101", False, False), + } +), +"DigitalImageDescriptor" : ("0d010101-0101-2700-060e-2b3402060101", "0d010101-0101-2500-060e-2b3402060101", False, { + "Compression" : ("04010601-0000-0000-060e-2b3401010102", 0x3201, "01030100-0000-0000-060e-2b3401040101", True, False), + "StoredHeight" : ("04010502-0100-0000-060e-2b3401010101", 0x3202, "01010300-0000-0000-060e-2b3401040101", False, False), + "StoredWidth" : ("04010502-0200-0000-060e-2b3401010101", 0x3203, "01010300-0000-0000-060e-2b3401040101", False, False), + "SampledHeight" : ("04010501-0700-0000-060e-2b3401010101", 0x3204, "01010300-0000-0000-060e-2b3401040101", True, False), + "SampledWidth" : ("04010501-0800-0000-060e-2b3401010101", 0x3205, "01010300-0000-0000-060e-2b3401040101", True, False), + "SampledXOffset" : ("04010501-0900-0000-060e-2b3401010101", 0x3206, "01010700-0000-0000-060e-2b3401040101", True, False), + "SampledYOffset" : ("04010501-0a00-0000-060e-2b3401010101", 0x3207, "01010700-0000-0000-060e-2b3401040101", True, False), + "DisplayHeight" : ("04010501-0b00-0000-060e-2b3401010101", 0x3208, "01010300-0000-0000-060e-2b3401040101", True, False), + "DisplayWidth" : ("04010501-0c00-0000-060e-2b3401010101", 0x3209, "01010300-0000-0000-060e-2b3401040101", True, False), + "DisplayXOffset" : ("04010501-0d00-0000-060e-2b3401010101", 0x320a, "01010700-0000-0000-060e-2b3401040101", True, False), + "DisplayYOffset" : ("04010501-0e00-0000-060e-2b3401010101", 0x320b, "01010700-0000-0000-060e-2b3401040101", True, False), + "FrameLayout" : ("04010301-0400-0000-060e-2b3401010101", 0x320c, "02010108-0000-0000-060e-2b3401040101", False, False), + "VideoLineMap" : ("04010302-0500-0000-060e-2b3401010102", 0x320d, "04010300-0000-0000-060e-2b3401040101", False, False), + "ImageAspectRatio" : ("04010101-0100-0000-060e-2b3401010101", 0x320e, "03010100-0000-0000-060e-2b3401040101", False, False), + "AlphaTransparency" : ("05200102-0000-0000-060e-2b3401010102", 0x320f, "02010120-0000-0000-060e-2b3401040101", True, False), + "TransferCharacteristic": ("04010201-0101-0200-060e-2b3401010102", 0x3210, "02020102-0000-0000-060e-2b3401040101", True, False), + "ColorPrimaries" : ("04010201-0106-0100-060e-2b3401010109", 0x3219, "02020105-0000-0000-060e-2b3401040101", True, False), + "CodingEquations" : ("04010201-0103-0100-060e-2b3401010102", 0x321a, "02020106-0000-0000-060e-2b3401040101", True, False), + "ImageAlignmentFactor" : ("04180101-0000-0000-060e-2b3401010102", 0x3211, "01010300-0000-0000-060e-2b3401040101", True, False), + "FieldDominance" : ("04010301-0600-0000-060e-2b3401010102", 0x3212, "02010121-0000-0000-060e-2b3401040101", True, False), + "FieldStartOffset" : ("04180102-0000-0000-060e-2b3401010102", 0x3213, "01010300-0000-0000-060e-2b3401040101", True, False), + "FieldEndOffset" : ("04180103-0000-0000-060e-2b3401010102", 0x3214, "01010300-0000-0000-060e-2b3401040101", True, False), + "SignalStandard" : ("04050113-0000-0000-060e-2b3401010105", 0x3215, "02010127-0000-0000-060e-2b3401040101", True, False), + "StoredF2Offset" : ("04010302-0800-0000-060e-2b3401010105", 0x3216, "01010700-0000-0000-060e-2b3401040101", True, False), + "DisplayF2Offset" : ("04010302-0700-0000-060e-2b3401010105", 0x3217, "01010700-0000-0000-060e-2b3401040101", True, False), + "ActiveFormatDescriptor": ("04010302-0900-0000-060e-2b3401010105", 0x3218, "01010100-0000-0000-060e-2b3401040101", True, False), + } +), +"CDCIDescriptor" : ("0d010101-0101-2800-060e-2b3402060101", "0d010101-0101-2700-060e-2b3402060101", True, { + "ComponentWidth" : ("04010503-0a00-0000-060e-2b3401010102", 0x3301, "01010300-0000-0000-060e-2b3401040101", False, False), + "HorizontalSubsampling" : ("04010501-0500-0000-060e-2b3401010101", 0x3302, "01010300-0000-0000-060e-2b3401040101", False, False), + "ColorSiting" : ("04010501-0600-0000-060e-2b3401010101", 0x3303, "02010105-0000-0000-060e-2b3401040101", True, False), + "BlackReferenceLevel" : ("04010503-0300-0000-060e-2b3401010101", 0x3304, "01010300-0000-0000-060e-2b3401040101", True, False), + "WhiteReferenceLevel" : ("04010503-0400-0000-060e-2b3401010101", 0x3305, "01010300-0000-0000-060e-2b3401040101", True, False), + "ColorRange" : ("04010503-0500-0000-060e-2b3401010102", 0x3306, "01010300-0000-0000-060e-2b3401040101", True, False), + "PaddingBits" : ("04180104-0000-0000-060e-2b3401010102", 0x3307, "01010600-0000-0000-060e-2b3401040101", True, False), + "VerticalSubsampling" : ("04010501-1000-0000-060e-2b3401010102", 0x3308, "01010300-0000-0000-060e-2b3401040101", True, False), + "AlphaSamplingWidth" : ("04010503-0700-0000-060e-2b3401010102", 0x3309, "01010300-0000-0000-060e-2b3401040101", True, False), + "ReversedByteOrder" : ("03010201-0a00-0000-060e-2b3401010105", 0x330b, "01040100-0000-0000-060e-2b3401040101", True, False), + } +), +"RGBADescriptor" : ("0d010101-0101-2900-060e-2b3402060101", "0d010101-0101-2700-060e-2b3402060101", True, { + "PixelLayout" : ("04010503-0600-0000-060e-2b3401010102", 0x3401, "04020100-0000-0000-060e-2b3401040101", False, False), + "Palette" : ("04010503-0800-0000-060e-2b3401010102", 0x3403, "04100100-0000-0000-060e-2b3401040101", True, False), + "PaletteLayout" : ("04010503-0900-0000-060e-2b3401010102", 0x3404, "04020100-0000-0000-060e-2b3401040101", True, False), + "ScanningDirection" : ("04010404-0100-0000-060e-2b3401010105", 0x3405, "02010128-0000-0000-060e-2b3401040101", True, False), + "ComponentMaxRef" : ("04010503-0b00-0000-060e-2b3401010105", 0x3406, "01010300-0000-0000-060e-2b3401040101", True, False), + "ComponentMinRef" : ("04010503-0c00-0000-060e-2b3401010105", 0x3407, "01010300-0000-0000-060e-2b3401040101", True, False), + "AlphaMaxRef" : ("04010503-0d00-0000-060e-2b3401010105", 0x3408, "01010300-0000-0000-060e-2b3401040101", True, False), + "AlphaMinRef" : ("04010503-0e00-0000-060e-2b3401010105", 0x3409, "01010300-0000-0000-060e-2b3401040101", True, False), + } +), +"HTMLDescriptor" : ("0d010101-0101-2a00-060e-2b3402060101", "0d010101-0101-2500-060e-2b3402060101", True, { + } +), +"TIFFDescriptor" : ("0d010101-0101-2b00-060e-2b3402060101", "0d010101-0101-2500-060e-2b3402060101", True, { + "IsUniform" : ("05020103-0101-0000-060e-2b3401010102", 0x3701, "01040100-0000-0000-060e-2b3401040101", False, False), + "IsContiguous" : ("06080201-0000-0000-060e-2b3401010101", 0x3702, "01040100-0000-0000-060e-2b3401040101", False, False), + "LeadingLines" : ("04010302-0300-0000-060e-2b3401010101", 0x3703, "01010700-0000-0000-060e-2b3401040101", True, False), + "TrailingLines" : ("04010302-0400-0000-060e-2b3401010101", 0x3704, "01010700-0000-0000-060e-2b3401040101", True, False), + "JPEGTableID" : ("05020103-0102-0000-060e-2b3401010102", 0x3705, "01012003-0000-0000-060e-2b3401040101", True, False), + "Summary" : ("03030302-0300-0000-060e-2b3401010102", 0x3706, "04100100-0000-0000-060e-2b3401040101", False, False), + } +), +"WAVEDescriptor" : ("0d010101-0101-2c00-060e-2b3402060101", "0d010101-0101-2500-060e-2b3402060101", True, { + "Summary" : ("03030302-0100-0000-060e-2b3401010102", 0x3801, "04100100-0000-0000-060e-2b3401040101", False, False), + } +), +"FilmDescriptor" : ("0d010101-0101-2d00-060e-2b3402060101", "0d010101-0101-2400-060e-2b3402060101", True, { + "FilmFormat" : ("04100103-0108-0000-060e-2b3401010102", 0x3901, "0201010d-0000-0000-060e-2b3401040101", True, False), + "FrameRate" : ("04010802-0300-0000-060e-2b3401010102", 0x3902, "01010300-0000-0000-060e-2b3401040101", True, False), + "PerforationsPerFrame" : ("04100103-0103-0000-060e-2b3401010102", 0x3903, "01010100-0000-0000-060e-2b3401040101", True, False), + "FilmAspectRatio" : ("04100103-0203-0000-060e-2b3401010102", 0x3904, "03010100-0000-0000-060e-2b3401040101", True, False), + "Manufacturer" : ("04100103-0106-0100-060e-2b3401010102", 0x3905, "01100200-0000-0000-060e-2b3401040101", True, False), + "Model" : ("04100103-0105-0100-060e-2b3401010102", 0x3906, "01100200-0000-0000-060e-2b3401040101", True, False), + "FilmGaugeFormat" : ("04100103-0104-0100-060e-2b3401010102", 0x3907, "01100200-0000-0000-060e-2b3401040101", True, False), + "FilmBatchNumber" : ("04100103-0107-0100-060e-2b3401010102", 0x3908, "01100200-0000-0000-060e-2b3401040101", True, False), + } +), +"TapeDescriptor" : ("0d010101-0101-2e00-060e-2b3402060101", "0d010101-0101-2400-060e-2b3402060101", True, { + "FormFactor" : ("04100101-0101-0000-060e-2b3401010102", 0x3a01, "02010104-0000-0000-060e-2b3401040101", True, False), + "VideoSignal" : ("04010401-0100-0000-060e-2b3401010102", 0x3a02, "02010103-0000-0000-060e-2b3401040101", True, False), + "TapeFormat" : ("0d010101-0101-0100-060e-2b3401010102", 0x3a03, "02010102-0000-0000-060e-2b3401040101", True, False), + "Length" : ("04100101-0300-0000-060e-2b3401010102", 0x3a04, "01010300-0000-0000-060e-2b3401040101", True, False), + "ManufacturerID" : ("04100101-0401-0000-060e-2b3401010102", 0x3a05, "01100200-0000-0000-060e-2b3401040101", True, False), + "Model" : ("04100101-0201-0000-060e-2b3401010102", 0x3a06, "01100200-0000-0000-060e-2b3401040101", True, False), + "TapeBatchNumber" : ("04100101-0601-0000-060e-2b3401010102", 0x3a07, "01100200-0000-0000-060e-2b3401040101", True, False), + "TapeStock" : ("04100101-0501-0000-060e-2b3401010102", 0x3a08, "01100200-0000-0000-060e-2b3401040101", True, False), + } +), +"Header" : ("0d010101-0101-2f00-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "ByteOrder" : ("03010201-0200-0000-060e-2b3401010101", 0x3b01, "01010600-0000-0000-060e-2b3401040101", False, False), + "LastModified" : ("07020110-0204-0000-060e-2b3401010102", 0x3b02, "03010700-0000-0000-060e-2b3401040101", False, False), + "Content" : ("06010104-0201-0000-060e-2b3401010102", 0x3b03, "05020100-0000-0000-060e-2b3401040101", False, False), + "Dictionary" : ("06010104-0202-0000-060e-2b3401010102", 0x3b04, "05020200-0000-0000-060e-2b3401040101", False, False), + "Version" : ("03010201-0500-0000-060e-2b3401010102", 0x3b05, "03010300-0000-0000-060e-2b3401040101", False, False), + "IdentificationList" : ("06010104-0604-0000-060e-2b3401010102", 0x3b06, "05060300-0000-0000-060e-2b3401040101", False, False), + "ObjectModelVersion" : ("03010201-0400-0000-060e-2b3401010102", 0x3b07, "01010300-0000-0000-060e-2b3401040101", True, False), + "OperationalPattern" : ("01020203-0000-0000-060e-2b3401010105", 0x3b09, "01030100-0000-0000-060e-2b3401040101", True, False), + "EssenceContainers" : ("01020210-0201-0000-060e-2b3401010105", 0x3b0a, "04030100-0000-0000-060e-2b3401040101", True, False), + "DescriptiveSchemes" : ("01020210-0202-0000-060e-2b3401010105", 0x3b0b, "04030100-0000-0000-060e-2b3401040101", True, False), + } +), +"Identification" : ("0d010101-0101-3000-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "CompanyName" : ("05200701-0201-0000-060e-2b3401010102", 0x3c01, "01100200-0000-0000-060e-2b3401040101", False, False), + "ProductName" : ("05200701-0301-0000-060e-2b3401010102", 0x3c02, "01100200-0000-0000-060e-2b3401040101", False, False), + "ProductVersion" : ("05200701-0400-0000-060e-2b3401010102", 0x3c03, "03010200-0000-0000-060e-2b3401040101", True, False), + "ProductVersionString" : ("05200701-0501-0000-060e-2b3401010102", 0x3c04, "01100200-0000-0000-060e-2b3401040101", False, False), + "ProductID" : ("05200701-0700-0000-060e-2b3401010102", 0x3c05, "01030100-0000-0000-060e-2b3401040101", False, False), + "Date" : ("07020110-0203-0000-060e-2b3401010102", 0x3c06, "03010700-0000-0000-060e-2b3401040101", False, False), + "ToolkitVersion" : ("05200701-0a00-0000-060e-2b3401010102", 0x3c07, "03010200-0000-0000-060e-2b3401040101", True, False), + "Platform" : ("05200701-0601-0000-060e-2b3401010102", 0x3c08, "01100200-0000-0000-060e-2b3401040101", True, False), + "GenerationAUID" : ("05200701-0100-0000-060e-2b3401010102", 0x3c09, "01030100-0000-0000-060e-2b3401040101", False, False), + } +), +"Locator" : ("0d010101-0101-3100-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + } +), +"NetworkLocator" : ("0d010101-0101-3200-060e-2b3402060101", "0d010101-0101-3100-060e-2b3402060101", True, { + "URLString" : ("01020101-0100-0000-060e-2b3401010101", 0x4001, "01100200-0000-0000-060e-2b3401040101", False, False), + } +), +"TextLocator" : ("0d010101-0101-3300-060e-2b3402060101", "0d010101-0101-3100-060e-2b3402060101", True, { + "Name" : ("01040102-0100-0000-060e-2b3401010102", 0x4101, "01100200-0000-0000-060e-2b3401040101", False, False), + } +), +"Mob" : ("0d010101-0101-3400-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + "MobID" : ("01011510-0000-0000-060e-2b3401010101", 0x4401, "01030200-0000-0000-060e-2b3401040101", False, True), + "Name" : ("01030302-0100-0000-060e-2b3401010101", 0x4402, "01100200-0000-0000-060e-2b3401040101", True, False), + "Slots" : ("06010104-0605-0000-060e-2b3401010102", 0x4403, "05060500-0000-0000-060e-2b3401040101", False, False), + "LastModified" : ("07020110-0205-0000-060e-2b3401010102", 0x4404, "03010700-0000-0000-060e-2b3401040101", False, False), + "CreationTime" : ("07020110-0103-0000-060e-2b3401010102", 0x4405, "03010700-0000-0000-060e-2b3401040101", False, False), + "UserComments" : ("03020102-0c00-0000-060e-2b3401010102", 0x4406, "05060800-0000-0000-060e-2b3401040101", True, False), + "KLVData" : ("03010210-0300-0000-060e-2b3401010102", 0x4407, "05060900-0000-0000-060e-2b3401040101", True, False), + "Attributes" : ("03010210-0700-0000-060e-2b3401010107", 0x4409, "05060800-0000-0000-060e-2b3401040101", True, False), + "UsageCode" : ("05010108-0000-0000-060e-2b3401010107", 0x4408, "02020104-0000-0000-060e-2b3401040101", True, False), + } +), +"CompositionMob" : ("0d010101-0101-3500-060e-2b3402060101", "0d010101-0101-3400-060e-2b3402060101", True, { + "DefaultFadeLength" : ("07020201-0105-0100-060e-2b3401010102", 0x4501, "01012002-0000-0000-060e-2b3401040101", True, False), + "DefFadeType" : ("05300201-0000-0000-060e-2b3401010101", 0x4502, "02010107-0000-0000-060e-2b3401040101", True, False), + "DefFadeEditUnit" : ("05300403-0000-0000-060e-2b3401010102", 0x4503, "03010100-0000-0000-060e-2b3401040101", True, False), + "Rendering" : ("06010104-010a-0000-060e-2b3401010108", 0x4504, "01030200-0000-0000-060e-2b3401040101", True, False), + } +), +"MasterMob" : ("0d010101-0101-3600-060e-2b3402060101", "0d010101-0101-3400-060e-2b3402060101", True, { + } +), +"SourceMob" : ("0d010101-0101-3700-060e-2b3402060101", "0d010101-0101-3400-060e-2b3402060101", True, { + "EssenceDescription" : ("06010104-0203-0000-060e-2b3401010102", 0x4701, "05020300-0000-0000-060e-2b3401040101", False, False), + } +), +"MobSlot" : ("0d010101-0101-3800-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + "SlotID" : ("01070101-0000-0000-060e-2b3401010102", 0x4801, "01010300-0000-0000-060e-2b3401040101", False, False), + "SlotName" : ("01070102-0100-0000-060e-2b3401010102", 0x4802, "01100200-0000-0000-060e-2b3401040101", True, False), + "Segment" : ("06010104-0204-0000-060e-2b3401010102", 0x4803, "05020600-0000-0000-060e-2b3401040101", False, False), + "PhysicalTrackNumber" : ("01040103-0000-0000-060e-2b3401010102", 0x4804, "01010300-0000-0000-060e-2b3401040101", True, False), + } +), +"EventMobSlot" : ("0d010101-0101-3900-060e-2b3402060101", "0d010101-0101-3800-060e-2b3402060101", True, { + "EditRate" : ("05300402-0000-0000-060e-2b3401010102", 0x4901, "03010100-0000-0000-060e-2b3401040101", False, False), + "EventSlotOrigin" : ("07020103-010b-0000-060e-2b3401010105", 0x4902, "01012001-0000-0000-060e-2b3401040101", True, False), + } +), +"StaticMobSlot" : ("0d010101-0101-3a00-060e-2b3402060101", "0d010101-0101-3800-060e-2b3402060101", True, { + } +), +"TimelineMobSlot" : ("0d010101-0101-3b00-060e-2b3402060101", "0d010101-0101-3800-060e-2b3402060101", True, { + "EditRate" : ("05300405-0000-0000-060e-2b3401010102", 0x4b01, "03010100-0000-0000-060e-2b3401040101", False, False), + "Origin" : ("07020103-0103-0000-060e-2b3401010102", 0x4b02, "01012001-0000-0000-060e-2b3401040101", False, False), + "MarkIn" : ("07020103-010c-0000-060e-2b3401010107", 0x4b03, "01012001-0000-0000-060e-2b3401040101", True, False), + "MarkOut" : ("07020103-0203-0000-060e-2b3401010107", 0x4b04, "01012001-0000-0000-060e-2b3401040101", True, False), + "UserPos" : ("07020103-010d-0000-060e-2b3401010107", 0x4b05, "01012001-0000-0000-060e-2b3401040101", True, False), + } +), +"Parameter" : ("0d010101-0101-3c00-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + "Definition" : ("06010104-0104-0000-060e-2b3401010102", 0x4c01, "01030100-0000-0000-060e-2b3401040101", False, False), + } +), +"ConstantValue" : ("0d010101-0101-3d00-060e-2b3402060101", "0d010101-0101-3c00-060e-2b3402060101", True, { + "Value" : ("05300507-0000-0000-060e-2b3401010102", 0x4d01, "04100300-0000-0000-060e-2b3401040101", False, False), + } +), +"VaryingValue" : ("0d010101-0101-3e00-060e-2b3402060101", "0d010101-0101-3c00-060e-2b3402060101", True, { + "Interpolation" : ("06010104-0105-0000-060e-2b3401010102", 0x4e01, "05010500-0000-0000-060e-2b3401040101", False, False), + "PointList" : ("06010104-0606-0000-060e-2b3401010102", 0x4e02, "05060200-0000-0000-060e-2b3401040101", False, False), + } +), +"TaggedValue" : ("0d010101-0101-3f00-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "Name" : ("03020102-0901-0000-060e-2b3401010102", 0x5001, "01100200-0000-0000-060e-2b3401040101", False, False), + "Value" : ("03020102-0a01-0000-060e-2b3401010102", 0x5003, "04100300-0000-0000-060e-2b3401040101", False, False), + } +), +"KLVData" : ("0d010101-0101-4000-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "Value" : ("03010210-0200-0000-060e-2b3401010102", 0x5101, "04100400-0000-0000-060e-2b3401040101", False, False), + } +), +"DescriptiveMarker" : ("0d010101-0101-4100-060e-2b3402060101", "0d010101-0101-0800-060e-2b3402060101", True, { + "DescribedSlots" : ("01070105-0000-0000-060e-2b3401010104", 0x6102, "04030200-0000-0000-060e-2b3401040101", True, False), + "Description" : ("06010104-020c-0000-060e-2b3401010105", 0x6101, "05021f00-0000-0000-060e-2b3401040101", True, False), + } +), +"SoundDescriptor" : ("0d010101-0101-4200-060e-2b3402060101", "0d010101-0101-2500-060e-2b3402060101", True, { + "AudioSamplingRate" : ("04020301-0101-0000-060e-2b3401010105", 0x3d03, "03010100-0000-0000-060e-2b3401040101", False, False), + "Locked" : ("04020301-0400-0000-060e-2b3401010104", 0x3d02, "01040100-0000-0000-060e-2b3401040101", True, False), + "AudioRefLevel" : ("04020101-0300-0000-060e-2b3401010101", 0x3d04, "01010500-0000-0000-060e-2b3401040101", True, False), + "ElectroSpatial" : ("04020101-0100-0000-060e-2b3401010101", 0x3d05, "02010122-0000-0000-060e-2b3401040101", True, False), + "Channels" : ("04020101-0400-0000-060e-2b3401010105", 0x3d07, "01010300-0000-0000-060e-2b3401040101", False, False), + "QuantizationBits" : ("04020303-0400-0000-060e-2b3401010104", 0x3d01, "01010300-0000-0000-060e-2b3401040101", False, False), + "DialNorm" : ("04020701-0000-0000-060e-2b3401010105", 0x3d0c, "01010500-0000-0000-060e-2b3401040101", True, False), + "Compression" : ("04020402-0000-0000-060e-2b3401010102", 0x3d06, "01030100-0000-0000-060e-2b3401040101", True, False), + } +), +"DataEssenceDescriptor" : ("0d010101-0101-4300-060e-2b3402060101", "0d010101-0101-2500-060e-2b3402060101", True, { + "DataEssenceCoding" : ("04030302-0000-0000-060e-2b3401010103", 0x3e01, "01030100-0000-0000-060e-2b3401040101", True, False), + } +), +"MultipleDescriptor" : ("0d010101-0101-4400-060e-2b3402060101", "0d010101-0101-2500-060e-2b3402060101", True, { + "FileDescriptors" : ("06010104-060b-0000-060e-2b3401010104", 0x3f01, "05060b00-0000-0000-060e-2b3401040101", False, False), + } +), +"DescriptiveClip" : ("0d010101-0101-4500-060e-2b3402060101", "0d010101-0101-1100-060e-2b3402060101", True, { + "DescribedSlotIDs" : ("01070106-0000-0000-060e-2b3401010105", 0x6103, "04030200-0000-0000-060e-2b3401040101", True, False), + } +), +"PCMDescriptor" : ("0d010101-0101-4800-060e-2b3402060101", "0d010101-0101-4200-060e-2b3402060101", True, { + "BlockAlign" : ("04020302-0100-0000-060e-2b3401010105", 0x3d0a, "01010200-0000-0000-060e-2b3401040101", False, False), + "SequenceOffset" : ("04020302-0200-0000-060e-2b3401010105", 0x3d0b, "01010100-0000-0000-060e-2b3401040101", True, False), + "AverageBPS" : ("04020303-0500-0000-060e-2b3401010105", 0x3d09, "01010300-0000-0000-060e-2b3401040101", False, False), + "ChannelAssignment" : ("04020101-0500-0000-060e-2b3401010107", 0x3d32, "01030100-0000-0000-060e-2b3401040101", True, False), + "PeakEnvelopeVersion" : ("04020301-0600-0000-060e-2b3401010108", 0x3d29, "01010300-0000-0000-060e-2b3401040101", True, False), + "PeakEnvelopeFormat" : ("04020301-0700-0000-060e-2b3401010108", 0x3d2a, "01010300-0000-0000-060e-2b3401040101", True, False), + "PointsPerPeakValue" : ("04020301-0800-0000-060e-2b3401010108", 0x3d2b, "01010300-0000-0000-060e-2b3401040101", True, False), + "PeakEnvelopeBlockSize" : ("04020301-0900-0000-060e-2b3401010108", 0x3d2c, "01010300-0000-0000-060e-2b3401040101", True, False), + "PeakChannels" : ("04020301-0a00-0000-060e-2b3401010108", 0x3d2d, "01010300-0000-0000-060e-2b3401040101", True, False), + "PeakFrames" : ("04020301-0b00-0000-060e-2b3401010108", 0x3d2e, "01010300-0000-0000-060e-2b3401040101", True, False), + "PeakOfPeaksPosition" : ("04020301-0c00-0000-060e-2b3401010108", 0x3d2f, "01012001-0000-0000-060e-2b3401040101", True, False), + "PeakEnvelopeTimestamp" : ("04020301-0d00-0000-060e-2b3401010108", 0x3d30, "03010700-0000-0000-060e-2b3401040101", True, False), + "PeakEnvelopeData" : ("04020301-0e00-0000-060e-2b3401010108", 0x3d31, "04100200-0000-0000-060e-2b3401040101", True, False), + } +), +"AES3PCMDescriptor" : ("0d010101-0101-4700-060e-2b3402060101", "0d010101-0101-4800-060e-2b3402060101", True, { + "Emphasis" : ("04020501-0600-0000-060e-2b3401010105", 0x3d0d, "02010123-0000-0000-060e-2b3401040101", True, False), + "BlockStartOffset" : ("04020302-0300-0000-060e-2b3401010105", 0x3d0f, "01010200-0000-0000-060e-2b3401040101", True, False), + "AuxBitsMode" : ("04020501-0100-0000-060e-2b3401010105", 0x3d08, "02010124-0000-0000-060e-2b3401040101", True, False), + "ChannelStatusMode" : ("04020501-0200-0000-060e-2b3401010105", 0x3d10, "04010a00-0000-0000-060e-2b3401040101", True, False), + "FixedChannelStatusData": ("04020501-0300-0000-060e-2b3401010105", 0x3d11, "04010100-0000-0000-060e-2b3401040101", True, False), + "UserDataMode" : ("04020501-0400-0000-060e-2b3401010105", 0x3d12, "04010b00-0000-0000-060e-2b3401040101", True, False), + "FixedUserData" : ("04020501-0500-0000-060e-2b3401010105", 0x3d13, "04010100-0000-0000-060e-2b3401040101", True, False), + } +), +"PhysicalDescriptor" : ("0d010101-0101-4900-060e-2b3402060101", "0d010101-0101-2400-060e-2b3402060101", False, { + } +), +"ImportDescriptor" : ("0d010101-0101-4a00-060e-2b3402060101", "0d010101-0101-4900-060e-2b3402060101", True, { + } +), +"RecordingDescriptor" : ("0d010101-0101-4b00-060e-2b3402060101", "0d010101-0101-4900-060e-2b3402060101", True, { + } +), +"TaggedValueDefinition" : ("0d010101-0101-4c00-060e-2b3402060101", "0d010101-0101-1a00-060e-2b3402060101", True, { + } +), +"KLVDataDefinition" : ("0d010101-0101-4d00-060e-2b3402060101", "0d010101-0101-1a00-060e-2b3402060101", True, { + "KLVDataType" : ("06010104-0109-0000-060e-2b3401010107", 0x4d12, "05010900-0000-0000-060e-2b3401040101", True, False), + } +), +"AuxiliaryDescriptor" : ("0d010101-0101-4e00-060e-2b3402060101", "0d010101-0101-4900-060e-2b3402060101", True, { + "MimeType" : ("04090201-0000-0000-060e-2b3401010107", 0x4e11, "01100200-0000-0000-060e-2b3401040101", False, False), + "CharSet" : ("04090300-0000-0000-060e-2b3401010108", 0x4e12, "01100200-0000-0000-060e-2b3401040101", True, False), + } +), +"RIFFChunk" : ("0d010101-0101-4f00-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "ChunkID" : ("04060802-0000-0000-060e-2b3401010108", 0x4f01, "01010300-0000-0000-060e-2b3401040101", False, False), + "ChunkLength" : ("04060903-0000-0000-060e-2b3401010108", 0x4f02, "01010300-0000-0000-060e-2b3401040101", False, False), + "ChunkData" : ("04070400-0000-0000-060e-2b3401010108", 0x4f03, "04100200-0000-0000-060e-2b3401040101", False, False), + } +), +"BWFImportDescriptor" : ("0d010101-0101-5000-060e-2b3402060101", "0d010101-0101-4a00-060e-2b3402060101", True, { + "QltyFileSecurityReport": ("04020302-0500-0000-060e-2b3401010105", 0x3d15, "01010300-0000-0000-060e-2b3401040101", True, False), + "QltyFileSecurityWave" : ("04020302-0600-0000-060e-2b3401010105", 0x3d16, "01010300-0000-0000-060e-2b3401040101", True, False), + "BextCodingHistory" : ("04020502-0101-0000-060e-2b3401010105", 0x3d21, "01100200-0000-0000-060e-2b3401040101", True, False), + "QltyBasicData" : ("04020502-0201-0000-060e-2b3401010105", 0x3d22, "01100200-0000-0000-060e-2b3401040101", True, False), + "QltyStartOfModulation" : ("04020502-0301-0000-060e-2b3401010105", 0x3d23, "01100200-0000-0000-060e-2b3401040101", True, False), + "QltyQualityEvent" : ("04020502-0401-0000-060e-2b3401010105", 0x3d24, "01100200-0000-0000-060e-2b3401040101", True, False), + "QltyEndOfModulation" : ("04020502-0501-0000-060e-2b3401010105", 0x3d25, "01100200-0000-0000-060e-2b3401040101", True, False), + "QltyQualityParameter" : ("04020502-0601-0000-060e-2b3401010105", 0x3d26, "01100200-0000-0000-060e-2b3401040101", True, False), + "QltyOperatorComment" : ("04020502-0701-0000-060e-2b3401010105", 0x3d27, "01100200-0000-0000-060e-2b3401040101", True, False), + "QltyCueSheet" : ("04020502-0801-0000-060e-2b3401010105", 0x3d28, "01100200-0000-0000-060e-2b3401040101", True, False), + "UnknownBWFChunks" : ("06010104-060f-0000-060e-2b3401010108", 0x3d33, "05060c00-0000-0000-060e-2b3401040101", True, False), + } +), +"MPEGVideoDescriptor" : ("0d010101-0101-5100-060e-2b3402060101", "0d010101-0101-2800-060e-2b3402060101", True, { + "SingleSequence" : ("04010602-0102-0000-060e-2b3401010105", 0xffff, "01040100-0000-0000-060e-2b3401040101", True, False), + "ConstantBPictureCount" : ("04010602-0103-0000-060e-2b3401010105", 0xfffe, "01040100-0000-0000-060e-2b3401040101", True, False), + "CodedContentScanning" : ("04010602-0104-0000-060e-2b3401010105", 0xfffd, "0201012a-0000-0000-060e-2b3401040101", True, False), + "LowDelay" : ("04010602-0105-0000-060e-2b3401010105", 0xfffc, "01040100-0000-0000-060e-2b3401040101", True, False), + "ClosedGOP" : ("04010602-0106-0000-060e-2b3401010105", 0xfffb, "01040100-0000-0000-060e-2b3401040101", True, False), + "IdenticalGOP" : ("04010602-0107-0000-060e-2b3401010105", 0xfffa, "01040100-0000-0000-060e-2b3401040101", True, False), + "MaxGOP" : ("04010602-0108-0000-060e-2b3401010105", 0xfff9, "01010200-0000-0000-060e-2b3401040101", True, False), + "MaxBPictureCount" : ("04010602-0109-0000-060e-2b3401010105", 0xfff8, "01010200-0000-0000-060e-2b3401040101", True, False), + "BitRate" : ("04010602-010b-0000-060e-2b3401010105", 0xfff7, "01010300-0000-0000-060e-2b3401040101", True, False), + "ProfileAndLevel" : ("04010602-010a-0000-060e-2b3401010105", 0xfff6, "01010100-0000-0000-060e-2b3401040101", True, False), + } +), +"MetaDefinition" : ("0d010101-0224-0000-060e-2b3402060101", None, False, { + "Identification" : ("06010107-1300-0000-060e-2b3401010102", 0x0005, "01030100-0000-0000-060e-2b3401040101", False, True), + "Name" : ("03020401-0201-0000-060e-2b3401010102", 0x0006, "01100200-0000-0000-060e-2b3401040101", False, False), + "Description" : ("06010107-1401-0000-060e-2b3401010102", 0x0007, "01100200-0000-0000-060e-2b3401040101", True, False), + } +), +"ClassDefinition" : ("0d010101-0201-0000-060e-2b3402060101", "0d010101-0224-0000-060e-2b3402060101", True, { + "ParentClass" : ("06010107-0100-0000-060e-2b3401010102", 0x0008, "05010100-0000-0000-060e-2b3401040101", False, False), + "Properties" : ("06010107-0200-0000-060e-2b3401010102", 0x0009, "05050b00-0000-0000-060e-2b3401040101", True, False), + "IsConcrete" : ("06010107-0300-0000-060e-2b3401010102", 0x000a, "01040100-0000-0000-060e-2b3401040101", False, False), + } +), +"PropertyDefinition" : ("0d010101-0202-0000-060e-2b3402060101", "0d010101-0224-0000-060e-2b3402060101", True, { + "Type" : ("06010107-0400-0000-060e-2b3401010102", 0x000b, "01030100-0000-0000-060e-2b3401040101", False, False), + "IsOptional" : ("03010202-0100-0000-060e-2b3401010102", 0x000c, "01040100-0000-0000-060e-2b3401040101", False, False), + "LocalIdentification" : ("06010107-0500-0000-060e-2b3401010102", 0x000d, "01010200-0000-0000-060e-2b3401040101", False, False), + "IsUniqueIdentifier" : ("06010107-0600-0000-060e-2b3401010102", 0x000e, "01040100-0000-0000-060e-2b3401040101", True, False), + } +), +"TypeDefinition" : ("0d010101-0203-0000-060e-2b3402060101", "0d010101-0224-0000-060e-2b3402060101", False, { + } +), +"TypeDefinitionInteger" : ("0d010101-0204-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + "Size" : ("03010203-0100-0000-060e-2b3401010102", 0x000f, "01010100-0000-0000-060e-2b3401040101", False, False), + "IsSigned" : ("03010203-0200-0000-060e-2b3401010102", 0x0010, "01040100-0000-0000-060e-2b3401040101", False, False), + } +), +"TypeDefinitionStrongObjectReference" : ("0d010101-0205-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + "ReferencedType" : ("06010107-0900-0000-060e-2b3401010102", 0x0011, "05010100-0000-0000-060e-2b3401040101", False, False), + } +), +"TypeDefinitionWeakObjectReference" : ("0d010101-0206-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + "ReferencedType" : ("06010107-0a00-0000-060e-2b3401010102", 0x0012, "05010100-0000-0000-060e-2b3401040101", False, False), + "TargetSet" : ("03010203-0b00-0000-060e-2b3401010102", 0x0013, "04010600-0000-0000-060e-2b3401040101", False, False), + } +), +"TypeDefinitionEnumeration" : ("0d010101-0207-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + "ElementType" : ("06010107-0b00-0000-060e-2b3401010102", 0x0014, "05010900-0000-0000-060e-2b3401040101", False, False), + "ElementNames" : ("03010203-0400-0000-060e-2b3401010102", 0x0015, "04010500-0000-0000-060e-2b3401040101", False, False), + "ElementValues" : ("03010203-0500-0000-060e-2b3401010102", 0x0016, "04010400-0000-0000-060e-2b3401040101", False, False), + } +), +"TypeDefinitionFixedArray" : ("0d010101-0208-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + "ElementType" : ("06010107-0c00-0000-060e-2b3401010102", 0x0017, "05010900-0000-0000-060e-2b3401040101", False, False), + "ElementCount" : ("03010203-0300-0000-060e-2b3401010102", 0x0018, "01010300-0000-0000-060e-2b3401040101", False, False), + } +), +"TypeDefinitionVariableArray" : ("0d010101-0209-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + "ElementType" : ("06010107-0d00-0000-060e-2b3401010102", 0x0019, "05010900-0000-0000-060e-2b3401040101", False, False), + } +), +"TypeDefinitionSet" : ("0d010101-020a-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + "ElementType" : ("06010107-0e00-0000-060e-2b3401010102", 0x001a, "05010900-0000-0000-060e-2b3401040101", False, False), + } +), +"TypeDefinitionString" : ("0d010101-020b-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + "ElementType" : ("06010107-0f00-0000-060e-2b3401010102", 0x001b, "05010900-0000-0000-060e-2b3401040101", False, False), + } +), +"TypeDefinitionStream" : ("0d010101-020c-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + } +), +"TypeDefinitionRecord" : ("0d010101-020d-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + "MemberTypes" : ("06010107-1100-0000-060e-2b3401010102", 0x001c, "05040200-0000-0000-060e-2b3401040101", False, False), + "MemberNames" : ("03010203-0600-0000-060e-2b3401010102", 0x001d, "04010500-0000-0000-060e-2b3401040101", False, False), + } +), +"TypeDefinitionRename" : ("0d010101-020e-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + "RenamedType" : ("06010107-1200-0000-060e-2b3401010102", 0x001e, "05010900-0000-0000-060e-2b3401040101", False, False), + } +), +"TypeDefinitionExtendibleEnumeration" : ("0d010101-0220-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + "ElementNames" : ("03010203-0700-0000-060e-2b3401010102", 0x001f, "04010500-0000-0000-060e-2b3401040101", False, False), + "ElementValues" : ("03010203-0800-0000-060e-2b3401010102", 0x0020, "04010600-0000-0000-060e-2b3401040101", False, False), + } +), +"TypeDefinitionIndirect" : ("0d010101-0221-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + } +), +"TypeDefinitionOpaque" : ("0d010101-0222-0000-060e-2b3402060101", "0d010101-0221-0000-060e-2b3402060101", True, { + } +), +"TypeDefinitionCharacter" : ("0d010101-0223-0000-060e-2b3402060101", "0d010101-0203-0000-060e-2b3402060101", True, { + } +), +"MetaDictionary" : ("0d010101-0225-0000-060e-2b3402060101", None, True, { + "ClassDefinitions" : ("06010107-0700-0000-060e-2b3401010102", 0x0003, "05050100-0000-0000-060e-2b3401040101", True, False), + "TypeDefinitions" : ("06010107-0800-0000-060e-2b3401010102", 0x0004, "05050c00-0000-0000-060e-2b3401040101", True, False), + } +), +"DescriptiveObject" : ("0d010400-0000-0000-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + } +), +"DescriptiveFramework" : ("0d010401-0000-0000-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + } +)} + +aliases = { +"ClassDef" : "ClassDefinition", +"CodecDef" : "CodecDefinition", +"DataDef" : "DataDefinition", +"DefObject" : "DefinitionObject", +"Edgecode" : "EdgeCode", +"OperationDef" : "OperationDefinition", +"Object" : "InterchangeObject", +"ParameterDef" : "ParameterDefinition", +"InterpolationDef" : "InterpolationDefinition", +"TaggedValueDef" : "TaggedValueDefinition", +"PropertyDef" : "PropertyDefinition", +"TypeDef" : "TypeDefinition", +"TypeDefCharacter" : "TypeDefinitionCharacter", +"TypeDefEnum" : "TypeDefinitionEnumeration", +"TypeDefExtEnum" : "TypeDefinitionExtendibleEnumeration", +"TypeDefFixedArray" : "TypeDefinitionFixedArray", +"TypeDefInt" : "TypeDefinitionInteger", +"TypeDefRecord" : "TypeDefinitionRecord", +"TypeDefRename" : "TypeDefinitionRename", +"TypeDefSet" : "TypeDefinitionSet", +"TypeDefStream" : "TypeDefinitionStream", +"TypeDefString" : "TypeDefinitionString", +"TypeDefIndirect" : "TypeDefinitionIndirect", +"TypeDefOpaque" : "TypeDefinitionOpaque", +"TypeDefStrongObjRef" : "TypeDefinitionStrongObjectReference", +"TypeDefVariableArray" : "TypeDefinitionVariableArray", +"TypeDefWeakObjRef" : "TypeDefinitionWeakObjectReference", +"ContainerDef" : "ContainerDefinition", +"PluginDef" : "PluginDefinition", +} diff --git a/aaf2/model/datadefs.py b/aaf2/model/datadefs.py new file mode 100644 index 0000000..913994f --- /dev/null +++ b/aaf2/model/datadefs.py @@ -0,0 +1,305 @@ +OperationDefs = { +"0c3bea40-fc05-11d2-8a29-0050040ef7d2" : ("OperationDef_VideoDissolve", ""), +"0c3bea44-fc05-11d2-8a29-0050040ef7d2" : ("OperationDef_SMPTEVideoWipe", ""), +"9d2ea890-0968-11d3-8a38-0050040ef7d2" : ("OperationDef_VideoSpeedControl", ""), +"9d2ea891-0968-11d3-8a38-0050040ef7d2" : ("OperationDef_VideoRepeat", ""), +"f1db0f32-8d64-11d3-80df-006008143e6f" : ("OperationDef_Flip", ""), +"f1db0f34-8d64-11d3-80df-006008143e6f" : ("OperationDef_Flop", ""), +"f1db0f33-8d64-11d3-80df-006008143e6f" : ("OperationDef_FlipFlop", ""), +"86f5711e-ee72-450c-a118-17cf3b175dff" : ("OperationDef_VideoPosition", ""), +"f5826680-26c5-4149-8554-43d3c7a3bc09" : ("OperationDef_VideoCrop", ""), +"2e0a119d-e6f7-4bee-b5dc-6dd42988687e" : ("OperationDef_VideoScale", ""), +"f2ca330d-8d45-4db4-b1b5-136ab055586f" : ("OperationDef_VideoRotate", ""), +"21d5c51a-8acb-46d5-9392-5cae640c8836" : ("OperationDef_VideoCornerPinning", ""), +"14db900e-d537-49f6-889b-012568fcc234" : ("OperationDef_VideoAlphaWithinVideoKey", ""), +"e599cb0f-ba5f-4192-9356-51eb19c08589" : ("OperationDef_VideoSeparateAlphaKey", ""), +"38ff7903-69e5-476b-be5a-eafc2000f011" : ("OperationDef_VideoLuminanceKey", ""), +"30a315c2-71e5-4e82-a4ef-0513ee056b65" : ("OperationDef_VideoChromaKey", ""), +"9d2ea894-0968-11d3-8a38-0050040ef7d2" : ("OperationDef_MonoAudioGain", ""), +"9d2ea893-0968-11d3-8a38-0050040ef7d2" : ("OperationDef_MonoAudioPan", ""), +"0c3bea41-fc05-11d2-8a29-0050040ef7d2" : ("OperationDef_MonoAudioDissolve", ""), +"2311bd90-b5da-4285-aa3a-8552848779b3" : ("OperationDef_TwoParameterMonoAudioDissolve", ""), +"9bb90dfd-2aad-49af-b09c-8ba6cd5281d1" : ("OperationDef_VideoOpacity", ""), +"2c50831c-572e-4042-b1dd-55ed0b7c49df" : ("OperationDef_VideoTitle", ""), +"5aba98f8-f389-471f-8fee-dfde7ec7f9bb" : ("OperationDef_VideoColor", ""), +"1575e350-fca3-11d2-8a2a-0050040ef7d2" : ("OperationDef_Unknown", ""), +"0c3bea43-fc05-11d2-8a29-0050040ef7d2" : ("OperationDef_VideoFadeToBlack", ""), +"0a3c75e0-fd82-11d2-8a2b-0050040ef7d2" : ("OperationDef_PictureWithMate", ""), +"9d2ea892-0968-11d3-8a38-0050040ef7d2" : ("OperationDef_VideoFrameToMask", ""), +"0c3bea42-fc05-11d2-8a29-0050040ef7d2" : ("OperationDef_StereoAudioDissolve", ""), +"9d2ea895-0968-11d3-8a38-0050040ef7d2" : ("OperationDef_StereoAudioGain", ""), +"8d896ad0-2261-11d3-8a4c-0050040ef7d2" : ("OperationDef_MonoAudioMixdown", ""), +} + +ParameterDefs = { +"e4962320-2267-11d3-8a4c-0050040ef7d2" : ("ParameterDef_Level", ""), +"e4962323-2267-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTEWipeNumber", ""), +"9c894ba0-2277-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTEReverse", ""), +"72559a80-24d7-11d3-8a50-0050040ef7d2" : ("ParameterDef_SpeedRatio", ""), +"c573a510-071a-454f-b617-ad6ae69054c2" : ("ParameterDef_PositionOffsetX", ""), +"82e27478-1336-4ea3-bcb9-6b8f17864c42" : ("ParameterDef_PositionOffsetY", ""), +"d47b3377-318c-4657-a9d8-75811b6dc3d1" : ("ParameterDef_CropLeft", ""), +"5ecc9dd5-21c1-462b-9fec-c2bd85f14033" : ("ParameterDef_CropRight", ""), +"8170a539-9b55-4051-9d4e-46598d01b914" : ("ParameterDef_CropTop", ""), +"154ba82b-990a-4c80-9101-3037e28839a1" : ("ParameterDef_CropBottom", ""), +"8d568129-847e-11d5-935a-50f857c10000" : ("ParameterDef_ScaleX", ""), +"8d56812a-847e-11d5-935a-50f857c10000" : ("ParameterDef_ScaleY", ""), +"062cfbd8-f4b1-4a50-b944-f39e2fc73c17" : ("ParameterDef_Rotation", ""), +"72a3b4a2-873d-4733-9052-9f83a706ca5b" : ("ParameterDef_PinTopLeftX", ""), +"29e4d78f-a502-4ebb-8c07-ed5a0320c1b0" : ("ParameterDef_PinTopLeftY", ""), +"a95296c0-1ed9-4925-8481-2096c72e818d" : ("ParameterDef_PinTopRightX", ""), +"ce1757ae-7a0b-45d9-b3f3-3686adff1e2d" : ("ParameterDef_PinTopRightY", ""), +"08b2bc81-9b1b-4c01-ba73-bba3554ed029" : ("ParameterDef_PinBottomLeftX", ""), +"c163f2ff-cd83-4655-826e-3724ab7fa092" : ("ParameterDef_PinBottomLeftY", ""), +"53bc5884-897f-479e-b833-191f8692100d" : ("ParameterDef_PinBottomRightX", ""), +"812fb15b-0b95-4406-878d-efaa1cffc129" : ("ParameterDef_PinBottomRightY", ""), +"a2667f65-65d8-4abf-a179-0b9b93413949" : ("ParameterDef_AlphaKeyInvertAlpha", ""), +"21ed5b0f-b7a0-43bc-b779-c47f85bf6c4d" : ("ParameterDef_LumKeyLevel", ""), +"cbd39b25-3ece-441e-ba2c-da473ab5cc7c" : ("ParameterDef_LumKeyClip", ""), +"e4962321-2267-11d3-8a4c-0050040ef7d2" : ("ParameterDef_Amplitude", ""), +"e4962322-2267-11d3-8a4c-0050040ef7d2" : ("ParameterDef_Pan", ""), +"9e610007-1be2-41e1-bb11-c95de9964d03" : ("ParameterDef_OutgoingLevel", ""), +"48cea642-a8f9-455b-82b3-86c814b797c7" : ("ParameterDef_IncomingLevel", ""), +"cb7c0ec4-f45f-4ee6-aef0-c63ddb134924" : ("ParameterDef_OpacityLevel", ""), +"7b92827b-5ae3-465e-b5f9-5ee21b070859" : ("ParameterDef_TitleText", ""), +"e8eb7f50-602f-4a2f-8fb2-86c8826ccf24" : ("ParameterDef_TitleFontName", ""), +"01c55287-31b3-4f8f-bb87-c92f06eb7f5a" : ("ParameterDef_TitleFontSize", ""), +"dfe86f24-8a71-4dc5-83a2-988f583af711" : ("ParameterDef_TitleFontColorR", ""), +"f9f41222-36d9-4650-bd5a-a17866cf86b9" : ("ParameterDef_TitleFontColorG", ""), +"f5ba87fa-cf72-4f37-a736-d7096fcb06f1" : ("ParameterDef_TitleFontColorB", ""), +"47c1733f-6afb-4168-9b6d-476adfbae7ab" : ("ParameterDef_TitleAlignment", ""), +"8b5732c0-be8e-4332-aa71-5d866add777d" : ("ParameterDef_TitleBold", ""), +"e4a3c91b-f96a-4dd4-91d8-1ba32000ab72" : ("ParameterDef_TitleItalic", ""), +"a25061da-db25-402e-89ff-a6d0efa39444" : ("ParameterDef_TitlePositionX", ""), +"6151541f-9d3f-4a0e-a3f9-24cc60eea969" : ("ParameterDef_TitlePositionY", ""), +"be2033da-723b-4146-ace0-3299e0ff342e" : ("ParameterDef_ColorSlopeR", ""), +"7ca8e01b-c6d8-4b3f-b251-28a53e5b958f" : ("ParameterDef_ColorSlopeG", ""), +"1aeb007b-3cd5-4814-87b5-cbd6a3cdfe8d" : ("ParameterDef_ColorSlopeB", ""), +"4d1e65e0-85fc-4bb9-a264-13cf320a8539" : ("ParameterDef_ColorOffsetR", ""), +"76f783e4-0bbd-41d7-b01e-f418c1602a6f" : ("ParameterDef_ColorOffsetG", ""), +"57110628-522d-4b48-8a28-75477ced984d" : ("ParameterDef_ColorOffsetB", ""), +"c2d79c3a-9263-40d9-827d-953ac6b88813" : ("ParameterDef_ColorPowerR", ""), +"524d52e6-86a3-4f41-864b-fb53b15b1d5d" : ("ParameterDef_ColorPowerG", ""), +"5f0cc7dc-907d-4153-bf00-1f3cdf3c05bb" : ("ParameterDef_ColorPowerB", ""), +"0b135705-3312-4d03-ba89-be9ef45e5470" : ("ParameterDef_ColorSaturation", ""), +"f3b9466a-2579-4168-beb5-66b996919a3f" : ("ParameterDef_ColorCorrectionDescription", ""), +"b0124dbe-7f97-443c-ae39-c49c1c53d728" : ("ParameterDef_ColorInputDescription", ""), +"5a9dfc6f-611f-4db8-8eff-3b9cdb6e1220" : ("ParameterDef_ColorViewingDescription", ""), +"9c894ba1-2277-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTESoft", ""), +"9c894ba2-2277-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTEBorder", ""), +"9c894ba3-2277-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTEPosition", ""), +"9c894ba4-2277-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTEModulator", ""), +"9c894ba5-2277-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTEShadow", ""), +"9c894ba6-2277-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTETumble", ""), +"9c894ba7-2277-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTESpotlight", ""), +"9c894ba8-2277-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTEReplicationH", ""), +"9c894ba9-2277-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTEReplicationV", ""), +"9c894baa-2277-11d3-8a4c-0050040ef7d2" : ("ParameterDef_SMPTECheckerboard", ""), +"5f1c2560-2415-11d3-8a4f-0050040ef7d2" : ("ParameterDef_PhaseOffset", ""), +} + +DataDefs = { +"01030202-0100-0000-060e-2b3404010101" : ("DataDef_Picture", "Picture data"), +"01030202-0200-0000-060e-2b3404010101" : ("DataDef_Sound", "Sound data"), +"01030202-0300-0000-060e-2b3404010101" : ("DataDef_Data", "Data"), +"6f3c8ce1-6cef-11d2-807d-006008143e6f" : ("DataDef_LegacyPicture", "Picture data (legacy)"), +"05cba731-1daa-11d3-80ad-006008143e6f" : ("DataDef_Matte", "Matte data"), +"05cba732-1daa-11d3-80ad-006008143e6f" : ("DataDef_PictureWithMatte", "Picture and Matte data"), +"78e1ebe1-6cef-11d2-807d-006008143e6f" : ("DataDef_LegacySound", "Sound data (legacy)"), +"01030201-0100-0000-060e-2b3404010101" : ("DataDef_Timecode", "Timecode data"), +"7f275e81-77e5-11d2-807f-006008143e6f" : ("DataDef_LegacyTimecode", "Timecode data (legacy)"), +"d2bb2af0-d234-11d2-89ee-006097116212" : ("DataDef_Edgecode", "Edgecode data"), +"01030201-1000-0000-060e-2b3404010101" : ("DataDef_DescriptiveMetadata", "Descriptive metadata"), +"01030203-0100-0000-060e-2b3404010105" : ("DataDef_Auxiliary", "Auxiliary data"), +"851419d0-2e4f-11d3-8a5b-0050040ef7d2" : ("DataDef_Unknown", "Data kind not known"), +} + +ContainerDefs = { +"4313b572-d8ba-11d2-809b-006008143e6f" : ("ContainerDef_External", ""), +"4b1c1a46-03f2-11d4-80fb-006008143e6f" : ("ContainerDef_OMF", ""), +"4313b571-d8ba-11d2-809b-006008143e6f" : ("ContainerDef_AAF", ""), +"42464141-000d-4d4f-060e-2b34010101ff" : ("ContainerDef_AAFMSS", ""), +"4b464141-000d-4d4f-060e-2b34010101ff" : ("ContainerDef_AAFKLV", ""), +"58464141-000d-4d4f-060e-2b34010101ff" : ("ContainerDef_AAFXML", ""), +"0d010301-0201-0101-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_625x50I_50Mbps_DefinedTemplate", ""), +"0d010301-0201-0102-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_625x50I_50Mbps_ExtendedTemplate", ""), +"0d010301-0201-017f-060e-2b3404010102" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_625x50I_50Mbps_PictureOnly", ""), +"0d010301-0201-0201-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_525x5994I_50Mbps_DefinedTemplate", ""), +"0d010301-0201-0202-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_525x5994I_50Mbps_ExtendedTemplate", ""), +"0d010301-0201-027f-060e-2b3404010102" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_525x5994I_50Mbps_PictureOnly", ""), +"0d010301-0201-0301-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_625x50I_40Mbps_DefinedTemplate", ""), +"0d010301-0201-0302-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_625x50I_40Mbps_ExtendedTemplate", ""), +"0d010301-0201-037f-060e-2b3404010102" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_625x50I_40Mbps_PictureOnly", ""), +"0d010301-0201-0401-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_525x5994I_40Mbps_DefinedTemplate", ""), +"0d010301-0201-0402-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_525x5994I_40Mbps_ExtendedTemplate", ""), +"0d010301-0201-047f-060e-2b3404010102" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_525x5994I_40Mbps_PictureOnly", ""), +"0d010301-0201-0501-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_625x50I_30Mbps_DefinedTemplate", ""), +"0d010301-0201-0502-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_625x50I_30Mbps_ExtendedTemplate", ""), +"0d010301-0201-057f-060e-2b3404010102" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_625x50I_30Mbps_PictureOnly", ""), +"0d010301-0201-0601-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_525x5994I_30Mbps_DefinedTemplate", ""), +"0d010301-0201-0602-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_525x5994I_30Mbps_ExtendedTemplate", ""), +"0d010301-0201-067f-060e-2b3404010102" : ("ContainerDef_MXFGC_Framewrapped_SMPTE_D10_525x5994I_30Mbps_PictureOnly", ""), +"0d010301-0202-0101-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_IECDV_525x5994I_25Mbps", ""), +"0d010301-0202-0102-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_IECDV_525x5994I_25Mbps", ""), +"0d010301-0202-0201-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_IECDV_625x50I_25Mbps", ""), +"0d010301-0202-0202-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_IECDV_625x50I_25Mbps", ""), +"0d010301-0202-0301-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_IECDV_525x5994I_25Mbps_SMPTE322M", ""), +"0d010301-0202-0302-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_IECDV_525x5994I_25Mbps_SMPTE322M", ""), +"0d010301-0202-0401-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_IECDV_625x50I_25Mbps_SMPTE322M", ""), +"0d010301-0202-0402-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_IECDV_625x50I_25Mbps_SMPTE322M", ""), +"0d010301-0202-3f01-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_IECDV_UndefinedSource_25Mbps", ""), +"0d010301-0202-3f02-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_IECDV_UndefinedSource_25Mbps", ""), +"0d010301-0202-4001-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_DVbased_525x5994I_25Mbps", ""), +"0d010301-0202-4002-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_DVbased_525x5994I_25Mbps", ""), +"0d010301-0202-4101-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_DVbased_625x50I_25Mbps", ""), +"0d010301-0202-4102-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_DVbased_625x50I_25Mbps", ""), +"0d010301-0202-5001-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_DVbased_525x5994I_50Mbps", ""), +"0d010301-0202-5002-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_DVbased_525x5994I_50Mbps", ""), +"0d010301-0202-5101-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_DVbased_625x50I_50Mbps", ""), +"0d010301-0202-5102-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_DVbased_625x50I_50Mbps", ""), +"0d010301-0202-6001-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_DVbased_1080x5994I_100Mbps", ""), +"0d010301-0202-6002-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_DVbased_1080x5994I_100Mbps", ""), +"0d010301-0202-6101-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_DVbased_1080x50I_100Mbps", ""), +"0d010301-0202-6102-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_DVbased_1080x50I_100Mbps", ""), +"0d010301-0202-6201-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_DVbased_720x5994P_100Mbps", ""), +"0d010301-0202-6202-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_DVbased_720x5994P_100Mbps", ""), +"0d010301-0202-6301-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_DVbased_720x50P_100Mbps", ""), +"0d010301-0202-6302-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_DVbased_720x50P_100Mbps", ""), +"0d010301-0202-7f01-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_DVbased_UndefinedSource", ""), +"0d010301-0202-7f02-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_DVbased_UndefinedSource", ""), +"0d010301-0204-6001-060e-2b3404010102" : ("ContainerDef_MXFGC_Framewrapped_MPEGES_VideoStream0_SID", ""), +"0d010301-0204-6107-060e-2b3404010102" : ("ContainerDef_MXFGC_CustomClosedGOPwrapped_MPEGES_VideoStream1_SID", ""), +"0d010301-0205-0101-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_Uncompressed_525x5994I_720_422", ""), +"0d010301-0205-0102-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_Uncompressed_525x5994I_720_422", ""), +"0d010301-0205-0103-060e-2b3404010101" : ("ContainerDef_MXFGC_Linewrapped_Uncompressed_525x5994I_720_422", ""), +"0d010301-0205-0105-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_Uncompressed_625x50I_720_422", ""), +"0d010301-0205-0106-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_Uncompressed_625x50I_720_422", ""), +"0d010301-0205-0107-060e-2b3404010101" : ("ContainerDef_MXFGC_Linewrapped_Uncompressed_625x50I_720_422", ""), +"0d010301-0205-0119-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_Uncompressed_525x5994P_960_422", ""), +"0d010301-0205-011a-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_Uncompressed_525x5994P_960_422", ""), +"0d010301-0205-011b-060e-2b3404010101" : ("ContainerDef_MXFGC_Linewrapped_Uncompressed_525x5994P_960_422", ""), +"0d010301-0205-011d-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_Uncompressed_625x50P_960_422", ""), +"0d010301-0205-011e-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_Uncompressed_625x50P_960_422", ""), +"0d010301-0205-011f-060e-2b3404010101" : ("ContainerDef_MXFGC_Linewrapped_Uncompressed_625x50P_960_422", ""), +"0d010301-0206-0100-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_Broadcast_Wave_audio_data", ""), +"0d010301-0206-0200-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_Broadcast_Wave_audio_data", ""), +"0d010301-0206-0300-060e-2b3404010101" : ("ContainerDef_MXFGC_Framewrapped_AES3_audio_data", ""), +"0d010301-0206-0400-060e-2b3404010101" : ("ContainerDef_MXFGC_Clipwrapped_AES3_audio_data", ""), +"0d010301-020a-0100-060e-2b3404010103" : ("ContainerDef_MXFGC_Framewrapped_Alaw_Audio", ""), +"0d010301-020a-0200-060e-2b3404010103" : ("ContainerDef_MXFGC_Clipwrapped_Alaw_Audio", ""), +"0d010301-020a-0300-060e-2b3404010103" : ("ContainerDef_MXFGC_Customwrapped_Alaw_Audio", ""), +"0d010301-0210-6002-060e-2b340401010a" : ("ContainerDef_MXFGC_Clipwrapped_AVCByteStream_VideoStream0_SID", ""), +"0d010301-0211-0100-060e-2b340401010a" : ("ContainerDef_MXFGC_Framewrapped_VC3", ""), +"0d010301-0211-0200-060e-2b340401010a" : ("ContainerDef_MXFGC_Clipwrapped_VC3", ""), +"0d010301-0212-0100-060e-2b340401010a" : ("ContainerDef_MXFGC_Framewrapped_VC1", ""), +"0d010301-0212-0200-060e-2b340401010a" : ("ContainerDef_MXFGC_Clipwrapped_VC1", ""), +"0d010301-027f-0100-060e-2b3404010103" : ("ContainerDef_MXFGC_Generic_Essence_Multiple_Mappings", ""), +"0d011301-0101-0100-060e-2b3404010106" : ("ContainerDef_RIFFWAVE", ""), +"0d011301-0102-0200-060e-2b3404010107" : ("ContainerDef_JFIF", ""), +"0d011301-0104-0100-060e-2b3404010106" : ("ContainerDef_AIFFAIFC", ""), +"0e040301-0206-0101-060e-2b3404010101" : ("ContainerDef_MXFGC_Avid_DNX_220X_1080p", ""), +"0e040301-0206-0102-060e-2b3404010101" : ("ContainerDef_MXFGC_Avid_DNX_145_1080p", ""), +"0e040301-0206-0103-060e-2b3404010101" : ("ContainerDef_MXFGC_Avid_DNX_220_1080p", ""), +"0e040301-0206-0104-060e-2b3404010101" : ("ContainerDef_MXFGC_Avid_DNX_36_1080p", ""), +"0e040301-0206-0201-060e-2b3404010101" : ("ContainerDef_MXFGC_Avid_DNX_220X_1080i", ""), +"0e040301-0206-0202-060e-2b3404010101" : ("ContainerDef_MXFGC_Avid_DNX_145_1080i", ""), +"0e040301-0206-0203-060e-2b3404010101" : ("ContainerDef_MXFGC_Avid_DNX_220_1080i", ""), +"0e040301-0206-0204-060e-2b3404010101" : ("ContainerDef_MXFGC_Avid_DNX_145_1440_1080i", ""), +"0e040301-0206-0301-060e-2b3404010101" : ("ContainerDef_MXFGC_Avid_DNX_220X_720p", ""), +"0e040301-0206-0302-060e-2b3404010101" : ("ContainerDef_MXFGC_Avid_DNX_220_720p", ""), +"0e040301-0206-0303-060e-2b3404010101" : ("ContainerDef_MXFGC_Avid_DNX_145_720p", ""), +} + +InterpolationDefs = { +"5b6c85a3-0ede-11d3-80a9-006008143e6f" : ("InterpolationDef_None", ""), +"5b6c85a4-0ede-11d3-80a9-006008143e6f" : ("InterpolationDef_Linear", ""), +"5b6c85a5-0ede-11d3-80a9-006008143e6f" : ("InterpolationDef_Constant", ""), +"5b6c85a6-0ede-11d3-80a9-006008143e6f" : ("InterpolationDef_BSpline", ""), +"15829ec3-1f24-458a-960d-c65bb23c2aa1" : ("InterpolationDef_Log", ""), +"c09153f7-bd18-4e5a-ad09-cbdd654fa001" : ("InterpolationDef_Power", ""), +} + +OPDefs = { +"0d011201-0100-0000-060e-2b3404010105" : ("OPDef_EditProtocol", "Operational Pattern for the AAF Edit Protocol"), +"0d011201-0200-0000-060e-2b3404010109" : ("OPDef_Unconstrained", "Unconstrained by an Operational Pattern"), +} + +KLVDataDefs = { +} + +TaggedValueDefs = { +} + +PluginDefs = { +"3d1dd891-e793-11d2-809e-006008143e6f" : ("Platform_Independent", ""), +"9fdef8c1-e847-11d2-809e-006008143e6f" : ("Engine_None", ""), +"69c870a1-e793-11d2-809e-006008143e6f" : ("PluginAPI_EssenceAccess", ""), +"56905e0b-537d-11d4-a36c-009027dfca6a" : ("PluginCategory_Codec", ""), +} + +CodecDefs = { +"568fb761-9458-11d2-8089-006008143e6f" : ("CodecDef_None", ""), +"90ac17c8-e3e2-4596-9e9e-a6dd1c70c892" : ("CodecDef_PCM", ""), +"820f09b1-eb9b-11d2-809f-006008143e6f" : ("CodecDef_WAVE", ""), +"4b1c1a45-03f2-11d4-80fb-006008143e6f" : ("CodecDef_AIFC", ""), +"18634f8c-3bab-11d3-bfd6-00104bc9156d" : ("CodecDef_JPEG", ""), +"4e84045e-0f29-11d4-a359-009027dfca6a" : ("CodecDef_CDCI", ""), +"4e84045f-0f29-11d4-a359-009027dfca6a" : ("CodecDef_RGBA", ""), +"6c2a61c2-e7a2-46ee-8d90-6a1d06e15f41" : ("CodecDef_VC3", ""), +"8ef593f6-9521-4344-9ede-b84e8cfdc7da" : ("CodecDef_DNxHD", "", "CDCIDescriptor", ("DataDef_Picture", "DataDef_LegacyPicture")), +"1b31f3b1-9450-11d2-8089-006008143e6f" : ("CodecFlavour_None", ""), +"af4de587-23d7-4c8a-b37b-c1c13870e711" : ("CodecFlavour_DV_Based_100Mbps_1080x50I", ""), +"af4de587-23d7-4c8b-b37b-c1c13870e711" : ("CodecFlavour_DV_Based_100Mbps_1080x5994I", ""), +"af4de587-23d7-4c8c-b37b-c1c13870e711" : ("CodecFlavour_DV_Based_100Mbps_720x50P", ""), +"af4de587-23d7-4c8d-b37b-c1c13870e711" : ("CodecFlavour_DV_Based_100Mbps_720x5994P", ""), +"af4de587-23d7-4c80-b37b-c1c13870e711" : ("CodecFlavour_DV_Based_25Mbps_525_60", ""), +"af4de587-23d7-4c81-b37b-c1c13870e711" : ("CodecFlavour_DV_Based_25Mbps_625_50", ""), +"af4de587-23d7-4c82-b37b-c1c13870e711" : ("CodecFlavour_DV_Based_50Mbps_525_60", ""), +"af4de587-23d7-4c83-b37b-c1c13870e711" : ("CodecFlavour_DV_Based_50Mbps_625_50", ""), +"af4de587-23d7-4c7f-b37b-c1c13870e711" : ("CodecFlavour_IEC_DV_525_60", ""), +"af4de587-23d7-4c7e-b37b-c1c13870e711" : ("CodecFlavour_IEC_DV_625_50", ""), +"af4de587-23d7-4c7d-b37b-c1c13870e711" : ("CodecFlavour_LegacyDV_525_60", ""), +"af4de587-23d7-4c7c-b37b-c1c13870e711" : ("CodecFlavour_LegacyDV_625_50", ""), +"af4de587-23d7-4c84-b37b-c1c13870e711" : ("CodecFlavour_SMPTE_D10_50Mbps_625x50I", ""), +"af4de587-23d7-4c85-b37b-c1c13870e711" : ("CodecFlavour_SMPTE_D10_50Mbps_525x5994I", ""), +"af4de587-23d7-4c86-b37b-c1c13870e711" : ("CodecFlavour_SMPTE_D10_40Mbps_625x50I", ""), +"af4de587-23d7-4c87-b37b-c1c13870e711" : ("CodecFlavour_SMPTE_D10_40Mbps_525x5994I", ""), +"af4de587-23d7-4c88-b37b-c1c13870e711" : ("CodecFlavour_SMPTE_D10_30Mbps_625x50I", ""), +"af4de587-23d7-4c89-b37b-c1c13870e711" : ("CodecFlavour_SMPTE_D10_30Mbps_525x5994I", ""), +"effdb6b4-fe99-4768-88fe-3422a5762961" : ("CodecFlavour_VC3_1235", ""), +"21b15f27-2781-4656-aa1b-dc5e63862738" : ("CodecFlavour_VC3_1237", ""), +"62f37363-b1d1-4fa0-9fb7-6e7044371396" : ("CodecFlavour_VC3_1238", ""), +"1e9b855a-323e-4999-b0fa-8444267a63a7" : ("CodecFlavour_VC3_1241", ""), +"8b4c29cf-b255-4ef0-bf79-b5b616479238" : ("CodecFlavour_VC3_1242", ""), +"e063fd16-6a70-4128-936d-ac776f2630cf" : ("CodecFlavour_VC3_1243", ""), +"c80d0143-be86-45fd-aacc-7f612b4b9139" : ("CodecFlavour_VC3_1244", ""), +"47eb10b5-72fa-4dbb-9801-e0fe9ab8d9f0" : ("CodecFlavour_VC3_1250", ""), +"26cf3984-c716-4315-9de7-9228b5c0f922" : ("CodecFlavour_VC3_1251", ""), +"0909cf52-475a-4abc-9e13-0ddb9d60d16c" : ("CodecFlavour_VC3_1252", ""), +"7f5d77dd-5402-45e0-9128-038016f55406" : ("CodecFlavour_VC3_1253", ""), +"a362d3cb-dcef-4ffb-bb35-be72a16561ce" : ("CodecFlavour_VC3_1254", ""), +} + +CompressionDefs = { +"edb35383-6d30-11d3-a036-006094eb75cb" : ("CompressionDef_AAF_CMPR_FULL_JPEG", ""), +"edb35391-6d30-11d3-a036-006094eb75cb" : ("CompressionDef_AAF_CMPR_AUNC422", ""), +"edb35390-6d30-11d3-a036-006094eb75cb" : ("CompressionDef_LegacyDV", ""), +"04010202-0102-0101-060e-2b3404010101" : ("CompressionDef_SMPTE_D10_50Mbps_625x50I", ""), +"04010202-0102-0102-060e-2b3404010101" : ("CompressionDef_SMPTE_D10_50Mbps_525x5994I", ""), +"04010202-0102-0103-060e-2b3404010101" : ("CompressionDef_SMPTE_D10_40Mbps_625x50I", ""), +"04010202-0102-0104-060e-2b3404010101" : ("CompressionDef_SMPTE_D10_40Mbps_525x5994I", ""), +"04010202-0102-0105-060e-2b3404010101" : ("CompressionDef_SMPTE_D10_30Mbps_625x50I", ""), +"04010202-0102-0106-060e-2b3404010101" : ("CompressionDef_SMPTE_D10_30Mbps_525x5994I", ""), +"04010202-0201-0100-060e-2b3404010101" : ("CompressionDef_IEC_DV_525_60", ""), +"04010202-0201-0200-060e-2b3404010101" : ("CompressionDef_IEC_DV_625_50", ""), +"04010202-0202-0100-060e-2b3404010101" : ("CompressionDef_DV_Based_25Mbps_525_60", ""), +"04010202-0202-0200-060e-2b3404010101" : ("CompressionDef_DV_Based_25Mbps_625_50", ""), +"04010202-0202-0300-060e-2b3404010101" : ("CompressionDef_DV_Based_50Mbps_525_60", ""), +"04010202-0202-0400-060e-2b3404010101" : ("CompressionDef_DV_Based_50Mbps_625_50", ""), +"04010202-0202-0500-060e-2b3404010101" : ("CompressionDef_DV_Based_100Mbps_1080x5994I", ""), +"04010202-0202-0600-060e-2b3404010101" : ("CompressionDef_DV_Based_100Mbps_1080x50I", ""), +"04010202-0202-0700-060e-2b3404010101" : ("CompressionDef_DV_Based_100Mbps_720x5994P", ""), +"04010202-0202-0800-060e-2b3404010101" : ("CompressionDef_DV_Based_100Mbps_720x50P", ""), +"04010202-7100-0000-060e-2b340401010a" : ("CompressionDef_VC3_1", ""), +"0e040201-0204-0100-060e-2b3404010101" : ("CompressionDef_Avid_DNxHD_Legacy", ""), +} diff --git a/aaf2/model/ext/__init__.py b/aaf2/model/ext/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aaf2/model/ext/classdefs.py b/aaf2/model/ext/classdefs.py new file mode 100644 index 0000000..174ed9d --- /dev/null +++ b/aaf2/model/ext/classdefs.py @@ -0,0 +1,185 @@ +classdefs = { +"CommentMarker" : ("0d010101-0101-0800-060e-2b3402060101", "0d010101-0101-0600-060e-2b3402060101", True, { + "CommentMarkerAnnotationList" : ("6d64dd66-e5c7-488f-b0e4-272c932378a6", None, "01100200-0000-0000-060e-2b3401040101", True, False), + "CommentMarkerAttributeList" : ("c72cc817-aac5-499b-af34-bc47fec1eaa8", None, "05060800-0000-0000-060e-2b3401040101", True, False), + "CommentMarkerColor" : ("e96e6d44-c383-11d3-a069-006094eb75cb", None, "e96e6d43-c383-11d3-a069-006094eb75cb", True, False), + "CommentMarkerDate" : ("c4c45d9b-0967-11d4-a08a-006094eb75cb", None, "01100200-0000-0000-060e-2b3401040101", True, False), + "CommentMarkerIcon" : ("c4c45d9d-0967-11d4-a08a-006094eb75cb", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "CommentMarkerStatus" : ("c4c45d9e-0967-11d4-a08a-006094eb75cb", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "CommentMarkerTime" : ("c4c45d9c-0967-11d4-a08a-006094eb75cb", None, "01100200-0000-0000-060e-2b3401040101", True, False), + "CommentMarkerUser" : ("c4c45d9a-0967-11d4-a08a-006094eb75cb", None, "01100200-0000-0000-060e-2b3401040101", True, False), + } +), +"TapeDescriptor" : ("0d010101-0101-2e00-060e-2b3402060101", "0d010101-0101-2400-060e-2b3402060101", True, { + "ColorFrame" : ("9548b03a-15fb-11d4-a08f-006094eb75cb", None, "01010700-0000-0000-060e-2b3401040101", True, False), + } +), +"PCMDescriptor" : ("0d010101-0101-4800-060e-2b3402060101", "0d010101-0101-4200-060e-2b3402060101", True, { + "DataOffset" : ("bb3fabdd-fcc0-43a8-9759-c727771fcc4a", None, "01010700-0000-0000-060e-2b3401040101", True, False), + } +), +"CDCIDescriptor" : ("0d010101-0101-2800-060e-2b3402060101", "0d010101-0101-2700-060e-2b3402060101", True, { + "ImageStartAlignment" : ("506f8de5-54a1-11d3-a029-006094eb75cb", None, "01010300-0000-0000-060e-2b3401040101", True, False), + "OffsetToFrameIndexes" : ("9d15fca3-54c5-11d3-a029-006094eb75cb", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "OffsetToFrameIndexes64" : ("298eb260-30b6-4e30-8c90-cf63aa793c34", None, "01010800-0000-0000-060e-2b3401040101", True, False), + } +), +"DigitalImageDescriptor" : ("0d010101-0101-2700-060e-2b3402060101", "0d010101-0101-2500-060e-2b3402060101", False, { + "AvidEssenceElementSizeKind" : ("0e040101-0101-0110-060e-2b3401010101", None, "0e040201-0101-0000-060e-2b3401040101", True, False), + "DataOffset" : ("bfde81e4-bcc8-4abd-a80e-214dc0f14684", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "EssenceBox" : ("0e040101-0101-0107-060e-2b3401010101", None, "0e040301-0200-0000-060e-2b3401040101", True, False), + "FirstFrameOffset" : ("ce2aca4e-51ab-11d3-a024-006094eb75cb", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "FrameIndexByteOrder" : ("b57e925d-170d-11d4-a08f-006094eb75cb", None, "01010200-0000-0000-060e-2b3401040101", True, False), + "FrameSampleSize" : ("ce2aca50-51ab-11d3-a024-006094eb75cb", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "FrameStartOffset" : ("c8a0bd74-a247-4297-a52c-4458bffa1fc6", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "FramingBox" : ("0e040101-0101-010c-060e-2b3401010101", None, "0e040301-0200-0000-060e-2b3401040101", True, False), + "ImageSize" : ("ce2aca4f-51ab-11d3-a024-006094eb75cb", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "ResolutionID" : ("ce2aca4d-51ab-11d3-a024-006094eb75cb", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "SourceBox" : ("0e040101-0101-0108-060e-2b3401010101", None, "0e040301-0200-0000-060e-2b3401040101", True, False), + "ValidBox" : ("0e040101-0101-0106-060e-2b3401010101", None, "0e040301-0200-0000-060e-2b3401040101", True, False), + } +), +"ANCDataDescriptor" : ("0d010101-0101-5c00-060e-2b3402060101", "0d010101-0101-4300-060e-2b3402060101", True, { + "ManifestArray" : ("0e040101-0101-0105-060e-2b3401010101", None, "0e040402-0100-0000-060e-2b3401040101", True, False), + } +), +"VaryingValue" : ("0d010101-0101-3e00-060e-2b3402060101", "0d010101-0101-3c00-060e-2b3402060101", True, { + "VVal_Extrapolation" : ("8f2b8bae-b685-4939-b3a5-6373633b3e6c", None, "01030100-0000-0000-060e-2b3401040101", True, False), + "VVal_FieldCount" : ("2902558b-acfa-439e-a1cd-9fa1e8f891ef", None, "01010600-0000-0000-060e-2b3401040101", True, False), + } +), +"DataEssenceDescriptor" : ("0d010101-0101-4300-060e-2b3402060101", "0d010101-0101-2500-060e-2b3402060101", True, { + "DataOffset" : ("0e040101-0101-0109-060e-2b3401010101", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "FirstFrameOffset" : ("0e040101-0101-0102-060e-2b3401010101", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "MaxSampleSize" : ("0e040101-0101-0104-060e-2b3401010101", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "MinSampleSize" : ("0e040101-0101-0103-060e-2b3401010101", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "OffsetToFrameIndexes" : ("0e040101-0101-0101-060e-2b3401010101", None, "01010800-0000-0000-060e-2b3401040101", True, False), + } +), +"Parameter" : ("0d010101-0101-3c00-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + "IsEnabled" : ("0e040101-0101-010b-060e-2b3401010101", None, "01040100-0000-0000-060e-2b3401040101", True, False), + "IsSilent" : ("967dbcc7-4ba6-4b57-b8e8-3a0fbc550353", None, "01040100-0000-0000-060e-2b3401040101", True, False), + } +), +"ScopeReference" : ("0d010101-0101-0d00-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "Avid Scope" : ("9dc9c6cb-479d-4ff6-988a-b6784b90dc43", None, "01010300-0000-0000-060e-2b3401040101", True, False), + } +), +"Component" : ("0d010101-0101-0200-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + "ComponentAttributeList" : ("60958184-47b1-11d4-a01c-0004ac969f50", None, "05060800-0000-0000-060e-2b3401040101", True, False), + } +), +"Mob" : ("0d010101-0101-3400-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + "AltPrimaryMob" : ("a5820034-d057-4f55-a41c-e95c41c2afaf", None, "01040100-0000-0000-060e-2b3401040101", True, False), + "AppCode" : ("96c46992-4f62-11d3-a022-006094eb75cb", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "ConvertFrameRate" : ("d4243bd4-0142-4595-a8f3-f2eba54244de", None, "01040100-0000-0000-060e-2b3401040101", True, False), + "FileMobRate" : ("0e040101-0101-010f-060e-2b3401010101", None, "03010100-0000-0000-060e-2b3401040101", True, False), + "MobAttributeList" : ("60958183-47b1-11d4-a01c-0004ac969f50", None, "05060800-0000-0000-060e-2b3401040101", True, False), + "SubclipBegin" : ("aa24b657-fcbb-4921-951d-3a2038396722", None, "01010800-0000-0000-060e-2b3401040101", True, False), + "SubclipFullLength" : ("1262bf7b-fce2-4dfe-a0f6-ceec047c80aa", None, "01010800-0000-0000-060e-2b3401040101", True, False), + } +), +"SubDescriptor" : ("0d010101-0101-5900-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + } +), +"SourceClip" : ("0d010101-0101-1100-060e-2b3402060101", "0d010101-0101-1000-060e-2b3402060101", True, { + "SubclipFullLength" : ("660162e5-bbef-4618-8e0b-4b149b661a12", None, "01010800-0000-0000-060e-2b3401040101", True, False), + } +), +"EssenceDescriptor" : ("0d010101-0101-2400-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", False, { + "MediaContainer" : ("13980e2b-2f30-44ec-bdb0-3b730da56562", None, "01100200-0000-0000-060e-2b3401040101", True, False), + "MediaContainerGUID" : ("92790417-0131-4a05-898d-167691e11ca1", None, "01030100-0000-0000-060e-2b3401040101", True, False), + "SubDescriptors" : ("06010104-0610-0000-060e-2b3401010109", None, "05060e00-0000-0000-060e-2b3401040101", True, False), + } +), +"AvidTrackManTrackedParamClass" : ("30a42454-069e-11d4-9ffb-0004ac969f50", "0d010101-0101-0100-060e-2b3402060101", True, { + "TKMNTrackedParamSetngs" : ("30a42453-069e-11d4-9ffb-0004ac969f50", None, "ccaa73d1-f538-11d3-a081-006094eb75cb", False, False), + } +), +"TimelineMobSlot" : ("0d010101-0101-3b00-060e-2b3402060101", "0d010101-0101-3800-060e-2b3402060101", True, { + "TimelineMobAttributeList" : ("107f8331-1914-4234-b2c4-5a3eb755b7ca", None, "05060800-0000-0000-060e-2b3401040101", True, False), + } +), +"ControlPoint" : ("0d010101-0101-1900-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "ControlPointPointProperties" : ("3c1b48d0-c32c-4ea9-bb9d-35b898527283", None, "05060a00-0000-0000-060e-2b3401040101", True, False), + "ControlPointSource" : ("0e040101-0101-010a-060e-2b3401010101", None, "01010700-0000-0000-060e-2b3401040101", True, False), + } +), +"Transition" : ("0d010101-0101-1700-060e-2b3402060101", "0d010101-0101-0200-060e-2b3402060101", True, { + "TranTKMNTrackedParamAry" : ("2c04d7ec-179d-11d4-a003-0004ac969f50", None, "b56a2ec2-fc3b-11d3-9ff7-0004ac969f50", True, False), + "TranTKMNTrackedParamSetngs" : ("2c04d7ed-179d-11d4-a003-0004ac969f50", None, "ccaa73d1-f538-11d3-a081-006094eb75cb", True, False), + "TranTKMNTrackerDataAry" : ("2c04d7eb-179d-11d4-a003-0004ac969f50", None, "b56a2ec3-fc3b-11d3-9ff7-0004ac969f50", True, False), + } +), +"TaggedValue" : ("0d010101-0101-3f00-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "PortableObject" : ("b6bb5f4e-7b37-11d3-a044-006094eb75cb", None, "f9a74d0a-7b30-11d3-a044-006094eb75cb", True, False), + "PortableObjectClassID" : ("08835f4f-7b28-11d3-a044-006094eb75cb", None, "01010300-0000-0000-060e-2b3401040101", True, False), + "TaggedValueAttributeList" : ("60958185-47b1-11d4-a01c-0004ac969f50", None, "05060800-0000-0000-060e-2b3401040101", True, False), + "TaggedValue_Stream" : ("c12d81ac-bd68-4fef-a37f-562d28e37158", None, "04100200-0000-0000-060e-2b3401040101", True, False), + } +), +"EdgeCode" : ("0d010101-0101-0400-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "AvBasePerf" : ("1fb0160a-6907-45fe-a997-c6818820970e", None, "01010100-0000-0000-060e-2b3401040101", True, False), + "AvEdgeType" : ("4d783cfa-35da-4566-9a52-2190d5078616", None, "01010600-0000-0000-060e-2b3401040101", True, False), + "AvFilmType" : ("067da182-a750-48ba-995b-b7fd88f3b838", None, "01010600-0000-0000-060e-2b3401040101", True, False), + } +), +"RGBADescriptor" : ("0d010101-0101-2900-060e-2b3402060101", "0d010101-0101-2700-060e-2b3402060101", True, { + "OffsetToFrameIndexes" : ("0e040101-0101-010d-060e-2b3401010101", None, "01010800-0000-0000-060e-2b3401040101", True, False), + } +), +"AvidTrackManTrackerDataClass" : ("13e0a981-0412-11d4-9ff9-0004ac969f50", "0d010101-0101-0100-060e-2b3402060101", True, { + "TKMNTrkDataBoxX" : ("e3c9057c-311d-41c1-9a7d-41ae1de90150", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataBoxY" : ("f15129da-7d1a-4f68-87ab-c0956f125654", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataConfidence" : ("c63c3449-0412-11d4-9ff9-0004ac969f50", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataDataX" : ("c63c3447-0412-11d4-9ff9-0004ac969f50", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataDataY" : ("c63c3448-0412-11d4-9ff9-0004ac969f50", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataFilterDataAmt" : ("aab41ed6-07cc-4917-8119-a4bfeec607a0", None, "03010100-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataJitterRemovalEnabled" : ("d1f936be-6f3a-4b8d-8e7a-855ab8a8565f", None, "01040100-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataOffsetTrackingEnabled" : ("875d33e9-f596-4daa-9730-3128a06b9763", None, "01040100-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataOffsetX" : ("c63c344a-0412-11d4-9ff9-0004ac969f50", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataOffsetY" : ("c63c344b-0412-11d4-9ff9-0004ac969f50", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataPatternH" : ("c63c344d-0412-11d4-9ff9-0004ac969f50", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataPatternW" : ("c63c344c-0412-11d4-9ff9-0004ac969f50", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataSearchBH" : ("c63c3451-0412-11d4-9ff9-0004ac969f50", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataSearchLW" : ("c63c344e-0412-11d4-9ff9-0004ac969f50", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataSearchRW" : ("c63c344f-0412-11d4-9ff9-0004ac969f50", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataSearchTH" : ("c63c3450-0412-11d4-9ff9-0004ac969f50", None, "05021600-0000-0000-060e-2b3401040101", True, False), + "TKMNTrkDataSettings" : ("c63c3452-0412-11d4-9ff9-0004ac969f50", None, "ccaa73d1-f538-11d3-a081-006094eb75cb", False, False), + "TKMNTrkDataSmoothingEnabled" : ("75994f5f-e038-4769-9026-d8082cebc6e0", None, "01040100-0000-0000-060e-2b3401040101", True, False), + } +), +"OperationGroup" : ("0d010101-0101-0a00-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "OpGroupAVXParamStream" : ("b045db5e-87d7-47fb-b862-2a548a1cad60", None, "04100200-0000-0000-060e-2b3401040101", True, False), + "OpGroupGraphicsParamStream" : ("73fe71c5-15f3-4f0e-acb8-b70edfe6ca5c", None, "04100200-0000-0000-060e-2b3401040101", True, False), + "OpGroupLeftLength" : ("7cd5da62-6a1c-4490-9f6c-f57204ec7dba", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "OpGroupMotionCtlOffsetMapAdjust" : ("77ad6841-08fc-4f53-bd0a-2a6b0b5f94d9", None, "03010100-0000-0000-060e-2b3401040101", True, False), + "OpGroupMotionCtlSourceParams" : ("614406f1-d8e7-469b-bd99-0e70a9a9cd60", None, "05060a00-0000-0000-060e-2b3401040101", True, False), + "OpGroupRightLength" : ("a7da7356-2021-4f89-97d2-e683307c8dd7", None, "01010700-0000-0000-060e-2b3401040101", True, False), + "OpGrpTKMNTrackedParamAry" : ("30a42451-069e-11d4-9ffb-0004ac969f50", None, "b56a2ec2-fc3b-11d3-9ff7-0004ac969f50", True, False), + "OpGrpTKMNTrackedParamSetng" : ("30a42452-069e-11d4-9ffb-0004ac969f50", None, "ccaa73d1-f538-11d3-a081-006094eb75cb", True, False), + "OpGrpTKMNTrackerDataAry" : ("af913551-04c3-11d4-9ff9-0004ac969f50", None, "b56a2ec3-fc3b-11d3-9ff7-0004ac969f50", True, False), + } +), +"Header" : ("0d010101-0101-2f00-060e-2b3402060101", "0d010101-0101-0100-060e-2b3402060101", True, { + "AudioRateAdjustmentFactor" : ("b7d51ad5-650b-4d3a-8596-99b579e177a6", None, "01010200-0000-0000-060e-2b3401040101", True, False), + "EssenceFileMobID" : ("abf1b771-8efd-4802-8b2f-680dff611381", None, "01030200-0000-0000-060e-2b3401040101", True, False), + "MasterMobID" : ("ffdd41e1-ae2c-49c6-ae58-78e041454179", None, "01030200-0000-0000-060e-2b3401040101", True, False), + "ProjectEditRate" : ("f36546b1-387c-4ee9-8c70-a718467ae486", None, "03010100-0000-0000-060e-2b3401040101", True, False), + "ProjectName" : ("62fc3717-492d-42bf-a5fb-7b25f61594b9", None, "01100200-0000-0000-060e-2b3401040101", True, False), + } +), +"EssenceGroup" : ("0d010101-0101-0500-060e-2b3402060101", "0d010101-0101-0300-060e-2b3402060101", True, { + "EssenceGroupType" : ("d9c9bf24-f6b8-11d3-a083-006094eb75cb", None, "01010700-0000-0000-060e-2b3401040101", True, False), + } +), +"Avid MC Mob Reference" : ("6619f8e0-fe77-11d3-a084-006094eb75cb", "0d010101-0101-0100-060e-2b3402060101", True, { + "Mob Reference MobID" : ("81110e9f-fe7c-11d3-a084-006094eb75cb", None, "01030200-0000-0000-060e-2b3401040101", False, False), + "Mob Reference Position" : ("81110ea0-fe7c-11d3-a084-006094eb75cb", None, "01010800-0000-0000-060e-2b3401040101", False, False), + } +), +} + +aliases = { +"Avid_MC_Mob_Reference" : "Avid MC Mob Reference", +} diff --git a/aaf2/model/ext/typedefs.py b/aaf2/model/ext/typedefs.py new file mode 100644 index 0000000..df468eb --- /dev/null +++ b/aaf2/model/ext/typedefs.py @@ -0,0 +1,146 @@ +ints = { +} + +enums = { +"ColorSitingType" : ("02010105-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101",{ + 5 : "LineAlternating", + 6 : "VerticalMidpoint", + } +), +"AvidPannerKindType" : ("3659b342-4f19-4316-9309-f139434a94e5", "01010300-0000-0000-060e-2b3401040101",{ + 1 : "AvidPannerKind_Stereo", + 2 : "AvidPannerKind_LCR", + 3 : "AvidPannerKind_Quad", + 4 : "AvidPannerKind_LCRS", + 5 : "AvidPannerKind_5dot0", + 6 : "AvidPannerKind_5dot1", + 7 : "AvidPannerKind_6dot0", + 8 : "AvidPannerKind_6dot1", + 9 : "AvidPannerKind_7dot0", + 10 : "AvidPannerKind_7dot1", + } +), +"AvidEssenceElementSizeKind" : ("0e040201-0101-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101",{ + 0 : "AvidEssenceElementSizeKind_Unknown", + 1 : "AvidEssenceElementSizeKind_CBE", + 2 : "AvidEssenceElementSizeKind_VBE", + } +), +} + +records = { +"BoundsBox" : ("0e040301-0200-0000-060e-2b3401040101", ( + ("PositionX" ,"03010100-0000-0000-060e-2b3401040101"), + ("PositionY" ,"03010100-0000-0000-060e-2b3401040101"), + ("Width" ,"03010100-0000-0000-060e-2b3401040101"), + ("Height" ,"03010100-0000-0000-060e-2b3401040101"), + ), +), +"AvidManifestElement" : ("0e040301-0100-0000-060e-2b3401040101", ( + ("did" ,"01010100-0000-0000-060e-2b3401040101"), + ("sdid" ,"01010100-0000-0000-060e-2b3401040101"), + ), +), +"EqualizationBand" : ("c4c670c9-bd44-11d3-80e9-006008143e6f", ( + ("type" ,"01030100-0000-0000-060e-2b3401040101"), + ("frequency" ,"01010300-0000-0000-060e-2b3401040101"), + ("gain" ,"01010300-0000-0000-060e-2b3401040101"), + ("q" ,"01010300-0000-0000-060e-2b3401040101"), + ("enable" ,"01040100-0000-0000-060e-2b3401040101"), + ), +), +"RGBColor" : ("e96e6d43-c383-11d3-a069-006094eb75cb", ( + ("red" ,"01010200-0000-0000-060e-2b3401040101"), + ("green" ,"01010200-0000-0000-060e-2b3401040101"), + ("blue" ,"01010200-0000-0000-060e-2b3401040101"), + ), +), +"AudioSuitePlugInChunk" : ("4e4d8f5f-eefd-11d3-9ff5-0004ac969f50", ( + ("Version" ,"01010300-0000-0000-060e-2b3401040101"), + ("ManufacturerID" ,"0f96cb41-2aa8-11d4-a00f-0004ac969f50"), + ("ProductID" ,"0f96cb41-2aa8-11d4-a00f-0004ac969f50"), + ("PlugInID" ,"0f96cb41-2aa8-11d4-a00f-0004ac969f50"), + ("ChunkID" ,"0f96cb41-2aa8-11d4-a00f-0004ac969f50"), + ("Name" ,"3271a34f-f3a1-11d3-9ff5-0004ac969f50"), + ("ChunkDataUID" ,"01030100-0000-0000-060e-2b3401040101"), + ), +), +} + +fixed_arrays = { +"AvidBounds" : ("8bc42732-6bab-11d3-80cf-006008143e6f", "01010100-0000-0000-060e-2b3401040101", 48), +"AvidColor" : ("8bc42733-6bab-11d3-80cf-006008143e6f", "01010100-0000-0000-060e-2b3401040101", 68), +"AvidCrop" : ("8bc4272f-6bab-11d3-80cf-006008143e6f", "01010100-0000-0000-060e-2b3401040101", 32), +"AvidGlobalKeyFrame" : ("09997778-960e-11d3-a04e-006094eb75cb", "01010100-0000-0000-060e-2b3401040101", 16), +"AvidPosition" : ("8bc4272e-6bab-11d3-80cf-006008143e6f", "01010100-0000-0000-060e-2b3401040101", 24), +"AvidScale" : ("8bc42730-6bab-11d3-80cf-006008143e6f", "01010100-0000-0000-060e-2b3401040101", 16), +"AvidSpillSupress" : ("8bc42731-6bab-11d3-80cf-006008143e6f", "01010100-0000-0000-060e-2b3401040101", 8), +"AvidString4" : ("0f96cb41-2aa8-11d4-a00f-0004ac969f50", "01010100-0000-0000-060e-2b3401040101", 4), +"AvidWideString32" : ("3271a34f-f3a1-11d3-9ff5-0004ac969f50", "01010200-0000-0000-060e-2b3401040101", 32), +} + +var_arrays = { +"AudioSuitePIChunkArray" : ("4e4d8f60-eefd-11d3-9ff5-0004ac969f50", "4e4d8f5f-eefd-11d3-9ff5-0004ac969f50"), +"AudioSuitePIChunkData" : ("5cf19caf-ef83-11d3-9ff5-0004ac969f50", "01010100-0000-0000-060e-2b3401040101"), +"AvidBagOfBits" : ("ccaa73d1-f538-11d3-a081-006094eb75cb", "01010100-0000-0000-060e-2b3401040101"), +"AvidManifestArray" : ("0e040402-0100-0000-060e-2b3401040101", "0e040301-0100-0000-060e-2b3401040101"), +"AvidTKMNTrackedParamArray" : ("b56a2ec2-fc3b-11d3-9ff7-0004ac969f50", "f9a74d0a-7b30-11d3-a044-006094eb75cb"), +"AvidTKMNTrackerDataArray" : ("b56a2ec3-fc3b-11d3-9ff7-0004ac969f50", "f9a74d0a-7b30-11d3-a044-006094eb75cb"), +"EqualizationBandArray" : ("c4c670ca-bd44-11d3-80e9-006008143e6f", "c4c670c9-bd44-11d3-80e9-006008143e6f"), +"kAAFTypeID_SubDescriptorStrongReferenceVector" : ("05060e00-0000-0000-060e-2b3401040101", "05022600-0000-0000-060e-2b3401040101"), +} + +renames = { +} + +strings = { +} + +streams = { +} + +opaques = { +} + +extenums = { +"CodingEquationsType" : ("02020106-0000-0000-060e-2b3401040101", { + "0e040501-0201-0000-060e-2b3404010101" : "CodingEquations_ITU2020", + }, +), +"ColorPrimariesType" : ("02020105-0000-0000-060e-2b3401040101", { + "04010101-0304-0000-060e-2b340401010d" : "ColorPrimaries_ITU2020", + "0e040501-0301-0000-060e-2b3404010101" : "ColorPrimaries_SMPTE_RP431", + "0e040501-0302-0000-060e-2b3404010101" : "ColorPrimaries_Sony_SGamut3", + "0e040501-0303-0000-060e-2b3404010101" : "ColorPrimaries_Sony_SGamut3_Cine", + }, +), +"TransferCharacteristicType" : ("02020102-0000-0000-060e-2b3401040101", { + "0e040501-0101-0000-060e-2b3404010101" : "TransferCharacteristic_DPXPrintingDensity", + "0e040501-0102-0000-060e-2b3404010101" : "TransferCharacteristic_DPXLogarithmic", + "0e040501-0103-0000-060e-2b3404010101" : "TransferCharacteristic_SRGB", + "0e040501-0105-0000-060e-2b3404010101" : "TransferCharacteristic_SMPTE_RP431", + "0e040501-0106-0000-060e-2b3404010101" : "TransferCharacteristic_SMPTE_ST2084", + "0e040501-0108-0000-060e-2b3404010101" : "TransferCharacteristic_ARIB_B67", + "0e040501-010a-0000-060e-2b3404010101" : "TransferCharacteristic_ITU709_Extended2", + "0e060401-0101-0605-060e-2b3404010106" : "TransferCharacteristic_Sony_SLog3", + "0e170000-0001-0101-060e-2b340401010c" : "TransferCharacteristic_ARRI_LogC", + }, +), +} + +chars = { +} + +indirects = { +} + +sets = { +} + +strongrefs = { +"AvidStrongReference" : ("f9a74d0a-7b30-11d3-a044-006094eb75cb", "0d010101-0101-0100-060e-2b3402060101"), +"kAAFTypeID_SubDescriptorStrongReference" : ("05022600-0000-0000-060e-2b3401040101", "0d010101-0101-5900-060e-2b3402060101"), +} + +weakrefs = { +} diff --git a/aaf2/model/typedefs.py b/aaf2/model/typedefs.py new file mode 100644 index 0000000..2f68c4d --- /dev/null +++ b/aaf2/model/typedefs.py @@ -0,0 +1,510 @@ +ints = { +"aafUInt8" : ("01010100-0000-0000-060e-2b3401040101", 1, False, ), +"aafUInt16" : ("01010200-0000-0000-060e-2b3401040101", 2, False, ), +"aafUInt32" : ("01010300-0000-0000-060e-2b3401040101", 4, False, ), +"aafUInt64" : ("01010400-0000-0000-060e-2b3401040101", 8, False, ), +"aafInt8" : ("01010500-0000-0000-060e-2b3401040101", 1, True, ), +"aafInt16" : ("01010600-0000-0000-060e-2b3401040101", 2, True, ), +"aafInt32" : ("01010700-0000-0000-060e-2b3401040101", 4, True, ), +"aafInt64" : ("01010800-0000-0000-060e-2b3401040101", 8, True, ), +} + +enums = { +"Boolean" : ("01040100-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "False", + 1 : "True", + } +), +"ProductReleaseType" : ("02010101-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "VersionUnknown", + 1 : "VersionReleased", + 2 : "VersionDebug", + 3 : "VersionPatched", + 4 : "VersionBeta", + 5 : "VersionPrivateBuild", + } +), +"TapeFormatType" : ("02010102-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "TapeFormatNull", + 1 : "BetacamFormat", + 2 : "BetacamSPFormat", + 3 : "VHSFormat", + 4 : "SVHSFormat", + 5 : "8mmFormat", + 6 : "Hi8Format", + } +), +"VideoSignalType" : ("02010103-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "VideoSignalNull", + 1 : "NTSCSignal", + 2 : "PALSignal", + } +), +"TapeCaseType" : ("02010104-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "TapeCaseNull", + 1 : "ThreeFourthInchVideoTape", + 2 : "VHSVideoTape", + 3 : "8mmVideoTape", + 4 : "BetacamVideoTape", + 5 : "CompactCassette", + 6 : "DATCartridge", + 7 : "NagraAudioTape", + } +), +"ColorSitingType" : ("02010105-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "CoSiting", + 1 : "Averaging", + 2 : "ThreeTap", + 3 : "Quincunx", + 4 : "Rec601", + 255: "UnknownSiting", + } +), +"EditHintType" : ("02010106-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "NoEditHint", + 1 : "Proportional", + 2 : "RelativeLeft", + 3 : "RelativeRight", + 4 : "RelativeFixed", + } +), +"FadeType" : ("02010107-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "FadeNone", + 1 : "FadeLinearAmp", + 2 : "FadeLinearPower", + } +), +"LayoutType" : ("02010108-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "FullFrame", + 1 : "SeparateFields", + 2 : "OneField", + 3 : "MixedFields", + 4 : "SegmentedFrame", + } +), +"TCSource" : ("02010109-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "TimecodeLTC", + 1 : "TimecodeVITC", + } +), +"PulldownDirectionType" : ("0201010a-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "TapeToFilmSpeed", + 1 : "FilmToTapeSpeed", + } +), +"PulldownKindType" : ("0201010b-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "TwoThreePD", + 1 : "PALPD", + 2 : "OneToOneNTSC", + 3 : "OneToOnePAL", + 4 : "VideoTapNTSC", + 5 : "OneToOneHDSixty", + 6 : "TwentyFourToSixtyPD", + 7 : "TwoToOnePD", + } +), +"EdgeType" : ("0201010c-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "EtNull", + 1 : "EtKeycode", + 2 : "EtEdgenum4", + 3 : "EtEdgenum5", + 8 : "EtHeaderSize", + } +), +"FilmType" : ("0201010d-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "FtNull", + 1 : "Ft35MM", + 2 : "Ft16MM", + 3 : "Ft8MM", + 4 : "Ft65MM", + } +), +"RGBAComponentKind" : ("0201010e-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0x30: "CompNone", + 0x41: "CompAlpha", + 0x42: "CompBlue", + 0x46: "CompFill", + 0x47: "CompGreen", + 0x50: "CompPalette", + 0x52: "CompRed", + 0x00: "CompNull", + } +), +"ReferenceType" : ("0201010f-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "RefLimitMinimum", + 1 : "RefLimitMaximum", + 2 : "RefMinimum", + 3 : "RefMaximum", + 4 : "RefEnumvalue", + } +), +"AlphaTransparencyType" : ("02010120-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "MinValueTransparent", + 1 : "MaxValueTransparent", + } +), +"FieldNumber" : ("02010121-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "UnspecifiedField", + 1 : "FieldOne", + 2 : "FieldTwo", + } +), +"ElectroSpatialFormulation": ("02010122-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "ElectroSpatialFormulation_Default", + 1 : "ElectroSpatialFormulation_TwoChannelMode", + 2 : "ElectroSpatialFormulation_SingleChannelMode", + 3 : "ElectroSpatialFormulation_PrimarySecondaryMode", + 4 : "ElectroSpatialFormulation_StereophonicMode", + 7 : "ElectroSpatialFormulation_SingleChannelDoubleSamplingFrequencyMode", + 8 : "ElectroSpatialFormulation_StereoLeftChannelDoubleSamplingFrequencyMode", + 9 : "ElectroSpatialFormulation_StereoRightChannelDoubleSamplingFrequencyMode", + 15 : "ElectroSpatialFormulation_MultiChannelMode", + } +), +"EmphasisType" : ("02010123-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "Emphasis_Unknown", + 1 : "Emphasis_Reserved0", + 2 : "Emphasis_Reserved1", + 3 : "Emphasis_Reserved2", + 4 : "Emphasis_None", + 5 : "Emphasis_Reserved3", + 6 : "Emphasis_15and50", + 7 : "Emphasis_ITU", + } +), +"AuxBitsModeType" : ("02010124-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "AuxBitsMode_NotDefined", + 1 : "AuxBitsMode_MainAudioSampleData", + 2 : "AuxBitsMode_SingleCoordinationSignal", + 3 : "AuxBitsMode_UserDefined", + 4 : "AuxBitsMode_Reserved0", + 5 : "AuxBitsMode_Reserved1", + 6 : "AuxBitsMode_Reserved2", + 7 : "AuxBitsMode_Reserved3", + } +), +"ChannelStatusModeType" : ("02010125-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "ChannelStatusMode_None", + 1 : "ChannelStatusMode_Minimum", + 2 : "ChannelStatusMode_Standard", + 3 : "ChannelStatusMode_Fixed", + 4 : "ChannelStatusMode_Stream", + 5 : "ChannelStatusMode_Essence", + } +), +"UserDataModeType" : ("02010126-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "UserDataMode_NotDefined", + 1 : "UserDataMode_192BitBlockStructure", + 2 : "UserDataMode_AES18", + 3 : "UserDataMode_UserDefined", + 4 : "UserDataMode_IEC", + 5 : "UserDataMode_Metadata", + 6 : "UserDataMode_Reserved0", + 7 : "UserDataMode_Reserved1", + 8 : "UserDataMode_Reserved2", + 9 : "UserDataMode_Reserved3", + 10 : "UserDataMode_Reserved4", + 11 : "UserDataMode_Reserved5", + 12 : "UserDataMode_Reserved6", + 13 : "UserDataMode_Reserved7", + 14 : "UserDataMode_Reserved8", + 15 : "UserDataMode_Reserved9", + } +), +"SignalStandardType" : ("02010127-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "SignalStandard_None", + 1 : "SignalStandard_ITU601", + 2 : "SignalStandard_ITU1358", + 3 : "SignalStandard_SMPTE347M", + 4 : "SignalStandard_SMPTE274M", + 5 : "SignalStandard_SMPTE296M", + 6 : "SignalStandard_SMPTE349M", + } +), +"ScanningDirectionType" : ("02010128-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "ScanningDirection_LeftToRightTopToBottom", + 1 : "ScanningDirection_RightToLeftTopToBottom", + 2 : "ScanningDirection_LeftToRightBottomToTop", + 3 : "ScanningDirection_RightToLeftBottomToTop", + 4 : "ScanningDirection_TopToBottomLeftToRight", + 5 : "ScanningDirection_TopToBottomRightToLeft", + 6 : "ScanningDirection_BottomToTopLeftToRight", + 7 : "ScanningDirection_BottomToTopRightToLeft", + } +), +"ContentScanningType" : ("0201012a-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "ContentScanning_NotKnown", + 1 : "ContentScanning_Progressive", + 2 : "ContentScanning_Interlace", + 3 : "ContentScanning_Mixed", + } +), +"TitleAlignmentType" : ("0201012b-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", { + 0 : "TitleAlignment_Left", + 1 : "TitleAlignment_Center", + 2 : "TitleAlignment_Right", + } +), +} + +records = { +"AUID" : ("01030100-0000-0000-060e-2b3401040101", ( + ("Data1" , "01010300-0000-0000-060e-2b3401040101"), + ("Data2" , "01010200-0000-0000-060e-2b3401040101"), + ("Data3" , "01010200-0000-0000-060e-2b3401040101"), + ("Data4" , "04010800-0000-0000-060e-2b3401040101"), + ) +), +"MobIDType": ("01030200-0000-0000-060e-2b3401040101", ( + ("SMPTELabel" , "04010200-0000-0000-060e-2b3401040101"), + ("length" , "01010100-0000-0000-060e-2b3401040101"), + ("instanceHigh" , "01010100-0000-0000-060e-2b3401040101"), + ("instanceMid" , "01010100-0000-0000-060e-2b3401040101"), + ("instanceLow" , "01010100-0000-0000-060e-2b3401040101"), + ("material" , "01030100-0000-0000-060e-2b3401040101"), + ) +), +"Rational": ("03010100-0000-0000-060e-2b3401040101", ( + ("Numerator" , "01010700-0000-0000-060e-2b3401040101"), + ("Denominator" , "01010700-0000-0000-060e-2b3401040101"), + ) +), +"ProductVersion": ("03010200-0000-0000-060e-2b3401040101", ( + ("major" , "01010200-0000-0000-060e-2b3401040101"), + ("minor" , "01010200-0000-0000-060e-2b3401040101"), + ("tertiary" , "01010200-0000-0000-060e-2b3401040101"), + ("patchLevel" , "01010200-0000-0000-060e-2b3401040101"), + ("type" , "02010101-0000-0000-060e-2b3401040101"), + ) +), +"VersionType": ("03010300-0000-0000-060e-2b3401040101", ( + ("major" , "01010500-0000-0000-060e-2b3401040101"), + ("minor" , "01010500-0000-0000-060e-2b3401040101"), + ) +), +"RGBAComponent": ("03010400-0000-0000-060e-2b3401040101", ( + ("Code" , "0201010e-0000-0000-060e-2b3401040101"), + ("Size" , "01010100-0000-0000-060e-2b3401040101"), + ) +), +"DateStruct": ("03010500-0000-0000-060e-2b3401040101", ( + ("year" , "01010600-0000-0000-060e-2b3401040101"), + ("month" , "01010100-0000-0000-060e-2b3401040101"), + ("day" , "01010100-0000-0000-060e-2b3401040101"), + ) +), +"TimeStruct": ("03010600-0000-0000-060e-2b3401040101", ( + ("hour" , "01010100-0000-0000-060e-2b3401040101"), + ("minute" , "01010100-0000-0000-060e-2b3401040101"), + ("second" , "01010100-0000-0000-060e-2b3401040101"), + ("fraction" , "01010100-0000-0000-060e-2b3401040101"), + ) +), +"TimeStamp": ("03010700-0000-0000-060e-2b3401040101", ( + ("date" , "03010500-0000-0000-060e-2b3401040101"), + ("time" , "03010600-0000-0000-060e-2b3401040101"), + ) +), +} + +fixed_arrays = { +"aafUInt8Array12" : ("04010200-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", 12), +"aafUInt8Array8" : ("04010800-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101", 8), +"aafRGBALayout" : ("04020100-0000-0000-060e-2b3401040101", "03010400-0000-0000-060e-2b3401040101", 8), +} + +var_arrays = { +"aafUInt8Array" : ("04010100-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101"), +"aafInt32Array" : ("04010300-0000-0000-060e-2b3401040101", "01010700-0000-0000-060e-2b3401040101"), +"aafInt64Array" : ("04010400-0000-0000-060e-2b3401040101", "01010800-0000-0000-060e-2b3401040101"), +"aafStringArray" : ("04010500-0000-0000-060e-2b3401040101", "01100100-0000-0000-060e-2b3401040101"), +"aafAUIDArray" : ("04010600-0000-0000-060e-2b3401040101", "01030100-0000-0000-060e-2b3401040101"), +"aafPositionArray" : ("04010700-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101"), +"aafUInt32Array" : ("04010900-0000-0000-060e-2b3401040101", "01010300-0000-0000-060e-2b3401040101"), +"aafChannelStatusModeArray" : ("04010a00-0000-0000-060e-2b3401040101", "02010125-0000-0000-060e-2b3401040101"), +"aafUserDataModeArray" : ("04010b00-0000-0000-060e-2b3401040101", "02010126-0000-0000-060e-2b3401040101"), +"aafDataValue" : ("04100100-0000-0000-060e-2b3401040101", "01010100-0000-0000-060e-2b3401040101"), +"kAAFTypeID_ComponentStrongReferenceVector" : ("05060100-0000-0000-060e-2b3401040101", "05020b00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_ControlPointStrongReferenceVector" : ("05060200-0000-0000-060e-2b3401040101", "05020d00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_IdentificationStrongReferenceVector" : ("05060300-0000-0000-060e-2b3401040101", "05021000-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_LocatorStrongReferenceVector" : ("05060400-0000-0000-060e-2b3401040101", "05021200-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_MobSlotStrongReferenceVector" : ("05060500-0000-0000-060e-2b3401040101", "05021400-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_SegmentStrongReferenceVector" : ("05060600-0000-0000-060e-2b3401040101", "05020600-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_SourceReferenceStrongReferenceVector" : ("05060700-0000-0000-060e-2b3401040101", "05020800-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_TaggedValueStrongReferenceVector" : ("05060800-0000-0000-060e-2b3401040101", "05021a00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_KLVDataStrongReferenceVector" : ("05060900-0000-0000-060e-2b3401040101", "05021c00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_ParameterStrongReferenceVector" : ("05060a00-0000-0000-060e-2b3401040101", "05021600-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_FileDescriptorStrongReferenceVector" : ("05060b00-0000-0000-060e-2b3401040101", "05021d00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_RIFFChunkStrongReferenceVector" : ("05060c00-0000-0000-060e-2b3401040101", "05021e00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_DescriptiveObjectStrongReferenceVector" : ("05060d00-0000-0000-060e-2b3401040101", "05022200-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_OperationDefinitionWeakReferenceVector" : ("05040100-0000-0000-060e-2b3401040101", "05010700-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_TypeDefinitionWeakReferenceVector" : ("05040200-0000-0000-060e-2b3401040101", "05010900-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_DataDefinitionWeakReferenceVector" : ("05040300-0000-0000-060e-2b3401040101", "05010300-0000-0000-060e-2b3401040101" ), +} + +renames = { +"aafPositionType" : ("01012001-0000-0000-060e-2b3401040101", "01010800-0000-0000-060e-2b3401040101"), +"aafLengthType" : ("01012002-0000-0000-060e-2b3401040101", "01010800-0000-0000-060e-2b3401040101"), +"aafJPEGTableIDType" : ("01012003-0000-0000-060e-2b3401040101", "01010700-0000-0000-060e-2b3401040101"), +"aafPhaseFrameType" : ("01012300-0000-0000-060e-2b3401040101", "01010700-0000-0000-060e-2b3401040101"), +} + +strings = { +"aafString" : ("01100200-0000-0000-060e-2b3401040101", "01100100-0000-0000-060e-2b3401040101"), +} + +streams = { +"Stream" : "04100200-0000-0000-060e-2b3401040101", +} + +opaques = { +"aafOpaque" : "04100400-0000-0000-060e-2b3401040101", +} + +extenums = { +"OperationCategoryType": ("02020101-0000-0000-060e-2b3401040101", { + "0d010102-0101-0100-060e-2b3404010101" : "OperationCategory_Effect", + } +), +"TransferCharacteristicType": ("02020102-0000-0000-060e-2b3401040101", { + "04010101-0101-0000-060e-2b3404010101" : "TransferCharacteristic_ITU470_PAL", + "04010101-0102-0000-060e-2b3404010101" : "TransferCharacteristic_ITU709", + "04010101-0103-0000-060e-2b3404010101" : "TransferCharacteristic_SMPTE240M", + "04010101-0104-0000-060e-2b3404010101" : "TransferCharacteristic_274M_296M", + "04010101-0105-0000-060e-2b3404010101" : "TransferCharacteristic_ITU1361", + "04010101-0106-0000-060e-2b3404010101" : "TransferCharacteristic_linear", + } +), +"PluginCategoryType": ("02020103-0000-0000-060e-2b3401040101", { + "0d010102-0101-0200-060e-2b3404010101" : "PluginCategory_Effect", + "0d010102-0101-0300-060e-2b3404010101" : "PluginCategory_Codec", + "0d010102-0101-0400-060e-2b3404010101" : "PluginCategory_Interpolation", + } +), +"UsageType" : ("02020104-0000-0000-060e-2b3401040101", { + "0d010102-0101-0500-060e-2b3404010101" : "Usage_SubClip", + "0d010102-0101-0600-060e-2b3404010101" : "Usage_AdjustedClip", + "0d010102-0101-0700-060e-2b3404010101" : "Usage_TopLevel", + "0d010102-0101-0800-060e-2b3404010101" : "Usage_LowerLevel", + "0d010102-0101-0900-060e-2b3404010101" : "Usage_Template", + } +), +"ColorPrimariesType": ("02020105-0000-0000-060e-2b3401040101", { + "04010101-0301-0000-060e-2b3404010106" : "ColorPrimaries_SMPTE170M", + "04010101-0302-0000-060e-2b3404010106" : "ColorPrimaries_ITU470_PAL", + "04010101-0303-0000-060e-2b3404010106" : "ColorPrimaries_ITU709", + } +), +"CodingEquationsType": ("02020106-0000-0000-060e-2b3401040101", { + "04010101-0201-0000-060e-2b3404010101" : "CodingEquations_ITU601", + "04010101-0202-0000-060e-2b3404010101" : "CodingEquations_ITU709", + "04010101-0203-0000-060e-2b3404010101" : "CodingEquations_SMPTE240M", + } +), +} + +chars = { +"aafCharacter" : "01100100-0000-0000-060e-2b3401040101", +} + +indirects = { +"aafIndirect" : "04100300-0000-0000-060e-2b3401040101", +} + +sets = { +"AUIDSet": ("04030100-0000-0000-060e-2b3401040101", "01030100-0000-0000-060e-2b3401040101"), +"UInt32Set": ("04030200-0000-0000-060e-2b3401040101", "01010300-0000-0000-060e-2b3401040101"), +"kAAFTypeID_ClassDefinitionStrongReferenceSet" : ("05050100-0000-0000-060e-2b3401040101", "05020900-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_CodecDefinitionStrongReferenceSet" : ("05050200-0000-0000-060e-2b3401040101", "05020a00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_ContainerDefinitionStrongReferenceSet" : ("05050300-0000-0000-060e-2b3401040101", "05020c00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_DataDefinitionStrongReferenceSet" : ("05050400-0000-0000-060e-2b3401040101", "05020e00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_EssenceDataStrongReferenceSet" : ("05050500-0000-0000-060e-2b3401040101", "05020f00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_InterpolationDefinitionStrongReferenceSet" : ("05050600-0000-0000-060e-2b3401040101", "05021100-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_MobStrongReferenceSet" : ("05050700-0000-0000-060e-2b3401040101", "05021300-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_OperationDefinitionStrongReferenceSet" : ("05050800-0000-0000-060e-2b3401040101", "05021500-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_ParameterDefinitionStrongReferenceSet" : ("05050900-0000-0000-060e-2b3401040101", "05021700-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_PluginDefinitionStrongReferenceSet" : ("05050a00-0000-0000-060e-2b3401040101", "05021800-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_PropertyDefinitionStrongReferenceSet" : ("05050b00-0000-0000-060e-2b3401040101", "05021900-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_TypeDefinitionStrongReferenceSet" : ("05050c00-0000-0000-060e-2b3401040101", "05021b00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_KLVDataDefinitionStrongReferenceSet" : ("05050d00-0000-0000-060e-2b3401040101", "05022000-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_TaggedValueDefinitionStrongReferenceSet" : ("05050e00-0000-0000-060e-2b3401040101", "05022100-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_DescriptiveObjectStrongReferenceSet" : ("05050f00-0000-0000-060e-2b3401040101", "05022200-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_DataDefinitionWeakReferenceSet" : ("05030d00-0000-0000-060e-2b3401040101", "05010300-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_ParameterDefinitionWeakReferenceSet" : ("05030e00-0000-0000-060e-2b3401040101", "05010800-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_PluginDefinitionWeakReferenceSet" : ("05030f00-0000-0000-060e-2b3401040101", "05010a00-0000-0000-060e-2b3401040101" ), +"kAAFTypeID_PropertyDefinitionWeakReferenceSet" : ("05031000-0000-0000-060e-2b3401040101", "05010c00-0000-0000-060e-2b3401040101" ), +} + +strongrefs = { +"kAAFTypeID_ContentStorageStrongReference" : ("05020100-0000-0000-060e-2b3401040101", "0d010101-0101-1800-060e-2b3402060101" ), +"kAAFTypeID_DictionaryStrongReference" : ("05020200-0000-0000-060e-2b3401040101", "0d010101-0101-2200-060e-2b3402060101" ), +"kAAFTypeID_EssenceDescriptorStrongReference" : ("05020300-0000-0000-060e-2b3401040101", "0d010101-0101-2400-060e-2b3402060101" ), +"kAAFTypeID_NetworkLocatorStrongReference" : ("05020400-0000-0000-060e-2b3401040101", "0d010101-0101-3200-060e-2b3402060101" ), +"kAAFTypeID_OperationGroupStrongReference" : ("05020500-0000-0000-060e-2b3401040101", "0d010101-0101-0a00-060e-2b3402060101" ), +"kAAFTypeID_SegmentStrongReference" : ("05020600-0000-0000-060e-2b3401040101", "0d010101-0101-0300-060e-2b3402060101" ), +"kAAFTypeID_SourceClipStrongReference" : ("05020700-0000-0000-060e-2b3401040101", "0d010101-0101-1100-060e-2b3402060101" ), +"kAAFTypeID_SourceReferenceStrongReference" : ("05020800-0000-0000-060e-2b3401040101", "0d010101-0101-1000-060e-2b3402060101" ), +"kAAFTypeID_ClassDefinitionStrongReference" : ("05020900-0000-0000-060e-2b3401040101", "0d010101-0201-0000-060e-2b3402060101" ), +"kAAFTypeID_CodecDefinitionStrongReference" : ("05020a00-0000-0000-060e-2b3401040101", "0d010101-0101-1f00-060e-2b3402060101" ), +"kAAFTypeID_ComponentStrongReference" : ("05020b00-0000-0000-060e-2b3401040101", "0d010101-0101-0200-060e-2b3402060101" ), +"kAAFTypeID_ContainerDefinitionStrongReference" : ("05020c00-0000-0000-060e-2b3401040101", "0d010101-0101-2000-060e-2b3402060101" ), +"kAAFTypeID_ControlPointStrongReference" : ("05020d00-0000-0000-060e-2b3401040101", "0d010101-0101-1900-060e-2b3402060101" ), +"kAAFTypeID_DataDefinitionStrongReference" : ("05020e00-0000-0000-060e-2b3401040101", "0d010101-0101-1b00-060e-2b3402060101" ), +"kAAFTypeID_EssenceDataStrongReference" : ("05020f00-0000-0000-060e-2b3401040101", "0d010101-0101-2300-060e-2b3402060101" ), +"kAAFTypeID_IdentificationStrongReference" : ("05021000-0000-0000-060e-2b3401040101", "0d010101-0101-3000-060e-2b3402060101" ), +"kAAFTypeID_InterpolationDefinitionStrongReference" : ("05021100-0000-0000-060e-2b3401040101", "0d010101-0101-2100-060e-2b3402060101" ), +"kAAFTypeID_LocatorStrongReference" : ("05021200-0000-0000-060e-2b3401040101", "0d010101-0101-3100-060e-2b3402060101" ), +"kAAFTypeID_MobStrongReference" : ("05021300-0000-0000-060e-2b3401040101", "0d010101-0101-3400-060e-2b3402060101" ), +"kAAFTypeID_MobSlotStrongReference" : ("05021400-0000-0000-060e-2b3401040101", "0d010101-0101-3800-060e-2b3402060101" ), +"kAAFTypeID_OperationDefinitionStrongReference" : ("05021500-0000-0000-060e-2b3401040101", "0d010101-0101-1c00-060e-2b3402060101" ), +"kAAFTypeID_ParameterStrongReference" : ("05021600-0000-0000-060e-2b3401040101", "0d010101-0101-3c00-060e-2b3402060101" ), +"kAAFTypeID_ParameterDefinitionStrongReference" : ("05021700-0000-0000-060e-2b3401040101", "0d010101-0101-1d00-060e-2b3402060101" ), +"kAAFTypeID_PluginDefinitionStrongReference" : ("05021800-0000-0000-060e-2b3401040101", "0d010101-0101-1e00-060e-2b3402060101" ), +"kAAFTypeID_PropertyDefinitionStrongReference" : ("05021900-0000-0000-060e-2b3401040101", "0d010101-0202-0000-060e-2b3402060101" ), +"kAAFTypeID_TaggedValueStrongReference" : ("05021a00-0000-0000-060e-2b3401040101", "0d010101-0101-3f00-060e-2b3402060101" ), +"kAAFTypeID_TypeDefinitionStrongReference" : ("05021b00-0000-0000-060e-2b3401040101", "0d010101-0203-0000-060e-2b3402060101" ), +"kAAFTypeID_KLVDataStrongReference" : ("05021c00-0000-0000-060e-2b3401040101", "0d010101-0101-4000-060e-2b3402060101" ), +"kAAFTypeID_FileDescriptorStrongReference" : ("05021d00-0000-0000-060e-2b3401040101", "0d010101-0101-2500-060e-2b3402060101" ), +"kAAFTypeID_RIFFChunkStrongReference" : ("05021e00-0000-0000-060e-2b3401040101", "0d010101-0101-4f00-060e-2b3402060101" ), +"kAAFTypeID_DescriptiveFrameworkStrongReference" : ("05021f00-0000-0000-060e-2b3401040101", "0d010401-0000-0000-060e-2b3402060101" ), +"kAAFTypeID_KLVDataDefinitionStrongReference" : ("05022000-0000-0000-060e-2b3401040101", "0d010101-0101-4d00-060e-2b3402060101" ), +"kAAFTypeID_TaggedValueDefinitionStrongReference" : ("05022100-0000-0000-060e-2b3401040101", "0d010101-0101-4c00-060e-2b3402060101" ), +"kAAFTypeID_DescriptiveObjectStrongReference" : ("05022200-0000-0000-060e-2b3401040101", "0d010400-0000-0000-060e-2b3402060101" ), +} + +weakrefs = { +"ClassDefinitionWeakReference" : ("05010100-0000-0000-060e-2b3401040101", "0d010101-0201-0000-060e-2b3402060101", + ("0d010301-0101-0100-060e-2b3401010102", "06010107-0700-0000-060e-2b3401010102", ) +), +"ContainerDefinitionWeakReference" : ("05010200-0000-0000-060e-2b3401040101", "0d010101-0101-2000-060e-2b3402060101", + ("0d010301-0102-0100-060e-2b3401010102", "06010104-0202-0000-060e-2b3401010102", "06010104-0508-0000-060e-2b3401010102", ) +), +"DataDefinitionWeakReference" : ("05010300-0000-0000-060e-2b3401040101", "0d010101-0101-1b00-060e-2b3402060101", + ("0d010301-0102-0100-060e-2b3401010102", "06010104-0202-0000-060e-2b3401010102", "06010104-0505-0000-060e-2b3401010102", ) +), +"InterpolationDefinitionWeakReference" : ("05010500-0000-0000-060e-2b3401040101", "0d010101-0101-2100-060e-2b3402060101", + ("0d010301-0102-0100-060e-2b3401010102", "06010104-0202-0000-060e-2b3401010102", "06010104-0509-0000-060e-2b3401010102", ) +), +"MobWeakReference" : ("05010600-0000-0000-060e-2b3401040101", "0d010101-0101-3400-060e-2b3402060101", + ("0d010301-0102-0100-060e-2b3401010102", "06010104-0201-0000-060e-2b3401010102", "06010104-0501-0000-060e-2b3401010102", ) +), +"OperationDefinitionWeakReference" : ("05010700-0000-0000-060e-2b3401040101", "0d010101-0101-1c00-060e-2b3402060101", + ("0d010301-0102-0100-060e-2b3401010102", "06010104-0202-0000-060e-2b3401010102", "06010104-0503-0000-060e-2b3401010102", ) +), +"ParameterDefinitionWeakReference" : ("05010800-0000-0000-060e-2b3401040101", "0d010101-0101-1d00-060e-2b3402060101", + ("0d010301-0102-0100-060e-2b3401010102", "06010104-0202-0000-060e-2b3401010102", "06010104-0504-0000-060e-2b3401010102", ) +), +"TypeDefinitionWeakReference" : ("05010900-0000-0000-060e-2b3401040101", "0d010101-0203-0000-060e-2b3402060101", + ("0d010301-0101-0100-060e-2b3401010102", "06010107-0800-0000-060e-2b3401010102", ) +), +"PluginDefinitionWeakReference" : ("05010a00-0000-0000-060e-2b3401040101", "0d010101-0101-1e00-060e-2b3402060101", + ("0d010301-0102-0100-060e-2b3401010102", "06010104-0202-0000-060e-2b3401010102", "06010104-0506-0000-060e-2b3401010102", ) +), +"CodecDefinitionWeakReference" : ("05010b00-0000-0000-060e-2b3401040101", "0d010101-0101-1f00-060e-2b3402060101", + ("0d010301-0102-0100-060e-2b3401010102", "06010104-0202-0000-060e-2b3401010102", "06010104-0507-0000-060e-2b3401010102", ) +), +"PropertyDefinitionWeakReference" : ("05010c00-0000-0000-060e-2b3401040101", "0d010101-0202-0000-060e-2b3402060101", + ("0d010301-0101-0100-060e-2b3401010102", "06010107-0700-0000-060e-2b3401010102", "06010107-0200-0000-060e-2b3401010102", ) +), +} + diff --git a/aaf2/mxf.py b/aaf2/mxf.py new file mode 100644 index 0000000..61999d4 --- /dev/null +++ b/aaf2/mxf.py @@ -0,0 +1,1027 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) +import sys +import struct +import datetime + +from io import BytesIO +import io + +from .utils import (read_u8, read_u16be, + read_u32be, read_s32be, + read_u64be, read_s64be, + int_from_bytes) +from .mobid import MobID +from .model import datadefs +from .auid import AUID + +MXF_CLASSES = {} + +def register_mxf_class(classobj): + MXF_CLASSES[classobj.class_id] = classobj + return classobj + + +class MXFRef(AUID): + pass + +class MXFRefArray(list): + pass + +def read_auid_be(f): + data = f.read(16) + if data: + return AUID(bytes_be=data) + +def read_strongref(f): + data = f.read(16) + if data: + return MXFRef(bytes_be=data) + + +def decode_strong_ref_array(data): + f = BytesIO(data) + count = read_u32be(f) + f.read(4) + refs = MXFRefArray() + for i in range(count): + refs.append(read_strongref(f)) + return refs + + +def decode_utf16be(data): + return data.decode('utf-16be').split(u'\x00')[0] + +def decode_auid(data): + return AUID(bytes_be=data) + +def reverse_auid(data): + new = data.hex[16:] + data.hex[:16] + return AUID(new) + +def decode_datadef(data): + orig = AUID(bytes_be=data) + datadef = reverse_auid(orig) + name = datadefs.DataDefs.get(str(datadef), (None, None))[0] + return name + +def decode_strongref(data): + return MXFRef(bytes_be=data) + +def decode_indirect_value(data): + data = bytearray(data) + typedef = reverse_auid(decode_auid(data[:16])) + if typedef == AUID("00060e2b-3401-0401-4c00-021001000000"): + assert data[16] == 0x01 # byte order? + return data[17:].decode('utf-16le').rstrip('\x00') + elif typedef == AUID("00060e2b-3401-0401-4201-100200000000"): + assert data[16] == 0x01# byte order? + return data[17:].decode('utf-16be').rstrip('\x00') + elif typedef == AUID("00060e2b-3401-0401-4c00-070101000000"): + assert data[16] == 0x01# byte order? + return struct.unpack(b" 0: + line_map.append(read_s32be(f)) + else: + line_map.append(0) + if count > 1: + line_map.append(read_s32be(f)) + else: + line_map.append(0) + return line_map + +def decode_pixel_layout(data): + f = BytesIO(data) + layout = [] + for i in range(8): + code = read_u8(f) + depth = read_u8(f) + if not code: + break + layout.append({'Code':code, 'Size':depth}) + return layout + +def decode_timestamp(data): + t = read_u64be(BytesIO(data)) + + year = t >> 48 + month = t >> 40 & 0xFF + day = t >> 32 & 0xFF + hour = t >> 24 & 0xFF + minute = t >> 16 & 0xFF + sec = t >> 8 & 0xFF + try: + d = datetime.datetime(year, month, day, hour, minute, sec) + return d + except: + return datetime.datetime.now() + + +def decode_mob_id(data): + uid1 = AUID(bytes_be=data[:16]) + uid2 = AUID(bytes_be=data[16:]) + m = MobID(str(uid1) + str(uid2)) + return m + +def ama_path(path): + prefix ="file://" + return prefix + path + +class MXFObject(object): + def __init__(self): + self.instance_id = None + self.data = {} + self.root = None + + def create_aaf_instance(): + pass + + def read_tag(self, tag, data): + if tag == 0x3c0a: + self.instance_id = decode_auid(data) + + def read_properties(self, f, length, local_tags): + for tag, data in iter_tags(f, length): + self.read_tag(tag, data) + uid = local_tags.get(tag, None) + + if uid == AUID("a0240060-94eb-75cb-ce2a-ca5051ab11d3"): + self.data['FrameSampleSize'] = read_s32be(BytesIO(data)) + elif uid == AUID("a0240060-94eb-75cb-ce2a-ca4d51ab11d3"): + self.data['ResolutionID'] = read_s32be(BytesIO(data)) + elif uid == AUID("a0220060-94eb-75cb-96c4-69924f6211d3"): + self.data['AppCode'] = read_s32be(BytesIO(data)) + elif uid == AUID("060e2b34-0101-0109-0601-010406100000"): + self.data['SubDescriptors'] = decode_strong_ref_array(data) + elif uid == AUID("a01c0004-ac96-9f50-6095-818347b111d4"): + self.data["MobAttributeList"] = decode_strong_ref_array(data) + elif uid == AUID("a01c0004-ac96-9f50-6095-818547b111d4"): + self.data["TaggedValueAttributeList"] = decode_strong_ref_array(data) + + def resolve_ref(self, key): + ref = self.data.get(key, None) + if ref: + obj = self.root.resolve(ref) + if obj: + return obj + raise Exception("unable to resolve: %s %s %s" % (key,auid_to_str_list(ref, sep=' '), str(ref)) ) + + def iter_strong_refs(self, key): + for ref in self.data.get(key, []): + # print(ref, auid_to_str_list(ref, sep=' ')) + yield self.root.resolve(ref) + + + def __repr__(self): + return str(self.data) + +@register_mxf_class +class MXFPreface(MXFObject): + class_id = AUID("060e2b34-0253-0101-0d01-010101012f00") + def read_tag(self, tag, data): + super(MXFPreface, self).read_tag(tag, data) + + if tag == 0x3b09: + self.data['OperationalPattern'] = decode_auid(data) + elif tag == 0x3b03: + self.data['ContentStorage'] = decode_strongref(data) + +@register_mxf_class +class MXFContentStorage(MXFObject): + class_id = AUID("060e2b34-0253-0101-0d01-010101011800") + def read_tag(self, tag, data): + super(MXFContentStorage, self).read_tag(tag, data) + + if tag == 0x1902: + self.data['EssenceContainerData'] = decode_strong_ref_array(data) + elif tag == 0x1901: + self.data['Packages'] = decode_strong_ref_array(data) + + +class MXFPackage(MXFObject): + + def read_tag(self, tag, data): + super(MXFPackage, self).read_tag(tag, data) + + if tag == 0x4403: + self.data['Slots'] = decode_strong_ref_array(data) + elif tag == 0x4401: + self.data['MobID'] = decode_mob_id(data) + elif tag == 0x4402: + self.data['Name'] = decode_utf16be(data) + elif tag == 0x4701: + self.data['Descriptor'] = decode_strongref(data) + elif tag == 0x4404: + self.data['LastModified'] = decode_timestamp(data) + elif tag == 0x4405: + self.data['CreationTime'] = decode_timestamp(data) + elif tag == 0x4408: + self.data['UsageCode'] = decode_auid(data) # doesn't appear reversed... + elif tag == 0x4406: + self.data['UserComments'] = decode_strong_ref_array(data) + + @property + def mob_id(self): + return self.data.get('MobID', None) + + def link(self): + mob = self.create_aaf_instance() + + mob.mob_id = self.mob_id + self.root.aaf.content.mobs.append(mob) + + name = self.data.get('Name', None) + if name: + mob.name = name + + for track in self.iter_strong_refs("Slots"): + # print(type(track), track) + + if isinstance(track, (MXFStaticTrack, MXFEventTrack)): + continue + + timeline = track.link() + mob.slots.append(timeline) + + for key in ('LastModified', 'CreationTime', 'UsageCode', 'AppCode'): + if key in self.data: + mob[key].value = self.data[key] + + for item in self.iter_strong_refs('UserComments'): + tag = item.link() + if tag: + mob['UserComments'].append(tag) + + for item in self.iter_strong_refs('MobAttributeList'): + tag = item.link() + if tag: + mob['MobAttributeList'].append(tag) + + if 'Descriptor' in self.data: + d = self.resolve_ref('Descriptor') + mob.descriptor = d.link() + + return mob + +@register_mxf_class +class MXFMaterialPackage(MXFPackage): + class_id = AUID("060e2b34-0253-0101-0d01-010101013600") + def create_aaf_instance(self): + return self.root.aaf.create.MasterMob() + +@register_mxf_class +class MXFSourcePackage(MXFPackage): + class_id = AUID("060e2b34-0253-0101-0d01-010101013700") + def create_aaf_instance(self): + return self.root.aaf.create.SourceMob() + +@register_mxf_class +class MXFTrack(MXFObject): + class_id = AUID("060e2b34-0253-0101-0d01-010101013b00") + + def create_aaf_instance(self): + return self.root.aaf.create.TimelineMobSlot() + + def read_tag(self, tag, data): + super(MXFTrack, self).read_tag(tag, data) + + if tag == 0x4b02: + self.data['Origin'] = read_s64be(BytesIO(data)) + elif tag == 0x4b01: + self.data['EditRate'] = decode_rational(data) + elif tag == 0x4803: + self.data['Segment'] = decode_strongref(data) + elif tag == 0x4804: + self.data['PhysicalTrackNumber'] = read_u32be(BytesIO(data)) + elif tag == 0x4801: + self.data['SlotID'] = read_u32be(BytesIO(data)) + elif tag == 0x4802: + self.data['SlotName'] = decode_utf16be(data) + + def link(self): + timeline = self.create_aaf_instance() + + for key in ('SlotID', 'SlotName', 'EditRate', 'PhysicalTrackNumber', 'Origin'): + if key in self.data: + timeline[key].value = self.data[key] + + segment = self.resolve_ref('Segment') + timeline.segment = segment.link() + return timeline + +@register_mxf_class +class MXFStaticTrack(MXFTrack): + class_id = AUID("060e2b34-0253-0101-0d01-010101013a00") + + def create_aaf_instance(self): + return self.root.aaf.create.StaticMobSlot() + +@register_mxf_class +class MXFEventTrack(MXFTrack): + class_id = AUID("060e2b34-0253-0101-0d01-010101013900") + + def create_aaf_instance(self): + return self.root.aaf.create.EventMobSlot() + +class MXFComponent(MXFObject): + def read_tag(self, tag, data): + super(MXFComponent, self).read_tag(tag, data) + + if tag == 0x1001: + self.data['Components'] = decode_strong_ref_array(data) + elif tag == 0x1201: + self.data['StartTime'] = read_u64be(BytesIO(data)) + elif tag == 0x1102: + self.data['SourceMobSlotID'] = read_u32be(BytesIO(data)) + elif tag == 0x1101: + self.data['SourceID'] = decode_mob_id(data) + elif tag == 0x0202: + self.data['Length'] = read_u64be(BytesIO(data)) + elif tag == 0x0201: + self.data['DataDef'] = decode_datadef(data) + elif tag == 0x1503: + self.data['Drop'] = read_u8(BytesIO(data)) == 1 + elif tag == 0x1502: + self.data['FPS'] = read_u16be(BytesIO(data)) + elif tag == 0x1501: + self.data['Start'] = read_u64be(BytesIO(data)) + elif tag == 0x0501: + self.data['Choices'] = decode_strong_ref_array(data) + elif tag == 0x0502: + self.data['StillFrame'] = decode_strongref(data) + elif tag == 0x0d01: + self.data['InputSegment'] = decode_strongref(data) + elif tag == 0x0d02: + self.data['PulldownKind'] = read_u8(BytesIO(data)) + elif tag == 0x0d03: + self.data['PulldownDirection'] = read_u8(BytesIO(data)) + elif tag == 0x0d04: + self.data['PhaseFrame'] = read_s32be(BytesIO(data)) + elif tag == 0x0e01: + self.data['RelativeScope'] = read_s32be(BytesIO(data)) + elif tag == 0x0e02: + self.data['RelativeSlot'] = read_s32be(BytesIO(data)) + +@register_mxf_class +class MXFSequence(MXFComponent): + class_id = AUID("060e2b34-0253-0101-0d01-010101010f00") + + def create_aaf_instance(self): + return self.root.aaf.create.Sequence() + + def link(self): + s = self.create_aaf_instance() + + s.media_kind = self.data['DataDef'] #or 'DataDef_Unknown' + s.length = self.data.get('Length', 0) + # for item in self.data['Components']: + # print(auid_to_str_list(item, sep=' '), item) + for item in self.iter_strong_refs('Components'): + s['Components'].append(item.link()) + return s + +@register_mxf_class +class MXFSourceClip(MXFComponent): + class_id = AUID("060e2b34-0253-0101-0d01-010101011100") + + def create_aaf_instance(self): + return self.root.aaf.create.SourceClip() + + def link(self): + s = self.create_aaf_instance() + s.media_kind = self.data['DataDef'] or 'DataDef_Unknown' + for key in ('SourceID', 'SourceMobSlotID', 'StartTime', 'Length'): + s[key].value = self.data.get(key, None) + return s + +@register_mxf_class +class MXFTimecode(MXFComponent): + class_id = AUID("060e2b34-0253-0101-0d01-010101011400") + + def create_aaf_instance(self): + return self.root.aaf.create.Timecode() + + def link(self): + + s = self.create_aaf_instance() + for key in ('FPS', 'Drop', 'Start'): + s[key].value = self.data[key] + + s.media_kind = self.data['DataDef'] + s.length = self.data['Length'] + + return s + +@register_mxf_class +class MXFPulldown(MXFComponent): + class_id = AUID("060e2b34-0253-0101-0d01-010101010c00") + + def create_aaf_instance(self): + return self.root.aaf.create.Pulldown() + + def link(self): + + p = self.create_aaf_instance() + p.media_kind = self.data['DataDef'] + p.length = self.data['Length'] + + p['InputSegment'].value = self.resolve_ref('InputSegment').link() + + for key in ('PhaseFrame', 'PulldownDirection', 'PulldownKind'): + p[key].value = self.data[key] + return p + +@register_mxf_class +class MXFFiller(MXFComponent): + class_id = AUID("060e2b34-0253-0101-0d01-010101010900") + + def create_aaf_instance(self): + return self.root.aaf.create.Filler() + + def link(self): + c = self.create_aaf_instance() + c.media_kind = self.data['DataDef'] + c.length = self.data['Length'] + return c + +@register_mxf_class +class MXFScopeReference(MXFComponent): + class_id = AUID("060e2b34-0253-0101-0d01-010101010d00") + + def create_aaf_instance(self): + return self.root.aaf.create.ScopeReference() + + def link(self): + c = self.create_aaf_instance() + c.media_kind = self.data['DataDef'] + c.length = self.data['Length'] + for key in ('RelativeSlot', 'RelativeScope'): + c[key].value = self.data[key] + + return c + +@register_mxf_class +class MXFEssenceGroup(MXFComponent): + class_id = AUID("060e2b34-0253-0101-0d01-010101010500") + + def create_aaf_instance(self): + return self.root.aaf.create.EssenceGroup() + + def link(self): + + e = self.create_aaf_instance() + e.media_kind = self.data['DataDef'] + e.length = self.data['Length'] + e['Choices'].value = [item.link() for item in self.iter_strong_refs('Choices')] + + return e + +class MXFDescriptor(MXFObject): + + def read_tag(self, tag, data): + super(MXFDescriptor, self).read_tag(tag, data) + if tag == 0x3f01: + self.data['FileDescriptors'] = decode_strong_ref_array(data) + elif tag == 0x3004: + self.data['ContainerFormat'] = reverse_auid(decode_auid(data)) + elif tag == 0x3005: + self.data['CodecDefinition'] = reverse_auid(decode_auid(data)) + elif tag == 0x3006: + self.data['LinkedTrackID'] = read_u32be(BytesIO(data)) + elif tag == 0x3203: + self.data['StoredWidth'] = read_u32be(BytesIO(data)) + elif tag == 0x3202: + self.data['StoredHeight'] = read_u32be(BytesIO(data)) + elif tag == 0x3208: #this is display + self.data['SampledHeight'] = read_u32be(BytesIO(data)) + elif tag == 0x3209: #this is display + self.data['SampledWidth'] = read_u32be(BytesIO(data)) + elif tag == 0x320d: + self.data['VideoLineMap'] = decode_video_line_map(data) + elif tag == 0x3211: + self.data['ImageAlignmentOffset'] = read_u32be(BytesIO(data)) + elif tag == 0x3002: + self.data['Length'] = read_s64be(BytesIO(data)) + elif tag == 0x3001: + self.data['SampleRate'] = decode_rational(data) + elif tag == 0x3d03: + self.data['AudioSamplingRate'] = decode_rational(data) + elif tag == 0x3d0a: + self.data['BlockAlign'] = read_u16be(BytesIO(data)) + elif tag == 0x3d01: + self.data['QuantizationBits'] = read_u32be(BytesIO(data)) + elif tag == 0x3d07: + self.data['Channels'] = read_u32be(BytesIO(data)) + elif tag == 0x3d09: + self.data['AverageBPS'] = read_u32be(BytesIO(data)) + elif tag == 0x3d02: + self.data['Locked'] = read_u8(BytesIO(data)) == 1 + elif tag == 0x3301: + self.data['ComponentWidth'] = read_u32be(BytesIO(data)) + elif tag == 0x320c: + self.data['FrameLayout'] = read_u8(BytesIO(data)) + elif tag == 0x320e: + self.data['ImageAspectRatio'] = decode_rational(data) + elif tag == 0x3d06: + self.data['SoundCompression'] = reverse_auid(decode_auid(data)) + elif tag == 0x3201: + self.data['Compression'] = reverse_auid(decode_auid(data)) + elif tag == 0x3302: + self.data['HorizontalSubsampling'] = read_u32be(BytesIO(data)) + elif tag == 0x3308: + self.data['VerticalSubsampling'] = read_u32be(BytesIO(data)) + elif tag == 0x2f01: + self.data['Locator'] = decode_strong_ref_array(data) + elif tag == 0x3401: + self.data['PixelLayout'] = decode_pixel_layout(data) + +@register_mxf_class +class MXFMultipleDescriptor(MXFDescriptor): + class_id = AUID("060e2b34-0253-0101-0d01-010101014400") + + def create_aaf_instance(self): + return self.root.aaf.create.MultipleDescriptor() + + def link(self): + d = self.create_aaf_instance() + for item in self.iter_strong_refs("FileDescriptors"): + # if isinstance(item, MXFAES3AudioDescriptor): + # continue + d['FileDescriptors'].append(item.link()) + d['Length'].value = self.data.get('Length', 0) + + n = self.root.aaf.create.NetworkLocator() + n['URLString'].value = ama_path(self.root.path) + d['Locator'].append(n) + + d['SampleRate'].value = self.data['SampleRate'] + + if self.root.ama: + n = self.root.aaf.create.NetworkLocator() + n['URLString'].value = ama_path(self.root.path) + d['Locator'].append(n) + d['MediaContainerGUID'].value = AUID("60eb8921-2a02-4406-891c-d9b6a6ae0645") + + return d + +@register_mxf_class +class MXFCDCIDescriptor(MXFDescriptor): + class_id = AUID("060e2b34-0253-0101-0d01-010101012800") + + def create_aaf_instance(self): + return self.root.aaf.create.CDCIDescriptor() + + def link(self): + d = self.create_aaf_instance() + + # required + for key in ('ComponentWidth', 'HorizontalSubsampling', 'ImageAspectRatio', + 'StoredWidth', 'VideoLineMap', 'StoredHeight', 'SampleRate', 'FrameLayout'): + d[key].value = self.data[key] + + d['Length'].value = self.data.get('Length', 0) + + + # optional + for key in ('FrameSampleSize', 'ResolutionID', 'Compression', 'VerticalSubsampling', + 'SampledWidth', 'SampledHeight'): + if key in self.data: + d[key].value = self.data[key] + + for item in self.iter_strong_refs("Locator"): + d['Locator'].append(item.link()) + n = self.root.aaf.create.NetworkLocator() + n['URLString'].value = ama_path(self.root.path) + d['Locator'].append(n) + + d['ContainerFormat'].value = self.root.aaf.dictionary.lookup_containerdef("AAFKLV") + if self.root.ama: + n = self.root.aaf.create.NetworkLocator() + n['URLString'].value = ama_path(self.root.path) + d['Locator'].append(n) + d['MediaContainerGUID'].value = AUID("60eb8921-2a02-4406-891c-d9b6a6ae0645") + + return d + +@register_mxf_class +class MXFRGBADescriptor(MXFDescriptor): + class_id = AUID("060e2b34-0253-0101-0d01-010101012900") + + def create_aaf_instance(self): + return self.root.aaf.create.RGBADescriptor() + + def link(self): + d = self.create_aaf_instance() + + for key in ('ImageAspectRatio', 'StoredWidth', 'FrameLayout', 'PixelLayout', + 'VideoLineMap', 'StoredHeight', 'SampleRate', 'Length'): + d[key].value = self.data[key] + + for key in ('FrameSampleSize','SampledWidth', 'SampledHeight', 'Compression',): + if key in self.data: + d[key].value = self.data[key] + + d['ContainerFormat'].value = self.root.aaf.dictionary.lookup_containerdef("AAFKLV") + n = self.root.aaf.create.NetworkLocator() + n['URLString'].value = ama_path(self.root.path) + d['Locator'].append(n) + if self.root.ama: + n = self.root.aaf.create.NetworkLocator() + n['URLString'].value = ama_path(self.root.path) + d['Locator'].append(n) + d['MediaContainerGUID'].value = AUID("60eb8921-2a02-4406-891c-d9b6a6ae0645") + + + return d + +@register_mxf_class +class MXFANCDataDescriptor(MXFDescriptor): + class_id = AUID("060e2b34-0253-0101-0d01-010101015c00") + + def create_aaf_instance(self): + return self.root.aaf.create.ANCDataDescriptor() + + def link(self): + d = self.create_aaf_instance() + for key in ('SampleRate', 'Length',): + d[key].value = self.data[key] + return d + +@register_mxf_class +class MXFMPEG2VideoDescriptor(MXFCDCIDescriptor): + class_id = AUID("060e2b34-0253-0101-0d01-010101015100") + + def link(self): + # 060e2b34.04010103.04010202.01040300 + self.data['ResolutionID'] = 4076 #XDCAM HD 50Mbit + + return super(MXFMPEG2VideoDescriptor, self).link() + +@register_mxf_class +class MXFPCMDescriptor(MXFDescriptor): + class_id = AUID("060e2b34-0253-0101-0d01-010101014800") + + def create_aaf_instance(self): + return self.root.aaf.create.PCMDescriptor() + + def link(self): + d = self.create_aaf_instance() + # required + for key in ('BlockAlign', 'AverageBPS', 'Channels', + 'QuantizationBits', 'AudioSamplingRate', 'SampleRate', 'Length'): + d[key].value = self.data[key] + n = self.root.aaf.create.NetworkLocator() + n['URLString'].value = ama_path(self.root.path) + d['Locator'].append(n) + if self.root.ama: + n = self.root.aaf.create.NetworkLocator() + n['URLString'].value = ama_path(self.root.path) + d['Locator'].append(n) + d['MediaContainerGUID'].value = AUID("60eb8921-2a02-4406-891c-d9b6a6ae0645") + + return d + +@register_mxf_class +class MXFAES3AudioDescriptor(MXFPCMDescriptor): + class_id = AUID("060e2b34-0253-0101-0d01-010101014700") + +@register_mxf_class +class MXFSoundDescriptor(MXFPCMDescriptor): + class_id = AUID("060e2b34-0253-0101-0d01-010101014200") + +@register_mxf_class +class MXFImportDescriptor(MXFDescriptor): + class_id = AUID("060e2b34-0253-0101-0d01-010101014a00") + + def create_aaf_instance(self): + return self.root.aaf.create.ImportDescriptor() + + def link(self): + d = self.create_aaf_instance() + n = self.root.aaf.create.NetworkLocator() + n['URLString'].value = ama_path(self.root.path) + d['Locator'].append(n) + + return d + +@register_mxf_class +class MXFTapeDescriptor(MXFDescriptor): + class_id = AUID("060e2b34-0253-0101-0d01-010101012e00") + + def create_aaf_instance(self): + return self.root.aaf.create.TapeDescriptor() + + def link(self): + return self.create_aaf_instance() + +class MXFLocator(MXFObject): + + def read_tag(self, tag, data): + super(MXFLocator, self).read_tag(tag, data) + + if tag == 0x4001: + self.data['URLString'] = decode_utf16be(data) + +@register_mxf_class +class MXFNetworkLocator(MXFLocator): + class_id = AUID("060e2b34-0253-0101-0d01-010101013200") + + def create_aaf_instance(self): + return self.root.aaf.create.NetworkLocator() + + def link(self): + n = self.create_aaf_instance() + n['URLString'].value = self.data['URLString'] + return n + +@register_mxf_class +class MXFEssenceData(MXFObject): + class_id = AUID("060e2b34-0253-0101-0d01-010101012300") + def read_tag(self, tag, data): + super(MXFEssenceData, self).read_tag(tag, data) + + if tag == 0x2701: + self.data['MobID'] = decode_mob_id(data) + +@register_mxf_class +class MXFTaggedValue(MXFObject): + class_id = AUID("060e2b34-0253-0101-0d01-010101013f00") + + def create_aaf_instance(self): + return self.root.aaf.create.TaggedValue() + + def read_tag(self, tag, data): + super(MXFTaggedValue, self).read_tag(tag, data) + + if tag == 0x5001: + self.data['Name'] = decode_utf16be(data) + elif tag == 0x5003: + self.data['Value'] = decode_indirect_value(data) + + def link(self): + v = self.data.get("Value", None) + if v is None: + return + + tag = self.create_aaf_instance() + tag.name = self.data["Name"] + tag.value = v + + for item in self.iter_strong_refs('TaggedValueAttributeList'): + attr = item.link() + if attr: + tag['TaggedValueAttributeList'].append(attr) + + return tag + +def ber_length(f): + length = read_u8(f) + if length > 127: + data = bytearray(length - 128) + bytes_read = f.readinto(data) + assert bytes_read == len(data) + length = int_from_bytes(data, byte_order='big') + return length + + +def iter_kl(f): + pos = f.tell() + while True: + # read the key + f.seek(pos) + key = read_auid_be(f) + if not key: + break + # read the ber_length + length = ber_length(f) + + pos = f.tell() + length + yield key, length + +def iter_tags(f, length): + while length > 0: + tag = read_u16be(f) + size = read_u16be(f) + # print(" tag 0x%04x %d" % (tag, size)) + if size: + yield tag, f.read(size) + length -= 4 + size + +def auid_to_str_list(v, sep=',', prefix=""): + return sep.join('%s%02x' % (prefix, i) for i in bytearray(v.bytes_be)) + +class MXFFile(object): + + def __init__(self, path): + self.objects = {} + self.local_tags = {} + self.preface = None + self.header_operation_pattern = None + self.header_partition_size = None + self.path = path + self.ama = False + self.aaf = None + with io.open(path, 'rb') as f: + + for key, length in iter_kl(f): + + # only read until the first body partition + if not self.header_partition_size is None and f.tell() > self.header_partition_size: + break + + if key == AUID("060e2b34-0205-0101-0d01-020101050100"): + self.local_tags = self.read_primer(f, length) + elif key == AUID("060e2b34-0205-0101-0d01-020101020400"): + self.read_header(f, length) + else: + # print('{') + # print(key, auid_to_str_list(key, sep='.')) + obj = self.read_object(f, key, length) + if obj: + # print(obj.__class__.__name__, obj.instance_id) + obj.root = self + self.objects[obj.instance_id] = obj + + if isinstance(obj, MXFPreface): + self.preface = obj + # print('}') + + def resolve(self, ref): + if isinstance(ref, MXFRef): + return self.objects.get(ref) + + @property + def content(self): + return self.preface.resolve_ref("ContentStorage") + + def packages(self): + if self.content: + for package in self.content.iter_strong_refs("Packages"): + yield package + + def material_packages(self): + for package in self.packages(): + if isinstance(package, MXFMaterialPackage): + yield package + + def link(self, f): + self.aaf = f + mobs = [] + for package in self.packages(): + if package.mob_id in f.content.mobs: + continue + + mobs.append(package.link()) + + return mobs + + def round_to_kag(self, pos, kag_size): + ret = (pos // kag_size) * kag_size + if ret == pos: + return pos + return pos + kag_size + + + def read_header(self, f, length): + major_version = read_u16be(f) + minor_version = read_u16be(f) + kag_size = read_u32be(f) + this_partion = read_u64be(f) + prev_partion = read_u64be(f) + footer_partion = read_u64be(f) + header_byte_count = read_u64be(f) + index_byte_count = read_u64be(f) + index_sid = read_u32be(f) + body_offset = read_u64be(f) + body_sid = read_u32be(f) + self.header_operation_pattern = read_auid_be(f) + + self.header_partition_size = (self.round_to_kag(length, kag_size) + + self.round_to_kag(header_byte_count, kag_size) + + self.round_to_kag(index_byte_count, kag_size)) + + def read_primer(self, f, length): + + item_num = read_u32be(f) + item_len = read_u32be(f) + if item_len != 18: + return + if item_num > 65536: + return + + tags = {} + for i in range(item_num): + tag = read_u16be(f) + uid = read_auid_be(f) + # print("%04x" % tag, ':', uid) + tags[tag] = uid + + return tags + + def read_object(self, f, key, length): + + b = bytearray(key.bytes_be) + if not b[5] == 0x53: + return + + obj_class = MXF_CLASSES.get(key, None) + if obj_class: + + obj = obj_class() + obj.root = self + obj.read_properties(f, length, self.local_tags) + return obj + else: + for tag, data in iter_tags(f, length): + pass + + def dump_flat(self): + for key,value in self.objects.items(): + print(value.__class__.__name__, key) + for p, v in value.data.items(): + print(" ",p, v) + + def dump(self, obj=None, space=""): + if obj is None: + obj = self.preface + + print (space, obj.__class__.__name__, obj.instance_id) + + space += " " + for key, value in sorted(obj.data.items()): + if isinstance(value, MXFRef): + c = self.objects.get(value, None) + if c: + self.dump(c, space) + else: + print(space, None) + + elif isinstance(value, MXFRefArray): + print (space, key) + for item in value: + c = self.objects.get(item, None) + if c: + self.dump(c, space + " ") + else: + print(space, None) + else: + print (space, key, value) + + @property + def operation_pattern(self): + + if self.header_operation_pattern: + op = self.header_operation_pattern + else: + op = self.preface.data.get('OperationalPattern', None) + + if not op: + return + + op = bytearray(op.bytes_be) + + prefix1 = bytearray([0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x01, 0x0d, 0x01, 0x02, 0x01]) + prefix2 = bytearray([0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x02, 0x0d, 0x01, 0x02, 0x01]) + prefix3 = bytearray([0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x03, 0x0d, 0x01, 0x02, 0x01]) + + prefix_valid = False + for prefix in (prefix1, prefix2, prefix3): + if op[:len(prefix)] == prefix: + prefix_valid = True + break + + if not prefix_valid: + return + + complexity = op[12] + + if complexity >= 1 and complexity <= 3: + package_complexity = op[13] + letter = {1:'a', 2:'b', 3:'c'}.get(package_complexity, None) + if letter: + return 'OP%d%s' % (complexity, letter) + + elif complexity >= 0x10 and complexity <= 0x7f: + if complexity == 0x10: + return 'OPAtom' diff --git a/aaf2/properties.py b/aaf2/properties.py new file mode 100644 index 0000000..32e9557 --- /dev/null +++ b/aaf2/properties.py @@ -0,0 +1,1361 @@ + +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +from io import BytesIO +import weakref +import struct +from .utils import ( + read_u8, + read_u16le, + read_u32le, + write_u8, + write_u16le, + write_u32le, + decode_utf16le, + encode_utf16le, + encode_utf16_array, + encode_auid_array, + encode_u16le, + encode_u32le, + encode_u8, + encode_s64le, + mangle_name, + ) +from .auid import AUID +from .mobid import MobID +from .exceptions import AAFPropertyError, AAFAttachError + +SF_DATA = 0x82 +SF_DATA_STREAM = 0x42 +SF_STRONG_OBJECT_REFERENCE = 0x22 +SF_STRONG_OBJECT_REFERENCE_VECTOR = 0x32 +SF_STRONG_OBJECT_REFERENCE_SET = 0x3A +SF_WEAK_OBJECT_REFERENCE = 0x02 +SF_WEAK_OBJECT_REFERENCE_VECTOR = 0x12 +SF_WEAK_OBJECT_REFERENCE_SET = 0x1A +SF_WEAK_OBJECT_REFERENCE_STORED_OBJECT_ID = 0x03 +SF_UNIQUE_OBJECT_ID = 0x86 +SF_OPAQUE_STREAM = 0x40 + +# not sure about these +SF_DATA_VECTOR = 0xD2 +SF_DATA_SET = 0xDA + +PROPERTY_VERSION=32 + +def writeonly(func): + def func_wrapper(self, *args, **kwargs): + if not self.writeable: + raise AAFPropertyError("file readonly") + result = func(self, *args, **kwargs) + self.mark_modified() + return result + + return func_wrapper + +CLASSDEF_AUID = AUID("0d010101-0101-0100-060e-2b3402060101") +TYPEDEF_AUID = AUID("0d010101-0203-0000-060e-2b3402060101") + +MOB_MOBID_AUID = AUID("01011510-0000-0000-060e-2b3401010101") +ESSENCEDATA_MOBID_AUID = AUID("06010106-0100-0000-060e-2b3401010102") + +class Property(object): + __slots__ = ('pid', 'format', 'version', 'data', 'parent', '_propertydef') + def __init__(self, parent, pid, format, version=PROPERTY_VERSION): + self.pid = pid + self.format = format + self.version = version + self.data = None + self._propertydef = None + self.parent = parent + + def format_name(self): + return str(property_formats[self.format].__name__) + + @property + def attached(self): + if self.parent.dir: + return True + return False + + @property + def writeable(self): + if self.parent.root.mode in ('rb', ): + return False + return True + + def decode(self): + pass + + def mark_modified(self): + if self.attached: + self.parent.root.manager.add_modified(self.parent) + + @property + def propertydef(self): + if self._propertydef: + return self._propertydef + + classdef = self.parent.classdef + if classdef is None: + return + + p = classdef.get_propertydef_from_pid(self.pid) + if p: + self._propertydef = p + return p + + # fall back to slow method if early in the bootstraping process + # seems to be on ClassDefinitions + for p in classdef.all_propertydefs(): + if p.pid == self.pid: + self._propertydef = p + return p + @property + def unique(self): + return self.propertydef.unique + + @property + def name(self): + propertydef = self.propertydef + if propertydef: + return propertydef.property_name + + @property + def typedef(self): + propertydef = self.propertydef + if propertydef: + return propertydef.typedef + + def copy(self, parent): + p = self.__class__(parent, self.pid, self.format, version=PROPERTY_VERSION) + p.data = bytes(self.data) + return p + + @property + def value(self): + d = self.data + if d is None: + return None + return self.typedef.decode(d) + + @value.setter + @writeonly + def value(self, value): + if self.data is not None and self.parent.dir and self.unique: + if self.propertydef.auid == MOB_MOBID_AUID: + self.parent.root.content.mobs.swap_unique_key(self.value, value) + elif self.propertydef.auid == ESSENCEDATA_MOBID_AUID: + self.parent.root.content.essencedata.swap_unique_key(self.value, value) + else: + raise AAFPropertyError("cannot change unique property value of attached object") + + if value is None: + self.remove_pid_entry() + return + + self.data = self.typedef.encode(value) + self.add_pid_entry() + + def add_pid_entry(self): + if not self.pid in self.parent.property_entries: + self.parent.property_entries[self.pid] = self + return self + + def remove_pid_entry(self): + if self.pid in self.parent.property_entries: + del self.parent.property_entries[self.pid] + + def __repr__(self): + name = self.name + if name: + return "<%s %s>" % (name, str(self.typedef)) + else: + return "<%s %d bytes>" % (self.__class__.__name__, len(self.data)) + +class StreamProperty(Property): + __slots__ = ('stream_name', 'dir') + def __init__(self, parent, pid, format, version=PROPERTY_VERSION): + super(StreamProperty, self).__init__(parent, pid, format, version) + self.stream_name = None + self.dir = None + + def copy(self, parent): + p = super(StreamProperty, self).copy(parent) + p.stream_name = self.stream_name + + a = self.open('r') + b = p.open("w") + + byte_size = a.dir.byte_size + read_size = self.parent.root.cfb.sector_size + + # copy stream data + while byte_size > 0: + d = a.read(read_size) + b.write(d) + byte_size -= read_size + + return p + + def decode(self): + # first byte is endianess + assert self.data[0:1] == b'\x55' # unspecified + self.stream_name = decode_utf16le(self.data[1:]) + + def encode(self, data): + return b'\x55' + encode_utf16le(data) + + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, str(self.stream_name)) + + def setup_stream(self): + if self.stream_name: + return + + self.stream_name = mangle_name(self.propertydef.property_name, self.pid, 32) + self.data = self.encode(self.stream_name) + self.add_pid_entry() + + def open(self, mode='r'): + self.setup_stream() + + if mode == 'r': + if self.attached: + stream = self.parent.dir.get(self.stream_name) + else: + stream = self.dir + + if not stream: + raise AAFPropertyError("cannot find stream: %s" % self.stream_name) + return stream.open(mode) + else: + if not self.writeable: + raise AAFPropertyError("file readonly") + + if self.attached: + return self.parent.dir.touch(self.stream_name).open(mode) + else: + if self.dir is None: + tmp_dir = self.parent.root.manager.create_temp_dir() + stream = tmp_dir.touch(self.stream_name).open(mode) + self.dir = stream.dir + return stream + + return self.dir.open(mode) + + def detach(self): + if self.stream_name is None: + raise AAFAttachError("stream has no name") + + stream = self.parent.dir.get(self.stream_name) + if not stream: + raise AAFAttachError("stream doesn't exists") + + stream_path = stream.path() + tmp = self.parent.root.manager.create_temp_dir().path() + self.dir = self.parent.root.cfb.move(stream_path, tmp + "/" + self.stream_name) + + def attach(self): + if self.dir is None: + return + + if self.parent.dir is None: + raise AAFAttachError("stream parent not attached") + + if self.stream_name is None: + raise AAFAttachError("stream has no name") + + stream = self.parent.dir.get(self.stream_name) + if stream: + raise AAFAttachError("dest stream already exists") + + stream_path = self.parent.dir.path() + "/" + self.stream_name + self.parent.root.cfb.move(self.dir.path(), stream_path) + + self.dir = None + + @property + def value(self): + return self.parent.dir.get(self.stream_name) + +class StrongRefProperty(Property): + __slots__ = ('ref', 'objectref') + def __init__(self, parent, pid, format, version=PROPERTY_VERSION): + super(StrongRefProperty, self).__init__(parent, pid, format, version) + self.ref = None + self.objectref = None + + @property + def object(self): + if self.objectref is None: + return None + elif isinstance(self.objectref, weakref.ref): + return self.objectref() + else: + return self.objectref + + @object.setter + def object(self, value): + if value is None: + self.objectref = None + elif self.attached: + self.objectref = weakref.ref(value) + else: + self.objectref = value + + def copy(self, parent): + p = super(StrongRefProperty, self).copy(parent) + p.ref = self.ref + + dir_entry = None + if parent.dir: + dir_entry = parent.dir.get(p.ref) + if dir_entry is None: + dir_entry = parent.dir.makedir(p.ref) + + p.object = self.value.copy(dir_entry) + return p + + def decode(self): + self.ref = decode_utf16le(self.data) + + def encode(self, data): + return encode_utf16le(data) + + def __repr__(self): + return "<%s %s to %s>" % (self.name, self.__class__.__name__, str(self.ref)) + + @property + def value(self): + if self.object: + return self.object + dir_entry = self.parent.dir.get(self.ref) + obj = None + if dir_entry: + obj = self.parent.root.manager.read_object(dir_entry) + self.object = obj + return obj + + + @value.setter + @writeonly + def value(self, value): + if value is None: + self.remove_pid_entry() + return + + typedef = self.typedef + ref_classdef = typedef.ref_classdef + + if not ref_classdef.isinstance(value.classdef): + raise TypeError("must be instance of: %s" % ref_classdef.class_name) + + if self.ref is None: + propdef = self.propertydef + self.ref = mangle_name(propdef.property_name, self.pid, 32) + self.data = self.encode(self.ref) + + # before assigning new object detach old + if self.object: + self.object.detach() + self.object = None + + self.object = value + if not self.pid in self.parent.property_entries: + self.parent.property_entries[self.pid] = self + + self.attach() + + def detach(self): + # convert to regular ref + self.objectref = self.value + + def attach(self): + + obj = self.value + + if not self.object: + return + + if not self.parent.dir: + return + + dir_entry = self.parent.dir.get(self.ref) + if dir_entry is None: + dir_entry = self.parent.dir.makedir(self.ref) + if self.object.dir != dir_entry: + self.object.attach(dir_entry) + + # convert to weakref + self.objectref = weakref.ref(obj) + + +class StrongRefVectorProperty(Property): + __slots__ = ('references', 'next_free_key', 'last_free_key','objects', '_index_name') + def __init__(self, parent, pid, format, version=PROPERTY_VERSION): + super(StrongRefVectorProperty, self).__init__(parent, pid, format, version) + self.references = [] + self._index_name = None + + # self.ref = None + self.next_free_key = 0 + self.last_free_key = 0xFFFFFFFF + + if self.attached: + self.objects = weakref.WeakValueDictionary() + else: + self.objects = {} + + def copy(self, parent): + p = super(StrongRefVectorProperty, self).copy(parent) + p.references = list(self.references) + + p.objects = {} + p.index_name = self.index_name + p.next_free_key = self.next_free_key + p.last_free_key = self.last_free_key + p.data = self.data + + for i, value in enumerate(self): + ref = self.index_ref_name(self.references[i]) + dir_entry = None + if parent.dir: + dir_entry = parent.dir.get(ref) + if dir_entry is None: + dir_entry = parent.dir.makedir(ref) + + c = value.copy(dir_entry) + p.objects[i] = c + + return p + + @property + def index_name(self): + if self._index_name: + return self._index_name + + propdef = self.propertydef + name = mangle_name(propdef.property_name, self.pid, 32-10) + self.data = self.encode(name) + self._index_name = name + return name + + @index_name.setter + def index_name(self, value): + self._index_name = value + + def encode(self, data): + return encode_utf16le(data) + + def decode(self): + self.index_name = decode_utf16le(self.data) + + def read_index(self): + index_name = self.index_name + " index" + index_dir = self.parent.dir.get(index_name) + if not index_dir: + raise AAFPropertyError("cannot find index stream: %s" % index_name) + + s = index_dir.open('r') + # read the whole index + f = BytesIO(s.read()) + + count = read_u32le(f) + self.next_free_key = read_u32le(f) + self.last_free_key = read_u32le(f) + + pack_fmt = str("<%dI" % count) + self.references = list(struct.unpack(pack_fmt, f.read(4 * count))) + + @writeonly + def write_index(self): + s = self.parent.dir.touch(self.index_name + " index").open(mode='rw') + f = BytesIO() + count = len(self.references) + write_u32le(f, count) + write_u32le(f, self.next_free_key) + write_u32le(f, self.last_free_key) + + for local_key in self.references: + write_u32le(f, local_key) + + s.write(f.getvalue()) + s.truncate() + + @property + def ref_classdef(self): + return self.typedef.element_typedef.ref_classdef + + def index_ref_name(self, index): + return "%s{%x}" % (self.index_name, self.references[index]) + + def get(self, index, default=None): + + if index >= len(self.references): + return default + + if index < 0: + index = max(0, len(self.references) + index) + + item = self.objects.get(index, None) + if item: + return item + ref = self.index_ref_name(index) + dir_entry = self.parent.dir.get(ref) + item = self.parent.root.manager.read_object(dir_entry) + + self.objects[index] = item + return item + + def __iter__(self): + for i in range(len(self.references)): + yield self.get(i) + + def __len__(self): + return len(self.references) + + def __getitem__(self, index): + item = self.get(index, None) + if item is None: + raise IndexError(index) + return item + + @writeonly + def __setitem__(self, index, value): + if index < 0: + index = max(0, len(self) + index) + + if index >= len(self): + raise IndexError("StrongRefVectorProperty assignment index out of range") + + if value.dir: + raise AAFAttachError("object already attached") + + obj = self.get(index, None) + if obj: + obj.detach() + + self.objects[index] = value + self.attach() + + @writeonly + def clear(self): + for obj in self: + obj.detach() + + self.next_free_key = 0 + self.references = [] + + if self.attached: + self.objects = weakref.WeakValueDictionary() + else: + self.objects = {} + + @writeonly + def pop(self, index): + obj = self.get(index, None) + if obj is None: + raise IndexError(index) + if index < 0: + index = max(0, len(self) + index) + + self.references.pop(index) + + # decrement all cached object with > index -1 + objects = {} + for key, value in self.objects.items(): + if key == index: + item = value + elif key > index: + objects[key-1] = value + else: + objects[key] = value + + if self.attached: + self.objects = weakref.WeakValueDictionary(objects) + else: + self.objects = objects + + assert obj is item + + obj.detach() + return obj + + @writeonly + def insert(self, index, value): + assert self.ref_classdef.isinstance(value.classdef) + + self.references.insert(index, self.next_free_key) + + objects = {} + objects[index] = value + # increment all cached object with > indices +1 + for key, value in self.objects.items(): + if key >= index: + objects[key+1] = value + else: + objects[key] = value + + self.next_free_key += 1 + if self.attached: + self.objects = weakref.WeakValueDictionary(objects) + else: + self.objects = objects + self.attach() + + @writeonly + def extend(self, value): + index_name = self.index_name # sets self.data + ref_classdef = self.ref_classdef + + for obj in value: + assert ref_classdef.isinstance(obj.classdef) + if obj.dir: + raise AAFAttachError("object already attached") + + for obj in value: + i = len(self.references) + self.references.append(self.next_free_key) + self.objects[i] = obj + self.next_free_key += 1 + + self.add_pid_entry() + self.attach() + + def append(self, value): + self.extend([value]) + + @property + def value(self): + return [item for item in self] + + @value.setter + @writeonly + def value(self, value): + if value is None: + self.remove_pid_entry() + return + + self.clear() + self.extend(value) + + def detach(self): + objects = {} + for i, obj in enumerate(self): + objects[i] = obj + self.objects = objects + + def attach(self): + + if not self.parent.dir: + return + + for i, obj in enumerate(self): + ref = self.index_ref_name(i) + + dir_entry = self.parent.dir.get(ref) + if dir_entry is None: + dir_entry = self.parent.dir.makedir(ref) + if obj.dir != dir_entry: + obj.attach(dir_entry) + + + def __repr__(self): + return "<%s %s to %s %d items>" % (self.name, self.__class__.__name__, str(self.index_name), len(self.references)) + + +class StrongRefSetProperty(Property): + __slots__ = ('references', 'index_name', 'next_free_key', 'last_free_key', 'key_pid', 'key_size', 'objects') + def __init__(self, parent, pid, format, version=PROPERTY_VERSION): + super(StrongRefSetProperty, self).__init__(parent, pid, format, version) + + self.references = {} + + self.index_name = None + + self.next_free_key = 0 + self.last_free_key = 0xFFFFFFFF + + # Pid of the referenced objects unique_key + self.key_pid = None + self.key_size = None + + if self.attached: + self.objects = weakref.WeakValueDictionary() + else: + self.objects = {} + + def copy(self, parent): + p = super(StrongRefSetProperty, self).copy(parent) + + p.references = dict(self.references) + p.next_free_key = self.next_free_key + p.last_free_key = self.last_free_key + p.key_size = self.key_size + p.index_name = self.index_name + p.key_pid = self.key_pid + + for key, value in self.items(): + ref = self.index_ref_name(key) + dir_entry = None + if parent.dir: + dir_entry = parent.dir.get(ref) + if dir_entry is None: + dir_entry = parent.dir.makedir(ref) + p.objects[key] = value.copy(dir_entry) + + return p + + def encode(self, data): + return encode_utf16le(data) + + def decode(self): + self.index_name = decode_utf16le(self.data) + + def read_index(self): + index_name = self.index_name + " index" + index_dir = self.parent.dir.get(index_name) + if not index_dir: + raise AAFPropertyError("cannot find index stream: %s" % index_name) + + s = index_dir.open('r') + # read the whole of the index + f = BytesIO(s.read()) + + count = read_u32le(f) + self.next_free_key = read_u32le(f) + self.last_free_key = read_u32le(f) + self.key_pid = read_u16le(f) + self.key_size = read_u8(f) + assert self.key_size in (16, 32) + + fmt = ''.join(( + 'I', # local_key + 'I', # ref_count + '%ds' % self.key_size)) + + index_fmt = struct.Struct(str('<' + fmt * count)) + index_data = index_fmt.unpack(f.read()) + + for i in range(count): + index = i * 3 + + local_key = index_data[index + 0] + ref_count = index_data[index + 1] + key = index_data[index + 2] + + # not sure if ref count is actually used + # doesn't appear to be + assert ref_count == 1 + + if self.key_size == 16: + key = AUID(bytes_le=key) + else: + key = MobID(bytes_le=key) + + self.references[key] = local_key + + @writeonly + def write_index(self): + s = self.parent.dir.touch(self.index_name + " index").open(mode='rw') + f = BytesIO() + count = len(self.references) + write_u32le(f, count) + write_u32le(f, self.next_free_key) + write_u32le(f, self.last_free_key) + + write_u16le(f, self.key_pid) + write_u8(f, self.key_size) + + for key, local_key in self.references.items(): + write_u32le(f, local_key) + write_u32le(f, 1) + f.write(key.bytes_le) + + s.write(f.getvalue()) + s.truncate() + + def index_ref_name(self, key): + return "%s{%x}" % (self.index_name, self.references[key]) + + def read_object(self, key): + + obj = self.objects.get(key, None) + if obj: + return obj + + ref = self.index_ref_name(key) + dir_entry = self.parent.dir.get(ref) + obj = self.parent.root.manager.read_object(dir_entry) + self.objects[key] = obj + return obj + + def __contains__(self, key): + return key in self.references + + def items(self): + for key in self.references: + obj = self.read_object(key) + yield (key, obj) + + def values(self): + for key, obj in self.items(): + yield obj + + def __iter__(self): + return self.values() + + def __len__(self): + return len(self.references) + + def get_object(self, key): + for obj in self.value: + if obj.name == key: + return obj + + def get(self, key, default=None): + if key not in self: + return default or self.get_object(key) + return self.read_object(key) + + def __getitem__(self, key): + result = self.get(key, default=None) + if result is None: + raise KeyError(key) + return result + + @writeonly + def swap_unique_key(self, old_key, new_key): + obj = self.get(old_key) + + if obj is None: + raise ValueError("invalid key: %s" % str(old_key)) + + # remove reference + self.objects.pop(old_key) + local_key = self.references.pop(old_key) + + self.references[new_key] = local_key + self.objects[new_key] = obj + + obj.unique_property.data = new_key.bytes_le + + @writeonly + def extend(self, values): + typedef = self.typedef + classdef = typedef.ref_classdef + + # check values are the correct type + for item in values: + if not classdef.isinstance(item.classdef): + raise TypeError("Invalid Value") + if item.dir: + raise AAFAttachError("object already attached") + + if self.key_pid is None: + self.key_pid = classdef.unique_key_pid + if self.key_size is None: + self.key_size = classdef.unique_key_size + + if self.index_name is None: + propdef = self.propertydef + self.index_name = mangle_name(propdef.property_name, self.pid, 32-10) + self.data = self.encode(self.index_name) + + for item in values: + key = item.unique_key + assert key is not None + + current = self.objects.get(key, None) + current_local_key = self.references.get(key, None) + + if current and current is not item: + current.detach() + + if current_local_key is None: + self.references[key] = self.next_free_key + self.next_free_key += 1 + + self.objects[key] = item + + if self.parent.dir: + ref = self.index_ref_name(key) + dir_entry = self.parent.dir.get(ref) + if dir_entry is None: + dir_entry = self.parent.dir.makedir(ref) + if item.dir != dir_entry: + item.attach(dir_entry) + + self.add_pid_entry() + + def append(self, value): + self.extend([value]) + + @writeonly + def clear(self): + for item in self.values(): + item.detach() + self.references = {} + if self.attached: + self.objects = weakref.WeakValueDictionary() + else: + self.objects = {} + self.next_free_key = 0 + + @writeonly + def pop(self, key): + obj = self.get(key) + if obj is None: + raise KeyError(key) + + self.references.pop(key) + self.objects.pop(key) + + obj.detach() + + return obj + + + @property + def value(self): + return [item for item in self] + + @value.setter + @writeonly + def value(self, value): + if value is None: + self.remove_pid_entry() + return + + self.clear() + if isinstance(value, dict): + value = value.values() + + self.extend(value) + return + + def detach(self): + objects = {} + for key, value in self.items(): + objects[key] = value + self.objects = objects + + def attach(self): + + if not self.parent.dir: + return + + for key in self.references: + obj = self.objects.get(key, None) + if not obj: + continue + ref = self.index_ref_name(key) + dir_entry = self.parent.dir.get(ref) + if dir_entry is None: + dir_entry = self.parent.dir.makedir(ref) + if obj.dir != dir_entry: + obj.attach(dir_entry) + + + def __repr__(self): + return "<%s to %s %d items>" % (self.__class__.__name__, str(self.index_name), len(self.references)) + +def resolve_weakref(p, ref): + ref_class_id = p.ref_classdef.auid + if ref_class_id == CLASSDEF_AUID: + return p.parent.root.metadict.lookup_classdef(ref) + elif ref_class_id == TYPEDEF_AUID: + return p.parent.root.metadict.lookup_typedef(ref) + else: + return p.parent.root.resovle_weakref(p.weakref_index, p.key_pid, ref) + +class WeakRefProperty(Property): + __slots__ = ('weakref_index', 'key_pid', 'key_size', 'ref') + def __init__(self, parent, pid, format, version=PROPERTY_VERSION): + super(WeakRefProperty, self).__init__(parent, pid, format, version) + self.weakref_index = None + self.key_pid = None + self.key_size = None + self.ref = None + + def copy(self, parent): + p = super(WeakRefProperty, self).copy(parent) + p.weakref_index = self.weakref_index + p.key_pid = self.pid + p.key_size = self.key_size + p.ref = self.ref + return p + + def decode(self): + f = BytesIO(self.data) + self.weakref_index = read_u16le(f) + self.key_pid = read_u16le(f) + self.key_size = read_u8(f) + assert self.key_size in (16, 32) + if self.key_size == 16: + self.ref = AUID(bytes_le=f.read(self.key_size)) + else: + self.ref = key = MobID(bytes_le=f.read(self.key_size)) + + def encode(self): + f = BytesIO() + ref = self.ref.bytes_le + key_size = len(ref) + assert key_size in (16, 32) + + write_u16le(f, self.weakref_index) + write_u16le(f, self.key_pid) + write_u8(f, key_size) + f.write(ref) + return f.getvalue() + + def __repr__(self): + return "<%s %s index %s %s>" % (self.name, self.__class__.__name__, self.weakref_index, self.ref) + + @property + def ref_classdef(self): + return self.typedef.ref_classdef + + @property + def value(self): + return resolve_weakref(self, self.ref) + + @property + def pid_path(self): + return self.typedef.pid_path + + @value.setter + @writeonly + def value(self, value): + if value is None: + self.remove_pid_entry() + return + + ref_classdef = self.ref_classdef + assert ref_classdef.isinstance(value.classdef) + + if self.key_pid is None: + self.key_pid = ref_classdef.unique_key_pid + if self.key_size is None: + self.key_size = ref_classdef.unique_key_size + if self.weakref_index is None: + self.weakref_index = self.parent.root.weakref_index(self.pid_path) + + self.ref = value.unique_key + self.data = self.encode() + self.add_pid_entry() + +class WeakRefArrayProperty(Property): + __slots__ = ('references', 'index_name', 'weakref_index', 'key_pid', 'key_size') + def __init__(self, parent, pid, format, version=PROPERTY_VERSION): + super(WeakRefArrayProperty, self).__init__(parent, pid, format, version) + self.references = [] + self.index_name = None + self.weakref_index = None + self.key_pid = None + self.key_size = None + + def copy(self, parent): + p = super(WeakRefArrayProperty, self).copy(parent) + p.references = list(self.references) + p.index_name = self.index_name + p.weakref_index = self.weakref_index + p.key_pid = self.key_pid + p.key_size = self.key_size + return p + + def encode(self, data): + return encode_utf16le(data) + + def decode(self): + self.index_name = decode_utf16le(self.data) + + def read_index(self): + index_name = self.index_name + " index" + index_dir = self.parent.dir.get(index_name) + if not index_dir: + raise AAFPropertyError("cannot find index stream: %s" % index_name) + + s = index_dir.open('r') + # read the whole index + f = BytesIO(s.read()) + + count = read_u32le(f) + self.weakref_index = read_u16le(f) + self.key_pid = read_u16le(f) + self.key_size = read_u8(f) + assert self.key_size in (16, 32) + + for i in range(count): + if self.key_size == 16: + key = AUID(bytes_le=f.read(self.key_size)) + else: + key = key = MobID(bytes_le=f.read(self.key_size)) + self.references.append(key) + + @writeonly + def write_index(self): + s = self.parent.dir.touch(self.index_name + " index").open(mode='rw') + f = BytesIO() + count = len(self.references) + write_u32le(f, count) + write_u16le(f, self.weakref_index) + write_u16le(f, self.key_pid) + write_u8(f, self.key_size) + + for item in self.references: + f.write(item.bytes_le) + + s.write(f.getvalue()) + s.truncate() + + def __repr__(self): + return "<%s %s to %d items>" % (self.name, self.__class__.__name__, len(self.references) ) + + @property + def ref_classdef(self): + return self.typedef.element_typedef.ref_classdef + + @property + def pid_path(self): + return self.typedef.element_typedef.pid_path + + def __len__(self): + return len(self.references) + + def __iter__(self): + for ref in self.references: + r = resolve_weakref(self, ref) + yield r + + @writeonly + def extend(self, values): + ref_classdef = self.ref_classdef + + # check values are the correct type + for item in values: + if not ref_classdef.isinstance(item.classdef): + raise TypeError("Invalid Value") + + if self.index_name is None: + propdef = self.propertydef + self.index_name = mangle_name(propdef.property_name, self.pid, 32-10) + self.data = self.encode(self.index_name) + + if self.weakref_index is None: + self.weakref_index = self.parent.root.weakref_index(self.pid_path) + if self.key_pid is None: + self.key_pid = ref_classdef.unique_key_pid + if self.key_size is None: + self.key_size = ref_classdef.unique_key_size + + for item in values: + self.references.append(item.unique_key) + + self.add_pid_entry() + + def append(self, value): + self.extend([value]) + + @writeonly + def clear(self): + self.references = [] + + @property + def value(self): + return [item for item in self] + + @value.setter + @writeonly + def value(self, value): + if value is None: + self.remove_pid_entry() + return + + self.clear() + self.extend(value) + + +class WeakRefVectorProperty(WeakRefArrayProperty): + pass +class WeakRefSetProperty(WeakRefArrayProperty): + pass + + +# haven't see aaf files that contain these yet +class WeakRefPropertyId(WeakRefProperty): + pass + +class UniqueIdProperty(Property): + pass + +class OpaqueStreamProperty(Property): + pass + +property_formats = { +SF_DATA : Property, +SF_DATA_STREAM : StreamProperty, +SF_STRONG_OBJECT_REFERENCE : StrongRefProperty, +SF_STRONG_OBJECT_REFERENCE_VECTOR : StrongRefVectorProperty, +SF_STRONG_OBJECT_REFERENCE_SET : StrongRefSetProperty, +SF_WEAK_OBJECT_REFERENCE : WeakRefProperty, +SF_WEAK_OBJECT_REFERENCE_VECTOR : WeakRefVectorProperty, +SF_WEAK_OBJECT_REFERENCE_SET : WeakRefSetProperty, +SF_WEAK_OBJECT_REFERENCE_STORED_OBJECT_ID : WeakRefPropertyId, +SF_UNIQUE_OBJECT_ID : UniqueIdProperty, +SF_OPAQUE_STREAM : OpaqueStreamProperty +} + +def add_string_property(parent, pid, value): + p = Property(parent, pid, SF_DATA, PROPERTY_VERSION) + if value: + p.data = encode_utf16le(value) + parent.property_entries[pid] = p + return p + +def add_bool_property(parent, pid, value): + p = Property(parent, pid, SF_DATA, PROPERTY_VERSION) + if value: + p.data = b"\x01" + else: + p.data = b"\x00" + parent.property_entries[pid] = p + return p + +def add_u32le_property(parent, pid, value): + p = Property(parent, pid, SF_DATA, PROPERTY_VERSION) + if value is not None: + p.data = encode_u32le(value) + parent.property_entries[pid] = p + return p + +def add_u16le_property(parent, pid, value): + p = Property(parent, pid, SF_DATA, PROPERTY_VERSION) + if value is not None: + p.data = encode_u16le(value) + parent.property_entries[pid] = p + return p + +def add_u8_property(parent, pid, value): + + p = Property(parent, pid, SF_DATA, PROPERTY_VERSION) + if value is not None: + p.data = encode_u8(value) + parent.property_entries[pid] = p + return p + +def add_auid_property(parent, pid, value): + p = Property(parent, pid, SF_DATA, PROPERTY_VERSION) + + if value is None: + value = AUID(int=0) + elif not isinstance(value, AUID): + value = AUID(value) + + p.data = value.bytes_le + parent.property_entries[pid] = p + return p + +def add_auid_array_propertry(parent, pid, values): + p = Property(parent, pid, SF_DATA, PROPERTY_VERSION) + p.data = encode_auid_array(values) + parent.property_entries[pid] = p + return p + +def add_utf16_array_property(parent, pid, values): + p = Property(parent, pid, SF_DATA, PROPERTY_VERSION) + p.data = encode_utf16_array(values) + parent.property_entries[pid] = p + return p + +def add_s64le_array_property(parent, pid, values): + p = Property(parent, pid, SF_DATA, PROPERTY_VERSION) + p.data = b'' + for i in values: + p.data += encode_s64le(i) + parent.property_entries[pid] = p + return p + +def add_weakref_property(parent, pid, pid_path, key_pid, value): + p = WeakRefProperty(parent, pid, SF_WEAK_OBJECT_REFERENCE, PROPERTY_VERSION) + p.weakref_index = parent.root.weakref_index(pid_path) + p.key_pid = key_pid + p.key_size = 16 + if not isinstance(value, AUID): + value = AUID(value) + p.ref = value + p.data = p.encode() + + parent.property_entries[pid] = p + + return p + +def add_classdef_weakref_property(parent, pid, value): + pid_path = [0x0001, 0x0003] + return add_weakref_property(parent, pid , pid_path, 0x0005, value) + +def add_typedef_weakref_property(parent, pid, value): + pid_path = [0x0001, 0x0004] + return add_weakref_property(parent, pid , pid_path, 0x0005, value) + +def add_strongref_set_property(parent, pid, property_name, unique_pid, key_size=16): + + p = StrongRefSetProperty(parent, pid, SF_STRONG_OBJECT_REFERENCE_SET, PROPERTY_VERSION) + name = mangle_name(property_name, pid, 32-10) + p.index_name = name + p.data = p.encode(name) + + p.key_pid = unique_pid + p.key_size = key_size + parent.property_entries[pid] = p + + return p + +def add2set(self, pid, key, value): + """low level add to StrongRefSetProperty""" + prop = self.property_entries[pid] + + current = prop.objects.get(key, None) + current_local_key = prop.references.get(key, None) + + if current and current is not value: + current.detach() + + if current_local_key is None: + prop.references[key] = prop.next_free_key + prop.next_free_key += 1 + + prop.objects[key] = value + + if prop.parent.dir: + ref = prop.index_ref_name(key) + dir_entry = prop.parent.dir.get(ref) + if dir_entry is None: + dir_entry = prop.parent.dir.makedir(ref) + if value.dir != dir_entry: + value.attach(dir_entry) + prop.mark_modified() + +def add_typedef_weakref_vector_property(parent, pid, property_name, values): + # kAAFTypeID_TypeDefinitionWeakReferenceVector + pid_path = [0x0001, 0x0004] + key_pid = 0x0005 + p = WeakRefVectorProperty(parent, pid, SF_WEAK_OBJECT_REFERENCE_VECTOR, PROPERTY_VERSION) + + p.weakref_index = parent.root.weakref_index(pid_path) + p.key_pid = key_pid + p.key_size = 16 + + p.index_name = mangle_name(property_name, pid, 32) + p.data = p.encode(p.index_name) + + p.references = [AUID(v) for v in values] + + parent.property_entries[pid] = p + return p diff --git a/aaf2/rational.py b/aaf2/rational.py new file mode 100644 index 0000000..a9413ab --- /dev/null +++ b/aaf2/rational.py @@ -0,0 +1,100 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +import sys +from fractions import Fraction, _RATIONAL_FORMAT +from decimal import Decimal +import numbers +Rational = numbers.Rational + +if sys.version_info.major >= 3: + unicode = str + +class AAFRational(Fraction): + """ + Subclass of fractions.Fraction from the standard library. Behaves exactly the same, except + doesn't round to the Greatest Common Divisor at the end. + """ + + def __new__(cls, numerator=0, denominator=None): + + self = super(AAFRational, cls).__new__(cls) + + if denominator is None: + if isinstance(numerator, Rational): + self._numerator = numerator.numerator + self._denominator = numerator.denominator + return self + + elif isinstance(numerator, float): + # Exact conversion from float + value = Fraction.from_float(numerator) + + # make sure fraction can fit in a int32 + value = value.limit_denominator(0x7FFFFFFF) + if value._numerator > 0x7FFFFFFF or value._numerator < -0x7FFFFFFF: + value._denominator = int(value._denominator * (0x7FFFFFFF / float(value._numerator))) + value._numerator = 0x7FFFFFFF + + self._numerator = value._numerator + self._denominator = value._denominator + return self + + elif isinstance(numerator, Decimal): + value = Fraction.from_decimal(numerator) + self._numerator = value._numerator + self._denominator = value._denominator + return self + + elif isinstance(numerator, (str, unicode)): + # Handle construction from strings. + m = _RATIONAL_FORMAT.match(numerator) + if m is None: + raise ValueError('Invalid literal for Fraction: %r' % + numerator) + numerator = int(m.group('num') or '0') + denom = m.group('denom') + if denom: + denominator = int(denom) + else: + denominator = 1 + decimal = m.group('decimal') + if decimal: + scale = 10**len(decimal) + numerator = numerator * scale + int(decimal) + denominator *= scale + exp = m.group('exp') + if exp: + exp = int(exp) + if exp >= 0: + numerator *= 10**exp + else: + denominator *= 10**-exp + if m.group('sign') == '-': + numerator = -numerator + + else: + raise TypeError("argument should be a string " + + "or a Rational instance") + + elif (isinstance(numerator, Rational) and + isinstance(denominator, Rational)): + numerator, denominator = ( + numerator.numerator * denominator.denominator, + denominator.numerator * numerator.denominator + ) + else: + raise TypeError("both arguments should be " + + "Rational instances") + + if denominator == 0: + raise ZeroDivisionError('Fraction(%s, 0)' % numerator) + # don't find the gcd + #g = gcd(numerator, denominator) + self._numerator = numerator + self._denominator = denominator + return self diff --git a/aaf2/types.py b/aaf2/types.py new file mode 100644 index 0000000..b068f5e --- /dev/null +++ b/aaf2/types.py @@ -0,0 +1,869 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) +import sys +from uuid import UUID +from io import BytesIO + +from . import core +from . import properties +from .mobid import MobID +from .rational import AAFRational +from .exceptions import AAFPropertyError +from .auid import AUID + +import datetime + +from struct import (unpack, pack) +from .utils import register_class, decode_utf16le, encode_utf16le, encode_utf16_array, encode_auid_array + +if sys.version_info.major >= 3: + unicode = str + +PID_NAME = 0x0006 +PID_AUID = 0x0005 + +@register_class +class TypeDef(core.AAFObject): + class_id = AUID("0d010101-0203-0000-060e-2b3402060101") + __slots__ = ('_auid') + + def __new__(cls, root=None, name=None, type_auid=None, *args, **kwargs): + self = super(TypeDef, cls).__new__(cls) + self.root = root + self._auid = None + if root: + properties.add_string_property(self, PID_NAME, name) + properties.add_auid_property(self, PID_AUID, type_auid) + return self + + @property + def unique_key(self): + return self.auid + + @property + def auid(self): + if self._auid: + return self._auid + p = self.property_entries.get(PID_AUID) + self._auid = AUID(bytes_le=p.data) + return self._auid + + @property + def uuid(self): + return self.auid.uuid + + @property + def type_name(self): + data = self.property_entries[PID_NAME].data + if data is not None: + return decode_utf16le(data) + + @property + def store_format(self): + return properties.SF_DATA + + def __repr__(self): + return "<%s %s>" % (self.type_name, self.__class__.__name__) + +PID_INT_SIZE = 0x000F +PID_INT_SIGNED = 0x0010 + +@register_class +class TypeDefInt(TypeDef): + class_id = AUID("0d010101-0204-0000-060e-2b3402060101") + __slots__ = () + + def __new__(cls, root=None, name=None, type_auid=None, size=None, signed=None): + self = super(TypeDefInt, cls).__new__(cls, root, name, type_auid) + if root: + properties.add_u8_property(self, PID_INT_SIZE, size) + properties.add_bool_property(self, PID_INT_SIGNED, signed) + return self + + @property + def signed(self): + return self.property_entries[PID_INT_SIGNED].data == b"\x01" + + @property + def size(self): + data = self.property_entries[PID_INT_SIZE].data + if data is not None: + return unpack(b'B', data)[0] + raise ValueError("%s No Size" % str(self.type_name)) + + @property + def byte_size(self): + return self.size + + def pack_format(self, elements=1): + fmt = "" + if self.size == 1: + fmt = '%dB' + elif self.size == 2: + fmt = "<%dH" + elif self.size == 4: + fmt = "<%dI" + elif self.size == 8: + fmt = "<%dQ" + else: + raise AAFPropertyError("unknown integer size: %d" % self.size) + fmt = fmt % elements + if self.signed: + fmt = fmt.lower() + + return str(fmt) + + def decode(self, data): + assert len(data) == self.size + (result,) = unpack(self.pack_format(), data) + return result + + def encode(self, value): + return pack(self.pack_format(), value) + + +PID_STRONGREF_REF_TYPE = 0x0011 + +@register_class +class TypeDefStrongRef(TypeDef): + class_id = AUID("0d010101-0205-0000-060e-2b3402060101") + __slots__ = () + + def __new__(cls, root=None, name=None, type_auid=None, classdef=None): + self = super(TypeDefStrongRef, cls).__new__(cls, root, name, type_auid) + if root: + properties.add_classdef_weakref_property(self, PID_STRONGREF_REF_TYPE, classdef) + return self + + @property + def store_format(self): + return properties.SF_STRONG_OBJECT_REFERENCE + + @property + def ref_classdef(self): + if PID_STRONGREF_REF_TYPE in self.property_entries: + return self.root.metadict.lookup_classdef(self.property_entries[PID_STRONGREF_REF_TYPE].ref) + +PID_WEAKREF_REF_TYPE = 0x0012 +PID_WEAKREF_TARGET_SET = 0x0013 + +@register_class +class TypeDefWeakRef(TypeDef): + class_id = AUID("0d010101-0206-0000-060e-2b3402060101") + __slots__ = () + + def __new__(cls, root=None, name=None, type_auid=None, classdef=None, path=None): + self = super(TypeDefWeakRef, cls).__new__(cls, root, name, type_auid) + if root: + properties.add_classdef_weakref_property(self, PID_WEAKREF_REF_TYPE, classdef) + properties.add_auid_array_propertry(self, PID_WEAKREF_TARGET_SET, path) + + return self + + @property + def store_format(self): + return properties.SF_WEAK_OBJECT_REFERENCE + + @property + def ref_classdef(self): + if PID_WEAKREF_REF_TYPE in self.property_entries: + return self.root.metadict.lookup_classdef(self.property_entries[PID_WEAKREF_REF_TYPE].ref) + + @property + def path(self): + return [p.name for c, p in self.propertydef_path] + + @property + def pid_path(self): + return [p.pid for c, p in self.propertydef_path] + + @property + def target_set_path(self): + return self['TargetSet'].value + + @property + def propertydef_path(self): + path = [] + classdef = self.root.metadict.lookup_classdef("Root") + for p_auid in self.target_set_path: + found = False + for p in classdef.propertydefs: + if p.auid == p_auid: + path.append((classdef, p)) + classdef = p.typedef.ref_classdef + found = True + break + if not found: + raise AAFPropertyError("unable to resolve property path") + + return path + +PID_ENUM_TYPE = 0x0014 +PID_ENUM_NAMES = 0x0015 +PID_ENUM_VALUES = 0x0016 + +@register_class +class TypeDefEnum(TypeDef): + class_id = AUID("0d010101-0207-0000-060e-2b3402060101") + __slots__ = () + + def __new__(cls, root=None, name=None, type_auid=None, typedef=None, elements=None): + self = super(TypeDefEnum, cls).__new__(cls, root, name, type_auid) + if root: + properties.add_typedef_weakref_property(self, PID_ENUM_TYPE, typedef) + names = [] + values = [] + for val, name in elements.items(): + names.append(name) + values.append(val) + + properties.add_utf16_array_property(self, PID_ENUM_NAMES, names) + properties.add_s64le_array_property(self, PID_ENUM_VALUES, values) + + return self + + @property + def byte_size(self): + return self.element_typedef.byte_size + + @property + def elements(self): + names = list(iter_utf16_array(self.property_entries[PID_ENUM_NAMES].data)) + element_count = len(names) + fmt = b"<%dq" % element_count + values = unpack(fmt, self.property_entries[PID_ENUM_VALUES].data) + elements = dict(zip(values, names)) + return elements + + @property + def element_typedef(self): + if PID_ENUM_TYPE in self.property_entries: + return self.root.metadict.lookup_typedef(self.property_entries[PID_ENUM_TYPE].ref) + + def register_element(self, element_name, element_value): + names = [] + values = [] + for val, name in sorted(self.elements.items()): + if val == element_value: + return + if name == element_name: + return + + names.append(name) + values.append(val) + + names.append(element_name) + values.append(element_value) + + self.property_entries[PID_ENUM_NAMES].data = encode_utf16_array(names) + fmt = b"<%dq" % len(values) + self.property_entries[PID_ENUM_VALUES].data = pack(fmt, *values) + + def decode(self, data): + + # Boolean + if self.auid == AUID("01040100-0000-0000-060e-2b3401040101"): + return data == b'\x01' + + typedef = self.element_typedef + index = typedef.decode(data) + return self.elements[index] + + def encode(self, data): + # Boolean + if self.auid == AUID("01040100-0000-0000-060e-2b3401040101"): + return b'\x01' if data else b'\x00' + + typedef = self.element_typedef + for index, value in self.elements.items(): + if value == data: + return typedef.encode(index) + if index == data: + return typedef.encode(index) + + raise AAFPropertyError("invalid enum: %s" % str(data)) + +def iter_utf16_array(data): + start = 0 + data = bytearray(data) + for i in range(0, len(data), 2): + if data[i] == 0x00 and data[i+1] == 0x00: + yield data[start:i].decode("utf-16le") + start = i+2 + +PID_FIXED_TYPE = 0x0017 +PID_FIXED_COUNT = 0x0018 + +@register_class +class TypeDefFixedArray(TypeDef): + class_id = AUID("0d010101-0208-0000-060e-2b3402060101") + __slots__ = () + + def __new__(cls, root=None, name=None, type_auid=None, typedef=None, size=None): + self = super(TypeDefFixedArray, cls).__new__(cls, root, name, type_auid) + if root: + properties.add_typedef_weakref_property(self, PID_FIXED_TYPE, typedef) + properties.add_u32le_property(self, PID_FIXED_COUNT, size) + return self + + @property + def element_typedef(self): + if PID_FIXED_TYPE in self.property_entries: + return self.root.metadict.lookup_typedef(self.property_entries[PID_FIXED_TYPE].ref) + + @property + def size(self): + (result, ) = unpack(b'= element_count: + raise AAFPropertyError("too many elements for fixed array: expected %d elements" % element_count) + break + result += element_typedef.encode(item) + + # zero out remaining bytes + if i < element_count: + bytes_left = (element_count - i) * byte_size + while bytes_left: + result += b'\0' + bytes_left -= 1 + + return result + +PID_VAR_TYPE = 0x0019 + +@register_class +class TypeDefVarArray(TypeDef): + class_id = AUID("0d010101-0209-0000-060e-2b3402060101") + __slots__ = () + + def __new__(cls, root=None, name=None, type_auid=None, typedef=None): + self = super(TypeDefVarArray, cls).__new__(cls, root, name, type_auid) + + if root: + properties.add_typedef_weakref_property(self, PID_VAR_TYPE, typedef) + return self + + @property + def store_format(self): + if self.element_typedef.store_format == properties.SF_WEAK_OBJECT_REFERENCE: + return properties.SF_WEAK_OBJECT_REFERENCE_VECTOR + elif self.element_typedef.store_format == properties.SF_STRONG_OBJECT_REFERENCE: + return properties.SF_STRONG_OBJECT_REFERENCE_VECTOR + + return super(TypeDefVarArray, self).store_format + + @property + def element_typedef(self): + if PID_VAR_TYPE in self.property_entries: + return self.root.metadict.lookup_typedef(self.property_entries[PID_VAR_TYPE].ref) + @property + def ref_classdef(self): + typedef = self.element_typedef + return typedef.ref_classdef + + def decode(self, data): + + element_typedef = self.element_typedef + + #aafCharacter + if element_typedef.auid == AUID("01100100-0000-0000-060e-2b3401040101"): + return list(iter_utf16_array(data)) + + + if isinstance(element_typedef, TypeDefInt): + size = element_typedef.size + elements = len(data)//size + fmt = element_typedef.pack_format(elements) + return list(unpack(fmt, data)) + + byte_size = element_typedef.byte_size + elements = len(data)//byte_size + start = 0 + result = [] + for i in range(elements): + end = start + byte_size + result.append(element_typedef.decode(data[start:end])) + start = end + return result + + def encode(self, value): + + element_typedef = self.element_typedef + + if element_typedef.type_name == "Character": + return encode_utf16_array(value) + + if isinstance(element_typedef, TypeDefInt): + + elements = len(value) + fmt = element_typedef.pack_format(elements) + return pack(fmt, *value) + + result = b'' + + for item in value: + result += element_typedef.encode(item) + + return result + +PID_SET_TYPE = 0x001A + +@register_class +class TypeDefSet(TypeDef): + class_id = AUID("0d010101-020a-0000-060e-2b3402060101") + __slots__ = () + + def __new__(cls, root=None, name=None, type_auid=None, typedef=None): + self = super(TypeDefSet, cls).__new__(cls, root, name, type_auid) + if root: + properties.add_typedef_weakref_property(self, PID_SET_TYPE, typedef) + return self + + @property + def element_typedef(self): + if PID_SET_TYPE in self.property_entries: + return self.root.metadict.lookup_typedef(self.property_entries[PID_SET_TYPE].ref) + + @property + def ref_classdef(self): + typedef = self.element_typedef + return typedef.ref_classdef + + @property + def store_format(self): + if self.element_typedef.store_format == properties.SF_STRONG_OBJECT_REFERENCE: + return properties.SF_STRONG_OBJECT_REFERENCE_SET + elif self.element_typedef.store_format == properties.SF_WEAK_OBJECT_REFERENCE: + return properties.SF_WEAK_OBJECT_REFERENCE_SET + elif self.element_typedef.store_format == properties.SF_DATA: + return properties.SF_DATA + else: + raise AAFPropertyError("unkown store format: 0x%x" % self.element_typedef.store_format) + + def decode(self, data): + + typedef = self.element_typedef + byte_size = typedef.byte_size + count = len(data) // byte_size + start = 0 + result = set() + for i in range(count): + end = start + byte_size + v = typedef.decode(data[start:end]) + result.add(v) + start = end + + return result + + def encode(self, data): + typedef = self.element_typedef + + set_data = set(data) + result = b"" + for item in set_data: + result += typedef.encode(item) + + return result + +PID_STR_TYPE = 0x001B + +@register_class +class TypeDefString(TypeDef): + class_id = AUID("0d010101-020b-0000-060e-2b3402060101") + __slots__ = () + + def __new__(cls, root=None, name=None, type_auid=None, typedef=None): + self = super(TypeDefString, cls).__new__(cls, root, name, type_auid) + if root: + properties.add_typedef_weakref_property(self, PID_STR_TYPE, typedef) + return self + + @property + def element_typedef(self): + if PID_STR_TYPE in self.property_entries: + return self.root.metadict.lookup_typedef(self.property_entries[PID_STR_TYPE].ref) + + def decode(self, data): + return decode_utf16le(data) + + def encode(self, data): + return encode_utf16le(data) + +@register_class +class TypeDefStream(TypeDef): + class_id = AUID("0d010101-020c-0000-060e-2b3402060101") + + @property + def store_format(self): + return properties.SF_DATA_STREAM + +PID_RECORD_TYPES = 0x001C +PID_RECORD_NAMES = 0x001D + +MOBID_AUID = AUID("01030200-0000-0000-060e-2b3401040101") +AUID_AUID = AUID("01030100-0000-0000-060e-2b3401040101") +TIMESTRUCT_AUID = AUID("03010600-0000-0000-060e-2b3401040101") +DATESTRUCT_AUID = AUID("03010500-0000-0000-060e-2b3401040101") +TIMESTAMP_AUID = AUID("03010700-0000-0000-060e-2b3401040101") +RATIONAL_AUID = AUID("03010100-0000-0000-060e-2b3401040101") + +@register_class +class TypeDefRecord(TypeDef): + class_id = AUID("0d010101-020d-0000-060e-2b3402060101") + __slots__ = ('_fields') + + def __new__(cls, root=None, name=None, type_auid=None, fields=None): + self = super(TypeDefRecord, cls).__new__(cls, root, name, type_auid) + if root: + names = [] + types = [] + for name, val in fields: + names.append(name) + types.append(val) + + properties.add_utf16_array_property(self, PID_RECORD_NAMES, names) + properties.add_typedef_weakref_vector_property(self, PID_RECORD_TYPES, 'MemberTypes', types) + + self._fields = None + return self + + @property + def fields(self): + if self._fields: + return self._fields + names = list(iter_utf16_array(self['MemberNames'].data)) + types = list(self['MemberTypes'].value) + self._fields = list(zip(names, [t.type_name for t in types])) + return self._fields + + @property + def byte_size(self): + size = 0 + for key, typedef_name in self.fields: + typedef = self.root.metadict.typedefs_by_name[typedef_name] + size += typedef.byte_size + if size == 0: + print("!!", self['MemberTypes'].value, list(iter_utf16_array(self['MemberNames'].data))) + + assert size != 0 + + return size + + def decode(self, data): + + # MobID + if self.auid == MOBID_AUID: + mobid = MobID(bytes_le=data) + return mobid + + # AUID + if self.auid == AUID_AUID: + return AUID(bytes_le=data) + + start = 0 + result = {} + + for key, typedef_name in self.fields: + typedef = self.root.metadict.lookup_typedef(typedef_name) + + end = start + typedef.byte_size + result[key] = typedef.decode(data[start:end]) + start = end + + + # TimeStruct + if self.auid == TIMESTRUCT_AUID: + t = datetime.time(result['hour'], + result['minute'], + result['second'], + result['fraction']) + return t + + # DateStruct + if self.auid == DATESTRUCT_AUID: + d = datetime.date(**result) + return d + + # TimeStamp + if self.auid == TIMESTAMP_AUID: + d = datetime.datetime.combine(result['date'], result['time']) + return d + + # Rational + if self.auid == RATIONAL_AUID: + r = AAFRational(result['Numerator'], result['Denominator']) + return r + + return result + + def encode(self, data): + # MobID + if self.auid == MOBID_AUID: + return data.bytes_le + + # AUID + if self.auid == AUID_AUID: + return data.bytes_le + + result = b"" + # TimeStamp + if self.auid == TIMESTAMP_AUID: + assert isinstance(data, datetime.datetime) + f = [self.root.metadict.lookup_typedef(t) for k, t in self.fields] + #date + result += f[0].encode(data.date()) + + #time + result += f[1].encode(data.time()) + return result + + + # DateStruct + if self.auid == DATESTRUCT_AUID: + assert isinstance(data, datetime.date) + d = {'year' : data.year, + 'month' : data.month, + 'day': data.day} + # print (d) + data = d + + # TimeStruct + if self.auid == TIMESTRUCT_AUID: + assert isinstance(data, datetime.time) + t = {'hour' : data.hour, + 'minute' : data.minute, + 'second' : data.second, + 'fraction' : 0 } + # print(t) + data = t + + # Rational + if self.auid == RATIONAL_AUID: + r = AAFRational(data) + data = {'Numerator': r.numerator, 'Denominator':r.denominator } + + for key, typedef_name in self.fields: + typedef = self.root.metadict.lookup_typedef(typedef_name) + value = typedef.encode(data[key]) + result+=value + + return result + +PID_RENAME_TYPE = 0x001E + +@register_class +class TypeDefRename(TypeDef): + class_id = AUID("0d010101-020e-0000-060e-2b3402060101") + __slots__ = () + + def __new__(cls, root=None, name=None, type_auid=None, typedef=None): + self = super(TypeDefRename, cls).__new__(cls, root, name, type_auid) + if root: + properties.add_typedef_weakref_property(self, PID_RENAME_TYPE, typedef) + return self + + @property + def renamed_typedef(self): + if PID_RENAME_TYPE in self.property_entries: + return self.root.metadict.lookup_typedef(self.property_entries[PID_RENAME_TYPE].ref) + + def decode(self, data): + return self.renamed_typedef.decode(data) + + def encode(self, data): + return self.renamed_typedef.encode(data) + +def iter_auid_array(data): + f = BytesIO(data) + result = [] + while True: + d = f.read(16) + if not d: + break + + if len(d) == 16: + yield AUID(bytes_le=d) + else: + raise Exception("auid length wrong: %d" % len(d)) + +PID_EXTENUM_NAMES = 0x001f +PID_EXTENUM_VALUES = 0x0020 + +@register_class +class TypeDefExtEnum(TypeDef): + class_id = AUID("0d010101-0220-0000-060e-2b3402060101") + __slots__ = () + + def __new__(cls, root=None, name=None, type_auid=None, elements=None): + self = super(TypeDefExtEnum, cls).__new__(cls, root, name, type_auid) + + if root: + names = [] + values = [] + for val, name in elements.items(): + values.append(val) + names.append(name) + + properties.add_utf16_array_property(self, PID_EXTENUM_NAMES, names) + properties.add_auid_array_propertry(self, PID_EXTENUM_VALUES, values) + + return self + + def register_element(self, element_name, element_auid): + + element_names = [] + element_values = [] + for val, name in self.elements.items(): + if val == element_auid: + return + if name == element_name: + return + + element_values.append(val) + element_names.append(name) + + element_names.append(element_name) + element_values.append(element_auid) + + self.property_entries[PID_EXTENUM_NAMES].data = encode_utf16_array(element_names) + self.property_entries[PID_EXTENUM_VALUES].data = encode_auid_array(element_values) + + @property + def elements(self): + element_names = list(iter_utf16_array(self.property_entries[PID_EXTENUM_NAMES].data)) + element_values = list(iter_auid_array(self.property_entries[PID_EXTENUM_VALUES].data)) + return dict(zip(element_values, element_names)) + + def decode(self, data): + if data is None: + return None + v = AUID(bytes_le=data) + result = self.elements.get(v, None) + if result is None: + return v + + return result + + def encode(self, data): + for key, value in self.elements.items(): + if isinstance(data, (AUID, UUID)): + if data == key: + return key.bytes_le + else: + + if value.lower() == data.lower(): + return key.bytes_le + + raise ValueError("invalid ext enum value: %s" % str(data)) + +@register_class +class TypeDefIndirect(TypeDef): + class_id = AUID("0d010101-0221-0000-060e-2b3402060101") + __slots__ = () + + def decode_typedef(self, data): + byte_order = data[0:1] + assert byte_order == b'\x4c' # little endian + type_auid = AUID(bytes_le=data[1:17]) + return self.root.metadict.lookup_typedef(type_auid) + + def decode(self, data): + typedef = self.decode_typedef(data) + result = typedef.decode(data[17:]) + return result + + def encode(self, data, data_typedef=None): + byte_order = b'\x4c' + typedef = None + if data_typedef is not None: + typedef = self.root.metadict.lookup_typedef(data_typedef) + if typedef is None: + raise AAFPropertyError("unable to find typedef: %s" % (str(data_typedef))) + type_auid = typedef.auid + + elif isinstance(data, (str, unicode)): + # aafString + type_auid = AUID("01100200-0000-0000-060e-2b3401040101") + + elif isinstance(data, AAFRational): + # Rational + type_auid = AUID("03010100-0000-0000-060e-2b3401040101") + + elif isinstance(data, int): + # aafInt32 + type_auid = AUID("01010700-0000-0000-060e-2b3401040101") + else: + raise NotImplementedError("Indirect type for: %s", str(type(data))) + + if typedef is None: + typedef = self.root.metadict.lookup_typedef(type_auid) + result = byte_order + result += type_auid.bytes_le + result += typedef.encode(data) + return result + + + +@register_class +class TypeDefOpaque(TypeDefIndirect): + class_id = AUID("0d010101-0222-0000-060e-2b3402060101") + __slots__ = () + +@register_class +class TypeDefCharacter(TypeDef): + class_id = AUID("0d010101-0223-0000-060e-2b3402060101") + __slots__ = () + + +categories = { +"ints" : TypeDefInt, +"enums" : TypeDefEnum, +"records" : TypeDefRecord, +"fixed_arrays" : TypeDefFixedArray, +"var_arrays" : TypeDefVarArray, +"renames" : TypeDefRename, +"strings" : TypeDefString, +"streams" : TypeDefStream, +"opaques" : TypeDefOpaque, +"extenums" : TypeDefExtEnum, +"chars" : TypeDefCharacter, +"indirects" : TypeDefIndirect, +"sets" : TypeDefSet, +"strongrefs" : TypeDefStrongRef, +"weakrefs" : TypeDefWeakRef, +} diff --git a/aaf2/utils.py b/aaf2/utils.py new file mode 100644 index 0000000..f909836 --- /dev/null +++ b/aaf2/utils.py @@ -0,0 +1,218 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) + +from struct import unpack, pack +from . import auid + +def read_u8(f): + (result, ) = unpack(b"B", f.read(1)) + return result + +def write_u8(f, value): + f.write(pack(b"B", value)) + +def read_u16le(f): + (result, ) = unpack(b"H", f.read(2)) + return result + +def write_u16le(f, value): + f.write(pack(b"I", f.read(4)) + return result + +def read_s32be(f): + (result, ) = unpack(b">i", f.read(4)) + return result + +def write_u32le(f, value): + return f.write(pack(b"Q", f.read(8)) + return result + +def read_s64be(f): + (result, ) = unpack(b">q", f.read(8)) + return result + +def write_u64le(f, value): + return f.write(pack(b"> (i * 8) & 0xff for i in range(length))) + return bytes(v) + elif byte_order == 'big': + v = bytearray((num >> (length - 1 - i) * 8) & 0xff for i in range(length)) + return bytes(v) + else: + raise ValueError('endianess must be "little" or "big"') + + +def squeeze_name(name, size): + if len(name) <= size: + return name + + half = size//2 + new_name = "" + for i in range(size): + if i < half: + ch = name[i] + elif i == half: + ch = '-' + else: + ch = name[len(name) - (size-i)] + new_name += ch + + return new_name + +def mangle_name(name, pid, size): + p = "%x" % pid + max_size = size - len(p) - 1 -1 + new_name = squeeze_name(name, max_size) + return "%s-%s" % (new_name, p) + +def safe_print(*args): + # python 2.7 + if bytes is str: + s = u' '.join([unicode(item) for item in args]) + print(s.encode('utf-8')) + else: + print(*args) + +AAFClaseID_dict = {} +AAFClassName_dict = {} +def register_class(classobj): + AAFClaseID_dict[classobj.class_id] = classobj + AAFClassName_dict[classobj.__name__] = classobj + + return classobj + +def rescale(value, current_rate, new_rate): + return (float(value) * float(new_rate)) / float(current_rate) diff --git a/aaf2/video.py b/aaf2/video.py new file mode 100644 index 0000000..2024492 --- /dev/null +++ b/aaf2/video.py @@ -0,0 +1,202 @@ +from __future__ import ( + unicode_literals, + absolute_import, + print_function, + division, + ) +from struct import unpack +from . utils import int_from_bytes +from io import BytesIO +from .auid import AUID + +dnx_profiles = { +'dnx_1080p_175x_23.97' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 175, "pix_fmt" : "yuv422p10", "frame_rate" : "24000/1001", }, +'dnx_1080p_365x_50' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 185, "pix_fmt" : "yuv422p10", "frame_rate" : "25/1", }, +'dnx_1080p_365x_60' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 365, "pix_fmt" : "yuv422p10", "frame_rate" : "50/1", }, +'dnx_1080p_440x_23.97' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 440, "pix_fmt" : "yuv422p10", "frame_rate" : "60000/1001", }, +'dnx_1080p_115_23.97' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 115, "pix_fmt" : "yuv422p", "frame_rate" : "24000/1001", }, +'dnx_1080p_120_25' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 120, "pix_fmt" : "yuv422p", "frame_rate" : "25/1", }, +'dnx_1080p_145_29.97' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 145, "pix_fmt" : "yuv422p", "frame_rate" : "30000/1001", }, +'dnx_1080p_240_50' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 240, "pix_fmt" : "yuv422p", "frame_rate" : "50/1", }, +'dnx_1080p_290_59.94' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 290, "pix_fmt" : "yuv422p", "frame_rate" : "60000/1001", }, +'dnx_1080p_175_23.97' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 175, "pix_fmt" : "yuv422p", "frame_rate" : "24000/1001", }, +'dnx_1080p_185_25' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 185, "pix_fmt" : "yuv422p", "frame_rate" : "25/1", }, +'dnx_1080p_220_29.97' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 220, "pix_fmt" : "yuv422p", "frame_rate" : "30000/1001", }, +'dnx_1080p_365_50' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 365, "pix_fmt" : "yuv422p", "frame_rate" : "50/1", }, +'dnx_1080p_440_59.94' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 440, "pix_fmt" : "yuv422p", "frame_rate" : "60000/1001", }, +'dnx_1080i_185x_25' : { "size" : (1920, 1080), 'interlaced' : True, "bitrate" : 185, "pix_fmt" : "yuv422p10", "frame_rate" : "25/1", }, +'dnx_1080i_220x_29.97' : { "size" : (1920, 1080), 'interlaced' : True, "bitrate" : 220, "pix_fmt" : "yuv422p10", "frame_rate" : "30000/1001", }, +'dnx_1080i_120_25' : { "size" : (1920, 1080), 'interlaced' : True, "bitrate" : 120, "pix_fmt" : "yuv422p", "frame_rate" : "25/1", }, +'dnx_1080i_145_29.97' : { "size" : (1920, 1080), 'interlaced' : True, "bitrate" : 145, "pix_fmt" : "yuv422p", "frame_rate" : "30000/1001", }, +'dnx_1080i_185_25' : { "size" : (1920, 1080), 'interlaced' : True, "bitrate" : 185, "pix_fmt" : "yuv422p", "frame_rate" : "25/1", }, +'dnx_1080i_220_29.97' : { "size" : (1920, 1080), 'interlaced' : True, "bitrate" : 220, "pix_fmt" : "yuv422p", "frame_rate" : "30000/1001", }, +'dnx_720p_90x_23.97' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 90, "pix_fmt" : "yuv422p10", "frame_rate" : "24000/1001", }, +'dnx_720p_90x_25' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 90, "pix_fmt" : "yuv422p10", "frame_rate" : "25/1", }, +'dnx_720p_180x_50' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 180, "pix_fmt" : "yuv422p10", "frame_rate" : "50/1", }, +'dnx_720p_220x_59.94' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 220, "pix_fmt" : "yuv422p10", "frame_rate" : "60000/1001", }, +'dnx_720p_90_23.97' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 90, "pix_fmt" : "yuv422p", "frame_rate" : "24000/1001", }, +'dnx_720p_90_25' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 90, "pix_fmt" : "yuv422p", "frame_rate" : "25/1", }, +'dnx_720p_110_29.97' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 110, "pix_fmt" : "yuv422p", "frame_rate" : "30000/1001", }, +'dnx_720p_180_50' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 180, "pix_fmt" : "yuv422p", "frame_rate" : "50/1", }, +'dnx_720p_220_59.94' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 220, "pix_fmt" : "yuv422p", "frame_rate" : "60000/1001", }, +'dnx_720p_60_23.97' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 60, "pix_fmt" : "yuv422p", "frame_rate" : "24000/1001", }, +'dnx_720p_60_25' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 60, "pix_fmt" : "yuv422p", "frame_rate" : "25/1", }, +'dnx_720p_75_29.97' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 75, "pix_fmt" : "yuv422p", "frame_rate" : "30000/1001", }, +'dnx_720p_120_50' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 120, "pix_fmt" : "yuv422p", "frame_rate" : "50/1", }, +'dnx_720p_145_59.94' : { "size" : (1280, 720), 'interlaced' : False, "bitrate" : 145, "pix_fmt" : "yuv422p", "frame_rate" : "60000/1001", }, +'dnx_1080p_36_23.97' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 36, "pix_fmt" : "yuv422p", "frame_rate" : "24000/1001", }, +'dnx_1080p_36_25' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 36, "pix_fmt" : "yuv422p", "frame_rate" : "25/1", }, +'dnx_1080p_45_29.97' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 45, "pix_fmt" : "yuv422p", "frame_rate" : "30000/1001", }, +'dnx_1080p_75_50' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 75, "pix_fmt" : "yuv422p", "frame_rate" : "50/1", }, +'dnx_1080p_90_59.94' : { "size" : (1920, 1080), 'interlaced' : False, "bitrate" : 90, "pix_fmt" : "yuv422p", "frame_rate" : "60000/1001", }, +'dnxhr_lb' : { "size" : None, 'interlaced' : False, "bitrate" : None, "pix_fmt" : "yuv422p", "frame_rate" : None, "video_profile": "dnxhr_lb", }, +'dnxhr_sq' : { "size" : None, 'interlaced' : False, "bitrate" : None, "pix_fmt" : "yuv422p", "frame_rate" : None, "video_profile": "dnxhr_sq", }, +'dnxhr_hq' : { "size" : None, 'interlaced' : False, "bitrate" : None, "pix_fmt" : "yuv422p", "frame_rate" : None, "video_profile": "dnxhr_hq", }, +'dnxhr_hqx' : { "size" : None, 'interlaced' : False, "bitrate" : None, "pix_fmt" : "yuv422p", "frame_rate" : None, "video_profile": "dnxhr_hqx",}, +} + +dnxhd_frame_sizes = { +1235 : 917504, +1237 : 606208, +1238 : 917504, +1241 : 917504, +1242 : 606208, +1243 : 917504, +1244 : 606208, +1250 : 458752, +1251 : 458752, +1252 : 303104, +1253 : 188416, +1256 : 1835008, +1258 : 212992, +1259 : 417792, +1260 : 835584, +} + +dnxhr_compression_ratio = { +1270 : (57344, 255), # dnxhr_444 +1271 : (28672, 255), # dnxhr_hqx +1272 : (28672, 255), # dnxhr_hq +1273 : (18944, 255), # dnxhr_sq +1274 : (5888, 255), # dnxhr_lb +} + +dnx_compression_auids = { +1235 : AUID("04010202-7101-0000-060e-2b340401010a"), +1236 : AUID("04010202-7102-0000-060e-2b340401010a"), +1237 : AUID("04010202-7103-0000-060e-2b340401010a"), +1238 : AUID("04010202-7104-0000-060e-2b340401010a"), + +1241 : AUID("04010202-7107-0000-060e-2b340401010a"), +1242 : AUID("04010202-7108-0000-060e-2b340401010a"), +1243 : AUID("04010202-7109-0000-060e-2b340401010a"), +1244 : AUID("04010202-710a-0000-060e-2b340401010a"), + +1250 : AUID("04010202-7110-0000-060e-2b340401010a"), +1251 : AUID("04010202-7111-0000-060e-2b340401010a"), +1252 : AUID("04010202-7112-0000-060e-2b340401010a"), +1253 : AUID("04010202-7113-0000-060e-2b340401010a"), + +1256 : AUID("04010202-7116-0000-060e-2b340401010a"), +1257 : AUID("04010202-7117-0000-060e-2b340401010a"), +1258 : AUID("04010202-7118-0000-060e-2b340401010a"), +1259 : AUID("04010202-7119-0000-060e-2b340401010a"), +1260 : AUID("04010202-711a-0000-060e-2b340401010a"), + +1270 : AUID("04010202-7124-0000-060e-2b340401010d"), # dnxhr_444 +1271 : AUID("04010202-7125-0000-060e-2b340401010d"), # dnxhr_hqx +1272 : AUID("04010202-7126-0000-060e-2b340401010d"), # dnxhr_hq +1273 : AUID("04010202-7127-0000-060e-2b340401010d"), # dnxhr_sq +1274 : AUID("04010202-7128-0000-060e-2b340401010d"), # dnxhr_lb + +} + +compression_ids = { +'CompressedPicture' : AUID('04010202-0000-0000-060e-2b3404010101'), +'AVCBaselineUnconstrained' : AUID('04010202-0131-1001-060e-2b340401010d'), +'AVCConstrainedBaselineUnconstrained' : AUID('04010202-0131-1101-060e-2b340401010d'), +'AVCMainUnconstrained' : AUID('04010202-0131-2001-060e-2b340401010d'), +'AVCExtendedUnconstrained' : AUID('04010202-0131-3001-060e-2b340401010d'), +'AVCHighUnconstrained' : AUID('04010202-0131-4001-060e-2b340401010d'), +'AVCHigh10Unconstrained' : AUID('04010202-0131-5001-060e-2b340401010d'), +'AVCHigh422Unconstrained' : AUID('04010202-0131-6001-060e-2b340401010d'), +'AVCHigh444PredictiveUnconstrained' : AUID('04010202-0131-7001-060e-2b340401010d'), +'AVCHigh10IntraUnconstrained' : AUID('04010202-0132-2001-060e-2b340401010a'), +'AVCHigh422IntraUnconstrained' : AUID('04010202-0132-3001-060e-2b340401010a'), +'AVCHigh444IntraUnconstrained' : AUID('04010202-0132-4001-060e-2b340401010d'), +'AVCCAVLC444IntraUnconstrained' : AUID('04010202-0132-5001-060e-2b340401010d'), +'mjpeg' : AUID('0e040201-0205-0100-060e-2b3404010101'), +} + +def dnx_frame_size(cid, width=None, height=None): + size = dnxhd_frame_sizes.get(cid, None) + if size: + return size + + # DNxHR frame size caclulation + ratio = dnxhr_compression_ratio[cid] + size = ((height + 15) // 16) * ((width + 15) // 16) * ratio[0] // ratio[1] + size = (size + 2048) // 4096 * 4096; + + return max(size, 8192); + + +def valid_dnx_prefix(prefix): + + # DNxHD prefix + dnxhd_header_prefix = 0x000002800100 + if prefix == dnxhd_header_prefix: + return True + + # DNxHR prefix + data_offset = prefix >> 16 + if ((prefix & 0xFFFF0000FFFF) == 0x0300 and + data_offset >= 0x0280 and data_offset <= 0x2170 and + (data_offset & 3) == 0): + return True + + return False + +def read_dnx_frame_header(dnx_header): + if len(dnx_header) < 640: + raise ValueError("Invalid DNxHD frame: header to Short") + + prefix = int_from_bytes(bytearray(dnx_header[:6]), byte_order='big') & 0xffffffffff00 + if not valid_dnx_prefix(prefix): + raise ValueError("Invalid DNxHD frame: unknown prefix: 0x%012X" % prefix) + + #NOTE Stored height then width... + height, width = unpack(b">24xhh", dnx_header[:28]) + cid = unpack(b">40xi", dnx_header[:44])[0] + + interlaced = unpack(b'B', dnx_header[5:6])[0] & 2 != 0 + bitdepth = unpack(b'B', dnx_header[33:34])[0] >> 5 + if bitdepth == 1: + bitdepth = 8 + elif bitdepth == 2: + bitdepth = 10 + elif bitdepth == 3: + bitdepth = 12 + else: + raise ValueError("Invalid DNxHD frame: unknown bitdepth: %d" % bitdepth) + + horizontal_subsampling = 2 + # 444 HorizontalSubsampling + if (unpack(b'B', dnx_header[44:45])[0] >> 6) & 1 != 0: + raise NotImplementedError("444 not tested") + horizontal_subsampling = 4 + + return cid, width, height, bitdepth, interlaced + +def iter_dnx_stream(f): + while True: + dnx_header = f.read(640) + if not dnx_header or len(dnx_header) != 640: + break + cid, width, height, bitdepth, interlaced = read_dnx_frame_header(dnx_header) + frame_size = dnx_frame_size(cid, width, height) + + data = BytesIO() + data.write(dnx_header) + data.write(f.read(frame_size - 640)) + yield data.getvalue() diff --git a/createaaf.exe b/createaaf.exe index b5ffbac..48f8e6c 100644 Binary files a/createaaf.exe and b/createaaf.exe differ