890 lines
28 KiB
Python
890 lines
28 KiB
Python
|
#!/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)
|