Skip to content

Instantly share code, notes, and snippets.

@rvanlaar
Created April 8, 2023 21:05
Show Gist options
  • Save rvanlaar/4f69b87b0f0af8efef83157cd43b4121 to your computer and use it in GitHub Desktop.
Save rvanlaar/4f69b87b0f0af8efef83157cd43b4121 to your computer and use it in GitHub Desktop.
QT mrcrowbar
#!/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