package matroska
// https://tools.ietf.org/html/draft-ietf-cellar-ebml-00
// https://matroska.org/technical/specs/index.html
// https://www.matroska.org/technical/basics.html
// https://www.matroska.org/technical/codec_specs.html
// https://wiki.xiph.org/MatroskaOpus
// TODO: refactor simepleblock/block to just defer decode etc?
// TODO: CRC
// TODO: value to names (TrackType etc)
// TODO: lacing
// TODO: handle garbage (see tcl and example files)
// TODO: could use md5 here somehow, see flac.go
import (
"embed"
"fmt"
"github.com/wader/fq/format"
"github.com/wader/fq/format/matroska/ebml"
"github.com/wader/fq/format/matroska/ebml_matroska"
"github.com/wader/fq/format/registry"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/ranges"
)
//go:embed *.jq
var matroskaFS embed.FS
var aacFrameFormat []*decode.Format
var av1CCRFormat []*decode.Format
var av1FrameFormat []*decode.Format
var flacFrameFormat []*decode.Format
var flacMetadatablocksFormat []*decode.Format
var imageFormat []*decode.Format
var mp3FrameFormat []*decode.Format
var mpegASCFrameFormat []*decode.Format
var mpegAVCAUFormat []*decode.Format
var mpegAVCDCRFormat []*decode.Format
var mpegHEVCDCRFormat []*decode.Format
var mpegHEVCSampleFormat []*decode.Format
var mpegPESPacketSampleFormat []*decode.Format
var mpegSPUFrameFormat []*decode.Format
var opusPacketFrameFormat []*decode.Format
var vorbisPacketFormat []*decode.Format
var vp8FrameFormat []*decode.Format
var vp9CFMFormat []*decode.Format
var vp9FrameFormat []*decode.Format
var codecToFormat map[string]*[]*decode.Format
func init() {
registry.MustRegister(&decode.Format{
Name: format.MATROSKA,
Description: "Matroska file",
Groups: []string{format.PROBE},
DecodeFn: matroskaDecode,
Dependencies: []decode.Dependency{
{Names: []string{format.AAC_FRAME}, Formats: &aacFrameFormat},
{Names: []string{format.AV1_CCR}, Formats: &av1CCRFormat},
{Names: []string{format.AV1_FRAME}, Formats: &av1FrameFormat},
{Names: []string{format.AVC_AU}, Formats: &mpegAVCAUFormat},
{Names: []string{format.AVC_DCR}, Formats: &mpegAVCDCRFormat},
{Names: []string{format.FLAC_FRAME}, Formats: &flacFrameFormat},
{Names: []string{format.FLAC_METADATABLOCKS}, Formats: &flacMetadatablocksFormat},
{Names: []string{format.HEVC_AU}, Formats: &mpegHEVCSampleFormat},
{Names: []string{format.HEVC_DCR}, Formats: &mpegHEVCDCRFormat},
{Names: []string{format.IMAGE}, Formats: &imageFormat},
{Names: []string{format.MP3_FRAME}, Formats: &mp3FrameFormat},
{Names: []string{format.MPEG_ASC}, Formats: &mpegASCFrameFormat},
{Names: []string{format.MPEG_PES_PACKET}, Formats: &mpegPESPacketSampleFormat},
{Names: []string{format.MPEG_SPU}, Formats: &mpegSPUFrameFormat},
{Names: []string{format.OPUS_PACKET}, Formats: &opusPacketFrameFormat},
{Names: []string{format.VORBIS_PACKET}, Formats: &vorbisPacketFormat},
{Names: []string{format.VP8_FRAME}, Formats: &vp8FrameFormat},
{Names: []string{format.VP9_CFM}, Formats: &vp9CFMFormat},
{Names: []string{format.VP9_FRAME}, Formats: &vp9FrameFormat},
},
Files: matroskaFS,
})
codecToFormat = map[string]*[]*decode.Format{
"A_VORBIS": &vorbisPacketFormat,
"A_MPEG/L3": &mp3FrameFormat,
"A_FLAC": &flacFrameFormat,
"A_AAC": &aacFrameFormat,
"A_OPUS": &opusPacketFrameFormat,
"V_VP8": &vp8FrameFormat,
"V_VP9": &vp9FrameFormat,
"V_AV1": &av1FrameFormat,
"V_VOBSUB": &mpegSPUFrameFormat,
"V_MPEG4/ISO/AVC": &mpegAVCAUFormat,
"V_MPEGH/ISO/HEVC": &mpegHEVCSampleFormat,
"V_MPEG2": &mpegPESPacketSampleFormat,
"S_VOBSUB": &mpegSPUFrameFormat,
}
}
// TODO: smarter?
func decodeRawVintWidth(d *decode.D) (uint64, int) {
n := d.U8()
w := 1
for i := 0; i <= 7 && (n&(1<<(7-i))) == 0; i++ {
w++
}
for i := 1; i < w; i++ {
n = n<<8 | d.U8()
}
return n, w
}
func decodeRawVint(d *decode.D) uint64 {
n, _ := decodeRawVintWidth(d)
return n
}
func decodeVint(d *decode.D) uint64 {
n, w := decodeRawVintWidth(d)
m := (uint64(1<<((w-1)*8+(8-w))) - 1)
return n & m
}
func FieldFormatVint(d *decode.D, name string, displayFormat decode.DisplayFormat) uint64 {
return d.FieldUFn(name, func() (uint64, decode.DisplayFormat, string) {
return decodeVint(d), displayFormat, ""
})
}
type track struct {
parentD *decode.D
number int
codec string
codecPrivatePos int64
codecPrivateTagSize int64
formatInArg interface{}
}
type block struct {
d *decode.D
r ranges.Range
simple bool
}
type decodeContext struct {
currentTrack *track
tracks []*track
blocks []block
}
func decodeMaster(d *decode.D, bitsLimit int64, tag ebml.Tag, dc *decodeContext) {
tagEndBit := d.Pos() + bitsLimit
d.FieldArrayFn("elements", func(d *decode.D) {
// var crcD *decode.D
// var crcStart int64
for d.Pos() < tagEndBit && d.NotEnd() {
startPos := d.Pos()
tagID := decodeRawVint(d)
d.SeekAbs(startPos)
a, ok := tag[tagID]
if !ok {
a, ok = ebml.Global[tagID]
if !ok {
d.Invalid(fmt.Sprintf("unknown id %d", tagID))
}
}
d.FieldStructFn("element", func(d *decode.D) {
if tagID == ebml_matroska.TrackEntryID {
dc.currentTrack = &track{}
dc.tracks = append(dc.tracks, dc.currentTrack)
}
// TODO: add a.Definition as description?
// TODO: map?
d.FieldValueU("type", uint64(a.Type), ebml.TypeNames[a.Type])
d.FieldUFn("id", func() (uint64, decode.DisplayFormat, string) {
n := decodeRawVint(d)
return n, decode.NumberHex, a.Name
})
// tagSize could be 0xffffffffffffff which means "unknown" size, then we will read until eof
// TODO: should read until unknown id:
// The end of a Master-element with unknown size is determined by the beginning of the next
// element that is not a valid sub-element of that Master-element
// TODO: should also handle garbage between
tagSize := FieldFormatVint(d, "size", decode.NumberDecimal)
if tagSize > 8 &&
(a.Type == ebml.Integer ||
a.Type == ebml.Uinteger ||
a.Type == ebml.Float) {
d.Invalid(fmt.Sprintf("invalid tagSize %d for non-master type", tagSize))
}
switch a.Type {
case ebml.Integer:
d.FieldSFn("value", func() (int64, decode.DisplayFormat, string) {
n := d.S(int(tagSize) * 8)
if len(a.UintegerEnums) > 0 {
// TODO: use enum Definition as description
return n, decode.NumberDecimal, a.IntegerEnums[n].Label
}
return n, decode.NumberDecimal, ""
})
case ebml.Uinteger:
v := d.FieldUFn("value", func() (uint64, decode.DisplayFormat, string) {
n := d.U(int(tagSize) * 8)
if len(a.UintegerEnums) > 0 {
// TODO: use enum Definition as description
return n, decode.NumberDecimal, a.UintegerEnums[n].Label
}
return n, decode.NumberDecimal, ""
})
if dc.currentTrack != nil && tagID == ebml_matroska.TrackNumberID {
dc.currentTrack.number = int(v)
}
case ebml.Float:
d.FieldF("value", int(tagSize)*8)
case ebml.String:
v := d.FieldStrFn("value", func() (string, string) {
s := d.UTF8(int(tagSize))
if len(a.StringEnums) > 0 {
// TODO: use enum Definition as description
return s, a.StringEnums[s].Label
}
return s, ""
})
if dc.currentTrack != nil && tagID == ebml_matroska.CodecIDID {
dc.currentTrack.codec = v
}
case ebml.UTF8:
d.FieldUTF8Null("value", int(tagSize))
case ebml.Date:
// TODO:
/*
proc type_date {size label _extra} {
set s [clock scan {2001-01-01 00:00:00}]
set frac 0
switch $size {
0 {}
8 {
set nano [int64]
set s [clock add $s [expr $nano/1000000000] seconds]
set frac [expr ($nano%1000000000)/1000000000.0]
}
default {
bytes $size $label
return
}
}
entry $label "[clock format $s] ${frac}s" $size [expr [pos]-$size]
}
*/
d.FieldBitBufLen("value", int64(tagSize)*8)
case ebml.Binary:
switch tagID {
case ebml_matroska.SimpleBlockID:
dc.blocks = append(dc.blocks, block{
d: d,
r: ranges.Range{Start: d.Pos(), Len: int64(tagSize) * 8},
simple: true,
})
d.SeekRel(int64(tagSize) * 8)
case ebml_matroska.BlockID:
dc.blocks = append(dc.blocks, block{
d: d,
r: ranges.Range{Start: d.Pos(), Len: int64(tagSize) * 8},
})
d.SeekRel(int64(tagSize) * 8)
case ebml_matroska.CodecPrivateID:
if dc.currentTrack != nil {
dc.currentTrack.parentD = d
dc.currentTrack.codecPrivatePos = d.Pos()
dc.currentTrack.codecPrivateTagSize = int64(tagSize) * 8
}
d.SeekRel(int64(tagSize) * 8)
case ebml_matroska.FileDataID:
d.FieldFormatLen("value", int64(tagSize)*8, imageFormat, nil)
default:
d.FieldBitBufLen("value", int64(tagSize)*8)
// if tagID == CRC {
// crcD = d
// crcStart = d.Pos()
// }
}
case ebml.Master:
decodeMaster(d, int64(tagSize)*8, a.Tag, dc)
}
})
}
// if crcD != nil {
// crcValue := crcD.FieldMustRemove("value")
// elementCRC := &crc.CRC{Bits: 32, Current: 0xffff_ffff, Table: crc.IEEELETable}
// //log.Printf("crc: %x-%x %d\n", crcStart/8, d.Pos()/8, (d.Pos()-crcStart)/8)
// ioextra.MustCopy(elementCRC, d.BitBufRange(crcStart, d.Pos()-crcStart))
// crcD.FieldChecksumRange("value", crcValue.Range.Start, crcValue.Range.Len, elementCRC.Sum(nil), decode.LittleEndian)
// }
})
}
func matroskaDecode(d *decode.D, in interface{}) interface{} {
ebmlHeaderID := uint64(0x1a45dfa3)
if d.PeekBits(32) != ebmlHeaderID {
d.Invalid("no EBML header found")
}
dc := &decodeContext{tracks: []*track{}}
decodeMaster(d, d.BitsLeft(), ebml_matroska.Root, dc)
trackNumberToTrack := map[int]*track{}
for _, t := range dc.tracks {
trackNumberToTrack[t.number] = t
}
for _, t := range dc.tracks {
// no CodecPrivate found
if t.parentD == nil {
continue
}
// TODO: refactor, one DecodeRangeFn or refactor to add FieldFormatRangeFn?
switch t.codec {
case "A_VORBIS":
t.parentD.DecodeRangeFn(t.codecPrivatePos, t.codecPrivateTagSize, func(d *decode.D) {
numPackets := d.FieldU8("num_packets")
// TODO: lacing
packetLengths := []int64{}
// Xiph-style lacing (similar to ogg) of n-1 packets, last is reset of block
d.FieldArrayFn("laces", func(d *decode.D) {
for i := uint64(0); i < numPackets; i++ {
l := d.FieldUFn("lace", func() (uint64, decode.DisplayFormat, string) {
var l uint64
for {
n := d.U8()
l += n
if n < 255 {
return l, decode.NumberDecimal, ""
}
}
})
packetLengths = append(packetLengths, int64(l))
}
})
d.FieldArrayFn("packets", func(d *decode.D) {
for _, l := range packetLengths {
d.FieldFormatLen("packet", l*8, vorbisPacketFormat, nil)
}
d.FieldFormatLen("packet", d.BitsLeft(), vorbisPacketFormat, nil)
})
})
case "A_AAC":
t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, mpegASCFrameFormat, nil)
case "A_OPUS":
t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, opusPacketFrameFormat, nil)
case "A_FLAC":
t.parentD.DecodeRangeFn(t.codecPrivatePos, t.codecPrivateTagSize, func(d *decode.D) {
d.FieldStructFn("value", func(d *decode.D) {
d.FieldValidateUTF8("magic", "fLaC")
_, v := d.FieldFormat("metadatablocks", flacMetadatablocksFormat, nil)
flacMetadatablockOut, ok := v.(format.FlacMetadatablocksOut)
if !ok {
d.Invalid(fmt.Sprintf("expected FlacMetadatablockOut got %#+v", v))
}
if flacMetadatablockOut.HasStreamInfo {
t.formatInArg = format.FlacFrameIn{StreamInfo: flacMetadatablockOut.StreamInfo}
}
})
})
case "V_MPEG4/ISO/AVC":
_, v := t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, mpegAVCDCRFormat, nil)
avcDcrOut, ok := v.(format.AvcDcrOut)
if !ok {
d.Invalid(fmt.Sprintf("expected AvcDcrOut got %#+v", v))
}
t.formatInArg = format.AvcIn{LengthSize: avcDcrOut.LengthSize} //nolint:gosimple
case "V_MPEGH/ISO/HEVC":
_, v := t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, mpegHEVCDCRFormat, nil)
hevcDcrOut, ok := v.(format.HevcDcrOut)
if !ok {
d.Invalid(fmt.Sprintf("expected HevcDcrOut got %#+v", v))
}
t.formatInArg = format.HevcIn{LengthSize: hevcDcrOut.LengthSize} //nolint:gosimple
case "V_AV1":
t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, av1CCRFormat, nil)
case "V_VP9":
t.parentD.FieldFormatRange("value", t.codecPrivatePos, t.codecPrivateTagSize, vp9CFMFormat, nil)
default:
t.parentD.FieldBitBufRange("value", t.codecPrivatePos, t.codecPrivateTagSize)
}
}
for _, b := range dc.blocks {
b.d.DecodeRangeFn(b.r.Start, b.r.Len, func(d *decode.D) {
trackNumber := FieldFormatVint(d, "track_number", decode.NumberDecimal)
d.FieldU16("timestamp")
if b.simple {
d.FieldStructFn("flags", func(d *decode.D) {
d.FieldBool("key_frame")
d.FieldU3("reserved")
d.FieldBool("invisible")
d.FieldU2("lacing")
d.FieldBool("discardable")
})
} else {
d.FieldStructFn("flags", func(d *decode.D) {
d.FieldU4("reserved")
d.FieldBool("invisible")
d.FieldU2("lacing")
d.FieldBool("not_used")
})
}
// TODO: lacing etc
// TODO: fixed/unknown?
if t, ok := trackNumberToTrack[int(trackNumber)]; ok {
if f, ok := codecToFormat[t.codec]; ok {
d.FieldFormat("packet", *f, t.formatInArg)
}
}
if d.BitsLeft() > 0 {
d.FieldBitBufLen("data", d.BitsLeft())
}
})
}
return nil
}