Created
April 8, 2023 21:05
-
-
Save rvanlaar/4f69b87b0f0af8efef83157cd43b4121 to your computer and use it in GitHub Desktop.
Revisions
-
rvanlaar renamed this gist
Apr 8, 2023 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
rvanlaar created this gist
Apr 8, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,522 @@ #!/usr/bin/env python3 """MrCrowbar file for Quicktime movies Specifiation is from: QuickTime File Format Specification https://multimedia.cx/mirror/qtff-2007-09-04.pdf All links in this file with #//apple... point to the relevant part in this document. """ import argparse from pathlib import Path from mrcrowbar import models as mrc from mrcrowbar.utils import from_uint32_be as FourCC from mrcrowbar.utils import to_uint32_be as FourCCB import struct class AppleFloatField(mrc.Field): def __init__(self, offset): default = [] super().__init__(default=default) self.offset = offset def get_from_buffer(self, buffer, parent=None): offset = self.offset a = struct.unpack(">h", buffer[offset : offset + 2])[0] # TODO: is b signed or unsigned? b = struct.unpack(">H", buffer[offset + 2 : offset + 4])[0] return a + b / 65536 class ContainerAtom(mrc.Block): atoms = mrc.ChunkField( mrc.Ref("CHUNK_MAP"), 0x00, id_field=mrc.UInt32_BE, length_field=mrc.UInt32_BE, default_klass=mrc.Unknown, length_before_id=True, length_inclusive=True, ) UNKNOWN_FOURCC = set() def print_unkown_fourccs(): if UNKNOWN_FOURCC: print("Unknown FourCCs:") for el in UNKNOWN_FOURCC: print(el) def create_knowntype(key, base_class=mrc.Unknown): fourcc = make_fourcc(key) UNKNOWN_FOURCC.add(fourcc) return type(f"{fourcc}-Unknown", (base_class,), {}) def make_fourcc(key): if not isinstance(key, int): return False try: return FourCCB(key).decode() except UnicodeDecodeError: return False class mrcdict(dict): def __getitem__(self, key): retval = super().__getitem__(key) return retval def __contains__(self, key): if make_fourcc(key): return True retval = super().__contains__(key) return retval def __missing__(self, key): return create_knowntype(key) # fmt: off class mvhdAtom(mrc.Block): """ MoVieHeaDer Specifies characteristics of the entire QuickTime movie. #//apple_ref/doc/uid/TP40000939-CH204-BBCGFGJG """ version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) # time in seconds since midnight January 1, 1904 creation_time = mrc.UInt32_BE(0x04) modification_time = mrc.UInt32_BE(0x08) # the number of time units that pass per second time_scale = mrc.UInt32_BE(0x0C) # duration of the movie in timescale units duration = mrc.UInt32_BE(0x10) # fixed point number, a value of 1.0 indicates normal rate preferred_rate = mrc.UInt32_BE(0x14) # fixed point number, a value of 1.0 indicates full volume preferred_volume = mrc.UInt16_BE(0x18) reserved = mrc.Bytes(0x1A, length=10) matrix_structure = mrc.Bytes(0x24, length=36) preview_time = mrc.UInt32_BE(0x48) preview_duration = mrc.UInt32_BE(0x4C) poster_time = mrc.UInt32_BE(0x50) selection_time = mrc.UInt32_BE(0x54) selection_duration = mrc.UInt32_BE(0x58) current_time = mrc.UInt32_BE(0x5C) next_track_id = mrc.UInt32_BE(0x60) class tkhdAtom(mrc.Block): """ TracKHeaDer #//apple_ref/doc/uid/TP40000939-CH204-BBCEIDFA """ version = mrc.UInt8(0x0) flags = mrc.Bytes(0x1, length=3) creation_time = mrc.UInt32_BE(0x4) modification_time = mrc.UInt32_BE(0x8) track_id = mrc.UInt32_BE(0xc) reserved = mrc.UInt32_BE(0x10) duration = mrc.UInt32_BE(0x14) reserved = mrc.Bytes(0x18, length=8) layer = mrc.UInt16_BE(0x20) alternate_group = mrc.UInt16_BE(0x22) volume = mrc.UInt16_BE(0x24) reserved = mrc.UInt16_BE(0x26) matrix_structure = mrc.Bytes(0x28, length=36) track_width = mrc.UInt32_BE(0x4c) track_height = mrc.UInt32_BE(0x50) class mdatAtom(mrc.Block): data = mrc.Bytes() class mdhdAtom(mrc.Block): version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) creation_time = mrc.UInt32_BE(0x04) modification_time = mrc.UInt32_BE(0x08) time_scale = mrc.UInt32_BE(0x0C) duration = mrc.UInt32_BE(0x10) language = mrc.UInt16_BE(0x14) quality = mrc.UInt16_BE(0x16) class hdlrAtom(mrc.Block): version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) component_type = mrc.UInt32_BE(0x04) component_subtype = mrc.UInt32_BE(0x08) component_manufacturer = mrc.UInt32_BE(0x0C) component_flags = mrc.UInt32_BE(0x10) component_flags_mask = mrc.UInt32_BE(0x14) component_name = mrc.Bytes(0x18) @property def repr(self): ct = FourCCB(self.component_type) cst = FourCCB(self.component_subtype) cm = FourCCB(self.component_manufacturer) cf = FourCCB(self.component_flags) cfm = FourCCB(self.component_flags_mask) # component name is a pascal string cn = self.component_name[1:].decode() return (f"Version={self.version}, flags={self.flags}, " f"Component Type={ct}, component SubType={cst}, " f"Component Manufacturer={cm}, Component Flags={cf}. " f"Component Flags Mask={cfm}, Component Name={cn}") class smhdAtom(mrc.Block): """ SoundMedia HeaDer """ version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) balance = mrc.UInt16_BE(0x04) reserved = mrc.UInt16_BE(0x06) class vmhdAtom(mrc.Block): """ VideoMedia HeaDer """ version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) graphics_mode = mrc.UInt16_BE(0x04) opcolor = mrc.Bytes(0x06, length=6) class EditListTableEntry(mrc.Block): track_duration = mrc.Int32_BE(0x00) media_time = mrc.Int32_BE(0x04) media_rate = mrc.Int32_BE(0x08) class elstAtom(mrc.Block): """ EditLiST """ version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) number_of_entries = mrc.UInt32_BE(0x04) edit_list_table = mrc.BlockField(EditListTableEntry, 0x08, count=mrc.Ref("number_of_entries")) class drefSubAtom(mrc.Block): version = mrc.UInt32_BE(0x00) flags = mrc.Bytes(0x04, length=3) data = mrc.Bytes(0x07) class drefAtom(mrc.Block): """ Data REFerance Atom """ CHUNK_MAP = mrcdict() MAPPING = { FourCC(b"alis"): drefSubAtom, FourCC(b"rsrc"): drefSubAtom, FourCC(b"url "): drefSubAtom } CHUNK_MAP.update(MAPPING) version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) number_of_entries = mrc.UInt32_BE(0x04) atoms = mrc.ChunkField( mrc.Ref("CHUNK_MAP"), 0x08, id_field=mrc.UInt32_BE, length_field=mrc.UInt32_BE, default_klass=drefSubAtom, length_before_id=True, length_inclusive=True, ) class stcoAtom(mrc.Block): """ Chunk Offset """ version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) number_of_entries = mrc.UInt32_BE(0x04) chunk_offset_table = mrc.Bytes(0x08) class stsdAtom(mrc.Block): """ Sample description ## todo: check if data_format is in sample_description table and contains qtvr """ version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) number_of_entries = mrc.UInt32_BE(0x04) sample_description_table = mrc.Bytes(0x08) class stscAtom(mrc.Block): """ SampleTable Sample to Chunk """ version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) number_of_entries = mrc.UInt32_BE(0x04) sample_to_chunk_table = mrc.Bytes(0x08) class sttsAtom(mrc.Block): """ SampleTable Time to Sample """ version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) number_of_entries = mrc.UInt32_BE(0x04) time_to_sample_table = mrc.Bytes(0x08) class stssAtom(mrc.Block): """ SampleTable SyncSample Identifies the key frames """ version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) number_of_entries = mrc.UInt32_BE(0x04) sync_sample_table = mrc.Bytes(0x08) class stszAtom(mrc.Block): """ SampleTable Sample siZe """ version = mrc.UInt8(0x00) flags = mrc.Bytes(0x01, length=3) sample_size = mrc.UInt32_BE(0x04) number_of_entries = mrc.UInt32_BE(0x08) sample_size_table = mrc.Bytes(0x0C) class NAVGAtom(mrc.Block): version = mrc.UInt16_BE(0x00) columns = mrc.UInt16_BE(0x02) rows = mrc.UInt16_BE(0x04) reserved = mrc.UInt16_BE(0x06) loop_frames = mrc.UInt16_BE(0x08) loop_dur = mrc.UInt16_BE(0x0A) movietype = mrc.UInt16_BE(0x0C) loop_timescale = mrc.UInt16_BE(0x0E) fieldofview = AppleFloatField(0x10) startHPan = AppleFloatField(0x14) endHPan = AppleFloatField(0x18) endVTilt = AppleFloatField(0x1C) startVTilt = AppleFloatField(0x20) initialHPan = AppleFloatField(0x24) #initialVTiltMajor = mrc.UInt16_BE(0x28) #initialVPanComp = mrc.Int16_BE(0x2A) initialVTilt = AppleFloatField(0x28) reserved2 = mrc.UInt32_BE(0x2C) class gmhdAtom(mrc.Block): """ base Media inforation HeaDer Indicates that this media information atom pertains to a base media """ pass # fmt: on class dinfAtom(ContainerAtom): """ DataINFormation """ CHUNK_MAP = mrcdict() MAPPING = { FourCC(b"dref"): drefAtom, } CHUNK_MAP.update(MAPPING) class stblAtom(ContainerAtom): """ Sample TaBLE """ CHUNK_MAP = mrcdict() MAPPING = { FourCC(b"stco"): stcoAtom, FourCC(b"stsc"): stsdAtom, FourCC(b"stsd"): stsdAtom, FourCC(b"stts"): sttsAtom, FourCC(b"stsz"): stszAtom, FourCC(b"stss"): stssAtom, } CHUNK_MAP.update(MAPPING) class minfAtom(ContainerAtom): """ MedINFormation Store handler-specific information for a track's media data. """ CHUNK_MAP = mrcdict() MAPPING = { FourCC(b"hdlr"): hdlrAtom, FourCC(b"dinf"): dinfAtom, FourCC(b"stbl"): stblAtom, FourCC(b"smhd"): smhdAtom, FourCC(b"vmhd"): vmhdAtom, FourCC(b"gmhd"): gmhdAtom, } CHUNK_MAP.update(MAPPING) class edtsAtom(ContainerAtom): CHUNK_MAP = mrcdict() MAPPING = {FourCC(b"elst"): elstAtom} CHUNK_MAP.update(MAPPING) class mdiaAtom(ContainerAtom): CHUNK_MAP = mrcdict() MAPPING = { FourCC(b"mdhd"): mdhdAtom, FourCC(b"hdlr"): hdlrAtom, FourCC(b"minf"): minfAtom, } CHUNK_MAP.update(MAPPING) class trakAtom(ContainerAtom): """ Track Atom Defines a single track of a movie. """ CHUNK_MAP = mrcdict() MAPPING = { FourCC(b"mdia"): mdiaAtom, FourCC(b"tkhd"): tkhdAtom, FourCC(b"edts"): edtsAtom, } CHUNK_MAP.update(MAPPING) class ctypAtom(mrc.Block): """ Controller TYPe """ id = mrc.UInt32_BE(0x00) @property def repr(self): _id = FourCCB(self.id).decode() return f"id={_id}" class WLOCAtom(mrc.Block): """ Window LOCation """ x = mrc.UInt16_BE(0x00) y = mrc.UInt16_BE(0x02) class udtaAtom(mrc.Block): """ User DaTA Atom """ CHUNK_MAP = mrcdict() MAPPING = { FourCC(b"ctyp"): ctypAtom, FourCC(b"WLOC"): WLOCAtom, FourCC(b"NAVG"): NAVGAtom, } CHUNK_MAP.update(MAPPING) atoms = mrc.ChunkField( mrc.Ref("CHUNK_MAP"), 0x00, id_field=mrc.UInt32_BE, length_field=mrc.UInt32_BE, default_klass=mrc.Unknown, length_before_id=True, length_inclusive=True, stream_end=b"\x00\x00\x00\x00", ) # For historical reasons, the data list is optionally terminated by a 32-bit integer set to 0 class moovAtom(ContainerAtom): """ moov #//apple_ref/doc/uid/TP40000939-CH204-55911 """ CHUNK_MAP = mrcdict() MAPPING = { FourCC(b"mvhd"): mvhdAtom, FourCC(b"trak"): trakAtom, FourCC(b"udta"): udtaAtom, } CHUNK_MAP.update(MAPPING) class QuickTime(ContainerAtom): CHUNK_MAP = mrcdict() MAPPING = {FourCC(b"mdat"): mdatAtom, FourCC(b"moov"): moovAtom} CHUNK_MAP.update(MAPPING) def print_atom(atom, indent=0): indent_str = ">" * indent print(f"{indent_str}{atom}") if hasattr(atom, "atoms"): for el in atom.atoms: print_atom(el, indent + 1) if hasattr(atom, "obj") and hasattr(atom.obj, "atoms"): for el in atom.obj.atoms: print_atom(el, indent + 1) def parse_file(filename): f = open(filename, "rb").read() return QuickTime(f) def main(): parser = argparse.ArgumentParser() parser.add_argument("filename", metavar="FILE", type=Path, help="QT Movie filename") args = parser.parse_args() qt = parse_file(args.filename) print_atom(qt) print_unkown_fourccs() if __name__ == "__main__": main()