890 lines
28 KiB
Python
Raw Normal View History

2025-03-18 00:17:27 +05:30
#!/usr/bin/env python
"""
Copyright 2016 beardypig
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import logging
from uuid import UUID
from construct import *
import construct.core
from construct.lib import *
log = logging.getLogger(__name__)
UNITY_MATRIX = [0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000]
class PrefixedIncludingSize(Subconstruct):
__slots__ = ["name", "lengthfield", "subcon"]
def __init__(self, lengthfield, subcon):
super(PrefixedIncludingSize, self).__init__(subcon)
self.lengthfield = lengthfield
def _parse(self, stream, context, path):
try:
lengthfield_size = self.lengthfield.sizeof()
length = self.lengthfield._parse(stream, context, path)
except SizeofError:
offset_start = stream.tell()
length = self.lengthfield._parse(stream, context, path)
lengthfield_size = stream.tell() - offset_start
stream2 = BoundBytesIO(stream, length - lengthfield_size)
obj = self.subcon._parse(stream2, context, path)
return obj
def _build(self, obj, stream, context, path):
try:
# needs to be both fixed size, seekable and tellable (third not checked)
self.lengthfield.sizeof()
if not stream.seekable:
raise SizeofError
offset_start = stream.tell()
self.lengthfield._build(0, stream, context, path)
self.subcon._build(obj, stream, context, path)
offset_end = stream.tell()
stream.seek(offset_start)
self.lengthfield._build(offset_end - offset_start, stream, context, path)
stream.seek(offset_end)
except SizeofError:
data = self.subcon.build(obj, context)
sl, p_sl = 0, 0
dlen = len(data)
# do..while
i = 0
while True:
i += 1
p_sl = sl
sl = len(self.lengthfield.build(dlen + sl))
if p_sl == sl: break
self.lengthfield._build(dlen + sl, stream, context, path)
else:
self.lengthfield._build(len(data), stream, context, path)
construct.core._write_stream(stream, len(data), data)
def _sizeof(self, context, path):
return self.lengthfield._sizeof(context, path) + self.subcon._sizeof(context, path)
# Header box
FileTypeBox = Struct(
"type" / Const(b"ftyp"),
"major_brand" / String(4),
"minor_version" / Int32ub,
"compatible_brands" / GreedyRange(String(4)),
)
SegmentTypeBox = Struct(
"type" / Const(b"styp"),
"major_brand" / String(4),
"minor_version" / Int32ub,
"compatible_brands" / GreedyRange(String(4)),
)
# Catch find boxes
RawBox = Struct(
"type" / String(4, padchar=b" ", paddir="right"),
"data" / Default(GreedyBytes, b"")
)
FreeBox = Struct(
"type" / Const(b"free"),
"data" / GreedyBytes
)
SkipBox = Struct(
"type" / Const(b"skip"),
"data" / GreedyBytes
)
# Movie boxes, contained in a moov Box
MovieHeaderBox = Struct(
"type" / Const(b"mvhd"),
"version" / Default(Int8ub, 0),
"flags" / Default(Int24ub, 0),
Embedded(Switch(this.version, {
1: Struct(
"creation_time" / Default(Int64ub, 0),
"modification_time" / Default(Int64ub, 0),
"timescale" / Default(Int32ub, 10000000),
"duration" / Int64ub
),
0: Struct(
"creation_time" / Default(Int32ub, 0),
"modification_time" / Default(Int32ub, 0),
"timescale" / Default(Int32ub, 10000000),
"duration" / Int32ub,
),
})),
"rate" / Default(Int32sb, 65536),
"volume" / Default(Int16sb, 256),
# below could be just Padding(10) but why not
Const(Int16ub, 0),
Const(Int32ub, 0),
Const(Int32ub, 0),
"matrix" / Default(Int32sb[9], UNITY_MATRIX),
"pre_defined" / Default(Int32ub[6], [0] * 6),
"next_track_ID" / Default(Int32ub, 0xffffffff)
)
# Track boxes, contained in trak box
TrackHeaderBox = Struct(
"type" / Const(b"tkhd"),
"version" / Default(Int8ub, 0),
"flags" / Default(Int24ub, 1),
Embedded(Switch(this.version, {
1: Struct(
"creation_time" / Default(Int64ub, 0),
"modification_time" / Default(Int64ub, 0),
"track_ID" / Default(Int32ub, 1),
Padding(4),
"duration" / Default(Int64ub, 0),
),
0: Struct(
"creation_time" / Default(Int32ub, 0),
"modification_time" / Default(Int32ub, 0),
"track_ID" / Default(Int32ub, 1),
Padding(4),
"duration" / Default(Int32ub, 0),
),
})),
Padding(8),
"layer" / Default(Int16sb, 0),
"alternate_group" / Default(Int16sb, 0),
"volume" / Default(Int16sb, 0),
Padding(2),
"matrix" / Default(Array(9, Int32sb), UNITY_MATRIX),
"width" / Default(Int32ub, 0),
"height" / Default(Int32ub, 0),
)
HDSSegmentBox = Struct(
"type" / Const(b"abst"),
"version" / Default(Int8ub, 0),
"flags" / Default(Int24ub, 0),
"info_version" / Int32ub,
EmbeddedBitStruct(
Padding(1),
"profile" / Flag,
"live" / Flag,
"update" / Flag,
Padding(4)
),
"time_scale" / Int32ub,
"current_media_time" / Int64ub,
"smpte_time_code_offset" / Int64ub,
"movie_identifier" / CString(),
"server_entry_table" / PrefixedArray(Int8ub, CString()),
"quality_entry_table" / PrefixedArray(Int8ub, CString()),
"drm_data" / CString(),
"metadata" / CString(),
"segment_run_table" / PrefixedArray(Int8ub, LazyBound(lambda x: Box)),
"fragment_run_table" / PrefixedArray(Int8ub, LazyBound(lambda x: Box))
)
HDSSegmentRunBox = Struct(
"type" / Const(b"asrt"),
"version" / Default(Int8ub, 0),
"flags" / Default(Int24ub, 0),
"quality_entry_table" / PrefixedArray(Int8ub, CString()),
"segment_run_enteries" / PrefixedArray(Int32ub, Struct(
"first_segment" / Int32ub,
"fragments_per_segment" / Int32ub
))
)
HDSFragmentRunBox = Struct(
"type" / Const(b"afrt"),
"version" / Default(Int8ub, 0),
"flags" / BitStruct(
Padding(23),
"update" / Flag
),
"time_scale" / Int32ub,
"quality_entry_table" / PrefixedArray(Int8ub, CString()),
"fragment_run_enteries" / PrefixedArray(Int32ub, Struct(
"first_fragment" / Int32ub,
"first_fragment_timestamp" / Int64ub,
"fragment_duration" / Int32ub,
"discontinuity" / If(this.fragment_duration == 0, Int8ub)
))
)
# Boxes contained by Media Box
class ISO6392TLanguageCode(Adapter):
def _decode(self, obj, context):
"""
Get the python representation of the obj
"""
return b''.join(map(int2byte, [c + 0x60 for c in bytearray(obj)])).decode("utf8")
def _encode(self, obj, context):
"""
Get the bytes representation of the obj
"""
return [c - 0x60 for c in bytearray(obj.encode("utf8"))]
MediaHeaderBox = Struct(
"type" / Const(b"mdhd"),
"version" / Default(Int8ub, 0),
"flags" / Const(Int24ub, 0),
"creation_time" / IfThenElse(this.version == 1, Int64ub, Int32ub),
"modification_time" / IfThenElse(this.version == 1, Int64ub, Int32ub),
"timescale" / Int32ub,
"duration" / IfThenElse(this.version == 1, Int64ub, Int32ub),
Embedded(BitStruct(
Padding(1),
"language" / ISO6392TLanguageCode(BitsInteger(5)[3]),
)),
Padding(2, pattern=b"\x00"),
)
HandlerReferenceBox = Struct(
"type" / Const(b"hdlr"),
"version" / Const(Int8ub, 0),
"flags" / Const(Int24ub, 0),
Padding(4, pattern=b"\x00"),
"handler_type" / String(4),
Padding(12, pattern=b"\x00"), # Int32ub[3]
"name" / CString(encoding="utf8")
)
# Boxes contained by Media Info Box
VideoMediaHeaderBox = Struct(
"type" / Const(b"vmhd"),
"version" / Default(Int8ub, 0),
"flags" / Const(Int24ub, 1),
"graphics_mode" / Default(Int16ub, 0),
"opcolor" / Struct(
"red" / Default(Int16ub, 0),
"green" / Default(Int16ub, 0),
"blue" / Default(Int16ub, 0),
),
)
DataEntryUrlBox = PrefixedIncludingSize(Int32ub, Struct(
"type" / Const(b"url "),
"version" / Const(Int8ub, 0),
"flags" / BitStruct(
Padding(23), "self_contained" / Rebuild(Flag, ~this._.location)
),
"location" / If(~this.flags.self_contained, CString(encoding="utf8")),
))
DataEntryUrnBox = PrefixedIncludingSize(Int32ub, Struct(
"type" / Const(b"urn "),
"version" / Const(Int8ub, 0),
"flags" / BitStruct(
Padding(23), "self_contained" / Rebuild(Flag, ~(this._.name & this._.location))
),
"name" / If(this.flags == 0, CString(encoding="utf8")),
"location" / If(this.flags == 0, CString(encoding="utf8")),
))
DataReferenceBox = Struct(
"type" / Const(b"dref"),
"version" / Const(Int8ub, 0),
"flags" / Default(Int24ub, 0),
"data_entries" / PrefixedArray(Int32ub, Select(DataEntryUrnBox, DataEntryUrlBox)),
)
# Sample Table boxes (stbl)
MP4ASampleEntryBox = Struct(
"version" / Default(Int16ub, 0),
"revision" / Const(Int16ub, 0),
"vendor" / Const(Int32ub, 0),
"channels" / Default(Int16ub, 2),
"bits_per_sample" / Default(Int16ub, 16),
"compression_id" / Default(Int16sb, 0),
"packet_size" / Const(Int16ub, 0),
"sampling_rate" / Int16ub,
Padding(2)
)
class MaskedInteger(Adapter):
def _decode(self, obj, context):
return obj & 0x1F
def _encode(self, obj, context):
return obj & 0x1F
AAVC = Struct(
"version" / Const(Int8ub, 1),
"profile" / Int8ub,
"compatibility" / Int8ub,
"level" / Int8ub,
EmbeddedBitStruct(
Padding(6, pattern=b'\x01'),
"nal_unit_length_field" / Default(BitsInteger(2), 3),
),
"sps" / Default(PrefixedArray(MaskedInteger(Int8ub), PascalString(Int16ub)), []),
"pps" / Default(PrefixedArray(Int8ub, PascalString(Int16ub)), [])
)
HVCC = Struct(
EmbeddedBitStruct(
"version" / Const(BitsInteger(8), 1),
"profile_space" / BitsInteger(2),
"general_tier_flag" / BitsInteger(1),
"general_profile" / BitsInteger(5),
"general_profile_compatibility_flags" / BitsInteger(32),
"general_constraint_indicator_flags" / BitsInteger(48),
"general_level" / BitsInteger(8),
Padding(4, pattern=b'\xff'),
"min_spatial_segmentation" / BitsInteger(12),
Padding(6, pattern=b'\xff'),
"parallelism_type" / BitsInteger(2),
Padding(6, pattern=b'\xff'),
"chroma_format" / BitsInteger(2),
Padding(5, pattern=b'\xff'),
"luma_bit_depth" / BitsInteger(3),
Padding(5, pattern=b'\xff'),
"chroma_bit_depth" / BitsInteger(3),
"average_frame_rate" / BitsInteger(16),
"constant_frame_rate" / BitsInteger(2),
"num_temporal_layers" / BitsInteger(3),
"temporal_id_nested" / BitsInteger(1),
"nalu_length_size" / BitsInteger(2),
),
# TODO: parse NALUs
"raw_bytes" / GreedyBytes
)
AVC1SampleEntryBox = Struct(
"version" / Default(Int16ub, 0),
"revision" / Const(Int16ub, 0),
"vendor" / Default(String(4, padchar=b" "), b"brdy"),
"temporal_quality" / Default(Int32ub, 0),
"spatial_quality" / Default(Int32ub, 0),
"width" / Int16ub,
"height" / Int16ub,
"horizontal_resolution" / Default(Int16ub, 72), # TODO: actually a fixed point decimal
Padding(2),
"vertical_resolution" / Default(Int16ub, 72), # TODO: actually a fixed point decimal
Padding(2),
"data_size" / Const(Int32ub, 0),
"frame_count" / Default(Int16ub, 1),
"compressor_name" / Default(String(32, padchar=b" "), ""),
"depth" / Default(Int16ub, 24),
"color_table_id" / Default(Int16sb, -1),
"avc_data" / PrefixedIncludingSize(Int32ub, Struct(
"type" / String(4, padchar=b" ", paddir="right"),
Embedded(Switch(this.type, {
b"avcC": AAVC,
b"hvcC": HVCC,
}, Struct("data" / GreedyBytes)))
)),
"sample_info" / LazyBound(lambda _: GreedyRange(Box))
)
SampleEntryBox = PrefixedIncludingSize(Int32ub, Struct(
"format" / String(4, padchar=b" ", paddir="right"),
Padding(6, pattern=b"\x00"),
"data_reference_index" / Default(Int16ub, 1),
Embedded(Switch(this.format, {
b"ec-3": MP4ASampleEntryBox,
b"mp4a": MP4ASampleEntryBox,
b"enca": MP4ASampleEntryBox,
b"avc1": AVC1SampleEntryBox,
b"encv": AVC1SampleEntryBox,
b"wvtt": Struct("children" / LazyBound(lambda ctx: GreedyRange(Box)))
}, Struct("data" / GreedyBytes)))
))
BitRateBox = Struct(
"type" / Const(b"btrt"),
"bufferSizeDB" / Int32ub,
"maxBitrate" / Int32ub,
"avgBirate" / Int32ub,
)
SampleDescriptionBox = Struct(
"type" / Const(b"stsd"),
"version" / Default(Int8ub, 0),
"flags" / Const(Int24ub, 0),
"entries" / PrefixedArray(Int32ub, SampleEntryBox)
)
SampleSizeBox = Struct(
"type" / Const(b"stsz"),
"version" / Int8ub,
"flags" / Const(Int24ub, 0),
"sample_size" / Int32ub,
"sample_count" / Int32ub,
"entry_sizes" / If(this.sample_size == 0, Array(this.sample_count, Int32ub))
)
SampleSizeBox2 = Struct(
"type" / Const(b"stz2"),
"version" / Int8ub,
"flags" / Const(Int24ub, 0),
Padding(3, pattern=b"\x00"),
"field_size" / Int8ub,
"sample_count" / Int24ub,
"entries" / Array(this.sample_count, Struct(
"entry_size" / LazyBound(lambda ctx: globals()["Int%dub" % ctx.field_size])
))
)
SampleDegradationPriorityBox = Struct(
"type" / Const(b"stdp"),
"version" / Const(Int8ub, 0),
"flags" / Const(Int24ub, 0),
)
TimeToSampleBox = Struct(
"type" / Const(b"stts"),
"version" / Const(Int8ub, 0),
"flags" / Const(Int24ub, 0),
"entries" / Default(PrefixedArray(Int32ub, Struct(
"sample_count" / Int32ub,
"sample_delta" / Int32ub,
)), [])
)
SyncSampleBox = Struct(
"type" / Const(b"stss"),
"version" / Const(Int8ub, 0),
"flags" / Const(Int24ub, 0),
"entries" / Default(PrefixedArray(Int32ub, Struct(
"sample_number" / Int32ub,
)), [])
)
SampleToChunkBox = Struct(
"type" / Const(b"stsc"),
"version" / Const(Int8ub, 0),
"flags" / Const(Int24ub, 0),
"entries" / Default(PrefixedArray(Int32ub, Struct(
"first_chunk" / Int32ub,
"samples_per_chunk" / Int32ub,
"sample_description_index" / Int32ub,
)), [])
)
ChunkOffsetBox = Struct(
"type" / Const(b"stco"),
"version" / Const(Int8ub, 0),
"flags" / Const(Int24ub, 0),
"entries" / Default(PrefixedArray(Int32ub, Struct(
"chunk_offset" / Int32ub,
)), [])
)
ChunkLargeOffsetBox = Struct(
"type" / Const(b"co64"),
"version" / Const(Int8ub, 0),
"flags" / Const(Int24ub, 0),
"entries" / PrefixedArray(Int32ub, Struct(
"chunk_offset" / Int64ub,
))
)
# Movie Fragment boxes, contained in moof box
MovieFragmentHeaderBox = Struct(
"type" / Const(b"mfhd"),
"version" / Const(Int8ub, 0),
"flags" / Const(Int24ub, 0),
"sequence_number" / Int32ub
)
TrackFragmentBaseMediaDecodeTimeBox = Struct(
"type" / Const(b"tfdt"),
"version" / Int8ub,
"flags" / Const(Int24ub, 0),
"baseMediaDecodeTime" / Switch(this.version, {1: Int64ub, 0: Int32ub})
)
TrackSampleFlags = BitStruct(
Padding(4),
"is_leading" / Default(Enum(BitsInteger(2), UNKNOWN=0, LEADINGDEP=1, NOTLEADING=2, LEADINGNODEP=3, default=0), 0),
"sample_depends_on" / Default(Enum(BitsInteger(2), UNKNOWN=0, DEPENDS=1, NOTDEPENDS=2, RESERVED=3, default=0), 0),
"sample_is_depended_on" / Default(Enum(BitsInteger(2), UNKNOWN=0, NOTDISPOSABLE=1, DISPOSABLE=2, RESERVED=3, default=0), 0),
"sample_has_redundancy" / Default(Enum(BitsInteger(2), UNKNOWN=0, REDUNDANT=1, NOTREDUNDANT=2, RESERVED=3, default=0), 0),
"sample_padding_value" / Default(BitsInteger(3), 0),
"sample_is_non_sync_sample" / Default(Flag, False),
"sample_degradation_priority" / Default(BitsInteger(16), 0),
)
TrackRunBox = Struct(
"type" / Const(b"trun"),
"version" / Int8ub,
"flags" / BitStruct(
Padding(12),
"sample_composition_time_offsets_present" / Flag,
"sample_flags_present" / Flag,
"sample_size_present" / Flag,
"sample_duration_present" / Flag,
Padding(5),
"first_sample_flags_present" / Flag,
Padding(1),
"data_offset_present" / Flag,
),
"sample_count" / Int32ub,
"data_offset" / Default(If(this.flags.data_offset_present, Int32sb), None),
"first_sample_flags" / Default(If(this.flags.first_sample_flags_present, Int32ub), None),
"sample_info" / Array(this.sample_count, Struct(
"sample_duration" / If(this._.flags.sample_duration_present, Int32ub),
"sample_size" / If(this._.flags.sample_size_present, Int32ub),
"sample_flags" / If(this._.flags.sample_flags_present, TrackSampleFlags),
"sample_composition_time_offsets" / If(
this._.flags.sample_composition_time_offsets_present,
IfThenElse(this._.version == 0, Int32ub, Int32sb)
),
))
)
TrackFragmentHeaderBox = Struct(
"type" / Const(b"tfhd"),
"version" / Int8ub,
"flags" / BitStruct(
Padding(6),
"default_base_is_moof" / Flag,
"duration_is_empty" / Flag,
Padding(10),
"default_sample_flags_present" / Flag,
"default_sample_size_present" / Flag,
"default_sample_duration_present" / Flag,
Padding(1),
"sample_description_index_present" / Flag,
"base_data_offset_present" / Flag,
),
"track_ID" / Int32ub,
"base_data_offset" / Default(If(this.flags.base_data_offset_present, Int64ub), None),
"sample_description_index" / Default(If(this.flags.sample_description_index_present, Int32ub), None),
"default_sample_duration" / Default(If(this.flags.default_sample_duration_present, Int32ub), None),
"default_sample_size" / Default(If(this.flags.default_sample_size_present, Int32ub), None),
"default_sample_flags" / Default(If(this.flags.default_sample_flags_present, TrackSampleFlags), None),
)
MovieExtendsHeaderBox = Struct(
"type" / Const(b"mehd"),
"version" / Default(Int8ub, 0),
"flags" / Const(Int24ub, 0),
"fragment_duration" / IfThenElse(this.version == 1,
Default(Int64ub, 0),
Default(Int32ub, 0))
)
TrackExtendsBox = Struct(
"type" / Const(b"trex"),
"version" / Const(Int8ub, 0),
"flags" / Const(Int24ub, 0),
"track_ID" / Int32ub,
"default_sample_description_index" / Default(Int32ub, 1),
"default_sample_duration" / Default(Int32ub, 0),
"default_sample_size" / Default(Int32ub, 0),
"default_sample_flags" / Default(TrackSampleFlags, Container()),
)
SegmentIndexBox = Struct(
"type" / Const(b"sidx"),
"version" / Int8ub,
"flags" / Const(Int24ub, 0),
"reference_ID" / Int32ub,
"timescale" / Int32ub,
"earliest_presentation_time" / IfThenElse(this.version == 0, Int32ub, Int64ub),
"first_offset" / IfThenElse(this.version == 0, Int32ub, Int64ub),
Padding(2),
"reference_count" / Int16ub,
"references" / Array(this.reference_count, BitStruct(
"reference_type" / Enum(BitsInteger(1), INDEX=1, MEDIA=0),
"referenced_size" / BitsInteger(31),
"segment_duration" / BitsInteger(32),
"starts_with_SAP" / Flag,
"SAP_type" / BitsInteger(3),
"SAP_delta_time" / BitsInteger(28),
))
)
SampleAuxiliaryInformationSizesBox = Struct(
"type" / Const(b"saiz"),
"version" / Const(Int8ub, 0),
"flags" / BitStruct(
Padding(23),
"has_aux_info_type" / Flag,
),
# Optional fields
"aux_info_type" / Default(If(this.flags.has_aux_info_type, Int32ub), None),
"aux_info_type_parameter" / Default(If(this.flags.has_aux_info_type, Int32ub), None),
"default_sample_info_size" / Int8ub,
"sample_count" / Int32ub,
# only if sample default_sample_info_size is 0
"sample_info_sizes" / If(this.default_sample_info_size == 0,
Array(this.sample_count, Int8ub))
)
SampleAuxiliaryInformationOffsetsBox = Struct(
"type" / Const(b"saio"),
"version" / Int8ub,
"flags" / BitStruct(
Padding(23),
"has_aux_info_type" / Flag,
),
# Optional fields
"aux_info_type" / Default(If(this.flags.has_aux_info_type, Int32ub), None),
"aux_info_type_parameter" / Default(If(this.flags.has_aux_info_type, Int32ub), None),
# Short offsets in version 0, long in version 1
"offsets" / PrefixedArray(Int32ub, Switch(this.version, {0: Int32ub, 1: Int64ub}))
)
# Movie data box
MovieDataBox = Struct(
"type" / Const(b"mdat"),
"data" / GreedyBytes
)
# Media Info Box
SoundMediaHeaderBox = Struct(
"type" / Const(b"smhd"),
"version" / Const(Int8ub, 0),
"flags" / Const(Int24ub, 0),
"balance" / Default(Int16sb, 0),
"reserved" / Const(Int16ub, 0)
)
# DASH Boxes
class UUIDBytes(Adapter):
def _decode(self, obj, context):
return UUID(bytes=obj)
def _encode(self, obj, context):
return obj.bytes
ProtectionSystemHeaderBox = Struct(
"type" / If(this._.type != b"uuid", Const(b"pssh")),
"version" / Rebuild(Int8ub, lambda ctx: 1 if (hasattr(ctx, "key_IDs") and ctx.key_IDs) else 0),
"flags" / Const(Int24ub, 0),
"system_ID" / UUIDBytes(Bytes(16)),
"key_IDs" / Default(If(this.version == 1,
PrefixedArray(Int32ub, UUIDBytes(Bytes(16)))),
None),
"init_data" / Prefixed(Int32ub, GreedyBytes)
)
TrackEncryptionBox = Struct(
"type" / If(this._.type != b"uuid", Const(b"tenc")),
"version" / Default(OneOf(Int8ub, (0, 1)), 0),
"flags" / Default(Int24ub, 0),
"_reserved" / Const(Int8ub, 0),
"default_byte_blocks" / Default(IfThenElse(
this.version > 0,
BitStruct(
# count of encrypted blocks in the protection pattern, where each block is 16-bytes
"crypt" / Nibble,
# count of unencrypted blocks in the protection pattern
"skip" / Nibble
),
Const(Int8ub, 0)
), 0),
"is_encrypted" / OneOf(Int8ub, (0, 1)),
"iv_size" / OneOf(Int8ub, (0, 8, 16)),
"key_ID" / UUIDBytes(Bytes(16)),
"constant_iv" / Default(If(
this.is_encrypted and this.iv_size == 0,
PrefixedArray(Int8ub, Byte)
), None)
)
SampleEncryptionBox = Struct(
"type" / If(this._.type != b"uuid", Const(b"senc")),
"version" / Const(Int8ub, 0),
"flags" / BitStruct(
Padding(22),
"has_subsample_encryption_info" / Flag,
Padding(1)
),
"sample_encryption_info" / PrefixedArray(Int32ub, Struct(
"iv" / Bytes(8),
# include the sub sample encryption information
"subsample_encryption_info" / Default(If(this.flags.has_subsample_encryption_info, PrefixedArray(Int16ub, Struct(
"clear_bytes" / Int16ub,
"cipher_bytes" / Int32ub
))), None)
))
)
OriginalFormatBox = Struct(
"type" / Const(b"frma"),
"original_format" / Default(String(4), b"avc1")
)
SchemeTypeBox = Struct(
"type" / Const(b"schm"),
"version" / Default(Int8ub, 0),
"flags" / Default(Int24ub, 0),
"scheme_type" / Default(String(4), b"cenc"),
"scheme_version" / Default(Int32ub, 0x00010000),
"schema_uri" / Default(If(this.flags & 1 == 1, CString()), None)
)
ProtectionSchemeInformationBox = Struct(
"type" / Const(b"sinf"),
# TODO: define which children are required 'schm', 'schi' and 'tenc'
"children" / LazyBound(lambda _: GreedyRange(Box))
)
# PIFF boxes
UUIDBox = Struct(
"type" / Const(b"uuid"),
"extended_type" / UUIDBytes(Bytes(16)),
"data" / Switch(this.extended_type, {
UUID("A2394F52-5A9B-4F14-A244-6C427C648DF4"): SampleEncryptionBox,
UUID("D08A4F18-10F3-4A82-B6C8-32D8ABA183D3"): ProtectionSystemHeaderBox,
UUID("8974DBCE-7BE7-4C51-84F9-7148F9882554"): TrackEncryptionBox
}, GreedyBytes)
)
# WebVTT boxes
CueIDBox = Struct(
"type" / Const(b"iden"),
"cue_id" / GreedyString("utf8")
)
CueSettingsBox = Struct(
"type" / Const(b"sttg"),
"settings" / GreedyString("utf8")
)
CuePayloadBox = Struct(
"type" / Const(b"payl"),
"cue_text" / GreedyString("utf8")
)
WebVTTConfigurationBox = Struct(
"type" / Const(b"vttC"),
"config" / GreedyString("utf8")
)
WebVTTSourceLabelBox = Struct(
"type" / Const(b"vlab"),
"label" / GreedyString("utf8")
)
ContainerBoxLazy = LazyBound(lambda ctx: ContainerBox)
class TellMinusSizeOf(Subconstruct):
def __init__(self, subcon):
super(TellMinusSizeOf, self).__init__(subcon)
self.flagbuildnone = True
def _parse(self, stream, context, path):
return stream.tell() - self.subcon.sizeof(context)
def _build(self, obj, stream, context, path):
return b""
def sizeof(self, context=None, **kw):
return 0
Box = PrefixedIncludingSize(Int32ub, Struct(
"offset" / TellMinusSizeOf(Int32ub),
"type" / Peek(String(4, padchar=b" ", paddir="right")),
Embedded(Switch(this.type, {
b"ftyp": FileTypeBox,
b"styp": SegmentTypeBox,
b"mvhd": MovieHeaderBox,
b"moov": ContainerBoxLazy,
b"moof": ContainerBoxLazy,
b"mfhd": MovieFragmentHeaderBox,
b"tfdt": TrackFragmentBaseMediaDecodeTimeBox,
b"trun": TrackRunBox,
b"tfhd": TrackFragmentHeaderBox,
b"traf": ContainerBoxLazy,
b"mvex": ContainerBoxLazy,
b"mehd": MovieExtendsHeaderBox,
b"trex": TrackExtendsBox,
b"trak": ContainerBoxLazy,
b"mdia": ContainerBoxLazy,
b"tkhd": TrackHeaderBox,
b"mdat": MovieDataBox,
b"free": FreeBox,
b"skip": SkipBox,
b"mdhd": MediaHeaderBox,
b"hdlr": HandlerReferenceBox,
b"minf": ContainerBoxLazy,
b"vmhd": VideoMediaHeaderBox,
b"dinf": ContainerBoxLazy,
b"dref": DataReferenceBox,
b"stbl": ContainerBoxLazy,
b"stsd": SampleDescriptionBox,
b"stsz": SampleSizeBox,
b"stz2": SampleSizeBox2,
b"stts": TimeToSampleBox,
b"stss": SyncSampleBox,
b"stsc": SampleToChunkBox,
b"stco": ChunkOffsetBox,
b"co64": ChunkLargeOffsetBox,
b"smhd": SoundMediaHeaderBox,
b"sidx": SegmentIndexBox,
b"saiz": SampleAuxiliaryInformationSizesBox,
b"saio": SampleAuxiliaryInformationOffsetsBox,
b"btrt": BitRateBox,
# dash
b"tenc": TrackEncryptionBox,
b"pssh": ProtectionSystemHeaderBox,
b"senc": SampleEncryptionBox,
b"sinf": ProtectionSchemeInformationBox,
b"frma": OriginalFormatBox,
b"schm": SchemeTypeBox,
b"schi": ContainerBoxLazy,
# piff
b"uuid": UUIDBox,
# HDS boxes
b'abst': HDSSegmentBox,
b'asrt': HDSSegmentRunBox,
b'afrt': HDSFragmentRunBox,
# WebVTT
b"vttC": WebVTTConfigurationBox,
b"vlab": WebVTTSourceLabelBox,
b"vttc": ContainerBoxLazy,
b"vttx": ContainerBoxLazy,
b"iden": CueIDBox,
b"sttg": CueSettingsBox,
b"payl": CuePayloadBox
}, default=RawBox)),
"end" / Tell
))
ContainerBox = Struct(
"type" / String(4, padchar=b" ", paddir="right"),
"children" / GreedyRange(Box)
)
MP4 = GreedyRange(Box)