Created
April 8, 2023 21:05
-
-
Save rvanlaar/4f69b87b0f0af8efef83157cd43b4121 to your computer and use it in GitHub Desktop.
QT mrcrowbar
This file contains 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 characters
#!/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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment