mirror of
https://github.com/wader/fq.git
synced 2024-11-25 05:55:43 +03:00
658 lines
17 KiB
Go
658 lines
17 KiB
Go
package riff
|
|
|
|
// TODO:
|
|
// mp3 mappig, samples can span over sample ranges?
|
|
// hevc mapping?
|
|
// DV handler https://learn.microsoft.com/en-us/windows/win32/directshow/dv-data-in-the-avi-file-format
|
|
// palette change
|
|
// rec groups
|
|
// nested indexes
|
|
// unknown fields for unreachable chunk header for > 1gb samples
|
|
// 2fields, field index?
|
|
|
|
// https://learn.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference
|
|
// http://www.jmcgowan.com/odmlff2.pdf
|
|
// https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/avidec.c
|
|
// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/um/aviriff.h
|
|
|
|
import (
|
|
"embed"
|
|
"strconv"
|
|
|
|
"github.com/wader/fq/format"
|
|
"github.com/wader/fq/pkg/decode"
|
|
"github.com/wader/fq/pkg/interp"
|
|
"github.com/wader/fq/pkg/ranges"
|
|
"github.com/wader/fq/pkg/scalar"
|
|
)
|
|
|
|
//go:embed avi.md
|
|
var aviFS embed.FS
|
|
|
|
var aviMp3FrameGroup decode.Group
|
|
var aviMpegAVCAUGroup decode.Group
|
|
var aviMpegHEVCAUGroup decode.Group
|
|
var aviFLACFrameGroup decode.Group
|
|
|
|
func init() {
|
|
interp.RegisterFormat(
|
|
format.AVI,
|
|
&decode.Format{
|
|
Description: "Audio Video Interleaved",
|
|
DecodeFn: aviDecode,
|
|
DefaultInArg: format.AVI_In{
|
|
DecodeSamples: true,
|
|
DecodeExtendedChunks: true,
|
|
},
|
|
Dependencies: []decode.Dependency{
|
|
{Groups: []*decode.Group{format.AVC_AU}, Out: &aviMpegAVCAUGroup},
|
|
{Groups: []*decode.Group{format.HEVC_AU}, Out: &aviMpegHEVCAUGroup},
|
|
{Groups: []*decode.Group{format.MP3_Frame}, Out: &aviMp3FrameGroup},
|
|
{Groups: []*decode.Group{format.FLAC_Frame}, Out: &aviFLACFrameGroup},
|
|
},
|
|
Groups: []*decode.Group{format.Probe},
|
|
})
|
|
interp.RegisterFS(aviFS)
|
|
}
|
|
|
|
var aviListTypeDescriptions = scalar.StrMapDescription{
|
|
"hdrl": "AVI main list",
|
|
"strl": "Stream list",
|
|
"movi": "Stream Data",
|
|
"rec": "Chunk group",
|
|
}
|
|
|
|
const (
|
|
aviStrhTypeAudio = "auds"
|
|
aviStrhTypeMidi = "mids"
|
|
aviStrhTypeVideo = "vids"
|
|
aviStrhTypeText = "txts"
|
|
)
|
|
|
|
var aviStrhTypeDescriptions = scalar.StrMapDescription{
|
|
aviStrhTypeAudio: "Audio stream",
|
|
aviStrhTypeMidi: "MIDI stream",
|
|
aviStrhTypeText: "Text stream",
|
|
aviStrhTypeVideo: "Video stream",
|
|
}
|
|
|
|
const (
|
|
aviIndexTypeIndexes = 0
|
|
aviIndexTypeChunks = 1
|
|
)
|
|
|
|
var aviIndexTypeNames = scalar.UintMapSymStr{
|
|
aviIndexTypeIndexes: "indexes",
|
|
aviIndexTypeChunks: "chunks",
|
|
}
|
|
|
|
const (
|
|
aviIndexSubType2Fields = 1
|
|
)
|
|
|
|
var aviIndexSubTypeNames = scalar.UintMapSymStr{
|
|
aviIndexSubType2Fields: "2fields",
|
|
}
|
|
|
|
const (
|
|
aviStreamChunkTypeUncompressedVideo = "db"
|
|
aviStreamChunkTypeCompressedVideo = "dc"
|
|
aviStreamChunkTypePaletteChange = "pc"
|
|
aviStreamChunkTypeAudio = "wb"
|
|
aviStreamChunkTypeIndex = "ix"
|
|
)
|
|
|
|
var aviStreamChunkTypeDescriptions = scalar.StrMapDescription{
|
|
aviStreamChunkTypeUncompressedVideo: "Uncompressed video frame",
|
|
aviStreamChunkTypeCompressedVideo: "Compressed video frame",
|
|
aviStreamChunkTypePaletteChange: "Palette change",
|
|
aviStreamChunkTypeAudio: "Audio data",
|
|
aviStreamChunkTypeIndex: "Index",
|
|
}
|
|
|
|
type idx1Sample struct {
|
|
offset int64
|
|
size int64
|
|
streamNr int
|
|
streamType string
|
|
}
|
|
|
|
type aviStream struct {
|
|
typ string
|
|
handler string
|
|
formatTag uint64
|
|
compression string
|
|
hasFormat bool
|
|
format *decode.Group
|
|
formatInArg any
|
|
sampleSize uint64
|
|
indexes []ranges.Range
|
|
ixSamples []ranges.Range
|
|
}
|
|
|
|
func aviParseChunkID(id string) (string, int, bool) {
|
|
if len(id) != 4 {
|
|
return "", 0, false
|
|
}
|
|
|
|
isDigits := func(s string) bool {
|
|
for _, c := range s {
|
|
if !(c >= '0' && c <= '9') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
var typ string
|
|
var indexStr string
|
|
switch {
|
|
case isDigits(id[0:2]):
|
|
// ##dc media etc
|
|
indexStr, typ = id[0:2], id[2:4]
|
|
case isDigits(id[2:4]):
|
|
// ix## index etc
|
|
typ, indexStr = id[0:2], id[2:4]
|
|
default:
|
|
return "", 0, false
|
|
}
|
|
|
|
index, err := strconv.Atoi(indexStr)
|
|
if err != nil {
|
|
panic("unreachable")
|
|
}
|
|
|
|
return typ, index, true
|
|
|
|
}
|
|
|
|
func aviIsStreamType(typ string) bool {
|
|
switch typ {
|
|
case aviStreamChunkTypeUncompressedVideo,
|
|
aviStreamChunkTypeCompressedVideo,
|
|
aviStreamChunkTypeAudio:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func aviDecorateStreamID(d *decode.D, id string) (string, int) {
|
|
typ, index, ok := aviParseChunkID(id)
|
|
if ok && aviIsStreamType(typ) {
|
|
d.FieldValueStr("stream_type", typ, aviStreamChunkTypeDescriptions)
|
|
d.FieldValueUint("stream_nr", uint64(index))
|
|
return typ, index
|
|
}
|
|
return "", 0
|
|
}
|
|
|
|
// ix frame index and indx frame index
|
|
func aviDecodeChunkIndex(d *decode.D) []ranges.Range {
|
|
var rs []ranges.Range
|
|
|
|
d.FieldU16("longs_per_entry") // TODO: use?
|
|
d.FieldU8("index_subtype", aviIndexSubTypeNames)
|
|
d.FieldU8("index_type", aviIndexTypeNames)
|
|
nEntriesInUse := d.FieldU32("entries_in_use")
|
|
chunkID := d.FieldUTF8("chunk_id", 4)
|
|
aviDecorateStreamID(d, chunkID)
|
|
baseOffset := int64(d.FieldU64("base_offset"))
|
|
d.FieldU32("unused")
|
|
d.FieldArray("index", func(d *decode.D) {
|
|
for i := 0; i < int(nEntriesInUse); i++ {
|
|
d.FieldStruct("index", func(d *decode.D) {
|
|
offset := int64(d.FieldU32("offset"))
|
|
sizeKeyFrame := d.FieldU32("size_keyframe")
|
|
size := sizeKeyFrame & 0x7f_ff_ff_ff
|
|
d.FieldValueUint("size", size)
|
|
d.FieldValueBool("key_frame", sizeKeyFrame&0x80_00_00_00 == 0)
|
|
rs = append(rs, ranges.Range{
|
|
Start: baseOffset*8 + offset*8,
|
|
Len: int64(size) * 8,
|
|
})
|
|
})
|
|
}
|
|
})
|
|
|
|
return rs
|
|
}
|
|
|
|
func aviDecodeEx(d *decode.D, ai format.AVI_In, extendedChunk bool) {
|
|
var streams []*aviStream
|
|
var idx1Samples []idx1Sample
|
|
var moviListPos int64 // point to first bit after type
|
|
|
|
requiredRiffType := "AVI "
|
|
if extendedChunk {
|
|
requiredRiffType = "AVIX"
|
|
}
|
|
var foundRiffType string
|
|
|
|
riffDecode(
|
|
d,
|
|
nil,
|
|
func(d *decode.D, path path) (string, int64) {
|
|
id := d.FieldUTF8("id", 4, scalar.ActualTrimSpace, chunkIDDescriptions)
|
|
aviDecorateStreamID(d, id)
|
|
size := d.FieldU32("size")
|
|
return id, int64(size)
|
|
},
|
|
func(d *decode.D, id string, path path) (bool, any) {
|
|
switch id {
|
|
case "RIFF":
|
|
foundRiffType = d.FieldUTF8("type", 4, d.StrAssert(requiredRiffType))
|
|
return true, nil
|
|
|
|
case "LIST":
|
|
typ := d.FieldUTF8("type", 4, scalar.ActualTrimSpace, aviListTypeDescriptions)
|
|
switch typ {
|
|
case "strl":
|
|
return true, &aviStream{}
|
|
case "movi":
|
|
moviListPos = d.Pos()
|
|
}
|
|
return true, nil
|
|
|
|
case "idx1":
|
|
d.FieldArray("indexes", func(d *decode.D) {
|
|
// TODO: seems there are files with weird tailing extra index entries
|
|
// TODO: limit using total_frame somehow instead?
|
|
for d.BitsLeft() >= 4*32 {
|
|
d.FieldStruct("index", func(d *decode.D) {
|
|
id := d.FieldUTF8("id", 4)
|
|
typ, index := aviDecorateStreamID(d, id)
|
|
d.FieldStruct("flags", func(d *decode.D) {
|
|
d.FieldRawLen("unused0", 3)
|
|
d.FieldBool("key_frame")
|
|
d.FieldRawLen("unused1", 3)
|
|
d.FieldBool("list")
|
|
d.FieldRawLen("unused2", 24)
|
|
})
|
|
offset := int64(d.FieldU32("offset"))
|
|
length := int64(d.FieldU32("length"))
|
|
|
|
idx1Samples = append(idx1Samples, idx1Sample{
|
|
offset: offset * 8,
|
|
size: length * 8,
|
|
streamNr: index,
|
|
streamType: typ,
|
|
})
|
|
})
|
|
}
|
|
})
|
|
return false, nil
|
|
|
|
case "avih":
|
|
d.FieldU32("micro_sec_per_frame")
|
|
d.FieldU32("max_bytes_per_sec")
|
|
d.FieldU32("padding_granularity")
|
|
d.FieldStruct("flags", func(d *decode.D) {
|
|
d.FieldRawLen("unused0", 2)
|
|
d.FieldBool("must_use_index")
|
|
d.FieldBool("has_index") // Index at end of file?
|
|
d.FieldRawLen("unused1", 8)
|
|
d.FieldBool("trust_ck_type") // Use CKType to find key frames
|
|
d.FieldRawLen("unused2", 2)
|
|
d.FieldBool("is_interleaved")
|
|
d.FieldRawLen("unused3", 6)
|
|
d.FieldBool("copyrighted")
|
|
d.FieldBool("was_capture_file")
|
|
d.FieldRawLen("unused4", 8)
|
|
})
|
|
d.FieldU32("total_frames")
|
|
d.FieldU32("initial_frames")
|
|
d.FieldU32("streams")
|
|
d.FieldU32("suggested_buffer_size")
|
|
d.FieldU32("width")
|
|
d.FieldU32("height")
|
|
d.FieldRawLen("reserved", 32*4)
|
|
return false, nil
|
|
|
|
case "dmlh":
|
|
d.FieldU32("total_frames")
|
|
d.FieldRawLen("future", 32*61)
|
|
return false, nil
|
|
|
|
case "strh":
|
|
typ := d.FieldUTF8("type", 4, aviStrhTypeDescriptions)
|
|
handler := d.FieldUTF8("handler", 4)
|
|
d.FieldStruct("flags", func(d *decode.D) {
|
|
d.FieldRawLen("unused0", 7)
|
|
d.FieldBool("disabled")
|
|
d.FieldRawLen("unused1", 15)
|
|
d.FieldBool("pal_changes")
|
|
d.FieldRawLen("unused2", 8)
|
|
})
|
|
d.FieldU16("priority")
|
|
d.FieldU16("language")
|
|
d.FieldU32("initial_frames")
|
|
d.FieldU32("scale")
|
|
d.FieldU32("rate")
|
|
d.FieldU32("start")
|
|
d.FieldU32("length")
|
|
d.FieldU32("suggested_buffer_size")
|
|
d.FieldU32("quality")
|
|
sampleSize := d.FieldU32("sample_size")
|
|
d.FieldStruct("frame", func(d *decode.D) {
|
|
d.FieldU16("left")
|
|
d.FieldU16("top")
|
|
d.FieldU16("right")
|
|
d.FieldU16("bottom")
|
|
})
|
|
|
|
if stream, ok := path.topData().(*aviStream); ok {
|
|
stream.typ = typ
|
|
stream.handler = handler
|
|
stream.sampleSize = sampleSize
|
|
}
|
|
|
|
return false, nil
|
|
|
|
case "strf":
|
|
stream, streamOk := path.topData().(*aviStream)
|
|
if !streamOk {
|
|
stream = &aviStream{}
|
|
}
|
|
typ := stream.typ
|
|
|
|
switch typ {
|
|
case aviStrhTypeVideo:
|
|
// BITMAPINFOHEADER
|
|
d.BitsLeft()
|
|
d.FieldU32("bi_size")
|
|
d.FieldU32("width")
|
|
d.FieldU32("height")
|
|
d.FieldU16("planes")
|
|
d.FieldU16("bit_count")
|
|
compression := d.FieldUTF8("compression", 4)
|
|
d.FieldU32("size_image")
|
|
d.FieldU32("x_pels_per_meter")
|
|
d.FieldU32("y_pels_per_meter")
|
|
d.FieldU32("clr_used")
|
|
d.FieldU32("clr_important")
|
|
if d.BitsLeft() > 0 {
|
|
d.FieldRawLen("extra", d.BitsLeft())
|
|
}
|
|
|
|
stream.compression = compression
|
|
|
|
// TODO: if dvsd handler and extraSize >= 32 then DVINFO?
|
|
|
|
switch compression {
|
|
case format.BMPTagH264,
|
|
format.BMPTagH264_h264,
|
|
format.BMPTagH264_X264,
|
|
format.BMPTagH264_x264,
|
|
format.BMPTagH264_avc1,
|
|
format.BMPTagH264_DAVC,
|
|
format.BMPTagH264_SMV2,
|
|
format.BMPTagH264_VSSH,
|
|
format.BMPTagH264_Q264,
|
|
format.BMPTagH264_V264,
|
|
format.BMPTagH264_GAVC,
|
|
format.BMPTagH264_UMSV,
|
|
format.BMPTagH264_tshd,
|
|
format.BMPTagH264_INMC:
|
|
stream.format = &aviMpegAVCAUGroup
|
|
stream.hasFormat = true
|
|
case format.BMPTagHEVC,
|
|
format.BMPTagHEVC_H265:
|
|
stream.format = &aviMpegHEVCAUGroup
|
|
stream.hasFormat = true
|
|
}
|
|
|
|
case aviStrhTypeAudio:
|
|
// WAVEFORMATEX
|
|
formatTag := d.FieldU16("format_tag", format.WAVTagNames)
|
|
d.FieldU16("channels")
|
|
d.FieldU32("samples_per_sec")
|
|
d.FieldU32("avg_bytes_per_sec")
|
|
d.FieldU16("block_align")
|
|
d.FieldU16("bits_per_sample")
|
|
// TODO: seems to be optional
|
|
if d.BitsLeft() >= 16 {
|
|
cbSize := d.FieldU16("cb_size")
|
|
d.FieldRawLen("extra", int64(cbSize)*8)
|
|
}
|
|
|
|
stream.formatTag = formatTag
|
|
|
|
switch formatTag {
|
|
case format.WAVTagMP3:
|
|
stream.format = &aviMp3FrameGroup
|
|
stream.hasFormat = true
|
|
case format.WAVTagFLAC:
|
|
// TODO: can flac in avi have streaminfo somehow?
|
|
stream.format = &aviFLACFrameGroup
|
|
stream.hasFormat = true
|
|
}
|
|
case "iavs":
|
|
// DVINFO
|
|
d.FieldU32("dva_aux_src")
|
|
d.FieldU32("dva_aux_ctl")
|
|
d.FieldU32("dva_aux_src1")
|
|
d.FieldU32("dva_aux_ctl1")
|
|
d.FieldU32("dvv_aux_src")
|
|
d.FieldU32("dvv_aux_ctl")
|
|
d.FieldRawLen("dvv_reserved", 32*2)
|
|
}
|
|
|
|
streams = append(streams, stream)
|
|
|
|
return false, nil
|
|
|
|
case "indx":
|
|
stream, _ := path.topData().(*aviStream)
|
|
|
|
d.FieldU16("longs_per_entry") // TODO: use?
|
|
d.FieldU8("index_subtype")
|
|
d.FieldU8("index_type")
|
|
nEntriesInUse := d.FieldU32("entries_in_use")
|
|
chunkID := d.FieldUTF8("chunk_id", 4)
|
|
aviDecorateStreamID(d, chunkID)
|
|
d.FieldU64("base")
|
|
d.FieldU32("unused0")
|
|
d.FieldArray("index", func(d *decode.D) {
|
|
for i := 0; i < int(nEntriesInUse); i++ {
|
|
d.FieldStruct("index", func(d *decode.D) {
|
|
offset := int64(d.FieldU64("offset"))
|
|
size := int64(d.FieldU32("size"))
|
|
d.FieldU32("duration")
|
|
|
|
if stream != nil {
|
|
stream.indexes = append(stream.indexes, ranges.Range{
|
|
Start: offset * 8,
|
|
Len: size * 8,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
if d.BitsLeft() > 0 {
|
|
d.FieldRawLen("unused1", d.BitsLeft())
|
|
}
|
|
|
|
return false, nil
|
|
|
|
case "vprp":
|
|
d.FieldU32("video_format_token")
|
|
d.FieldU32("video_standard")
|
|
d.FieldU32("vertical_refresh_rate")
|
|
d.FieldU32("h_total_in_t")
|
|
d.FieldU32("v_total_in_lines")
|
|
d.FieldStruct("frame_aspect_ratio", func(d *decode.D) {
|
|
d.FieldU16("x")
|
|
d.FieldU16("y")
|
|
})
|
|
d.FieldU32("frame_width_in_pixels")
|
|
d.FieldU32("frame_height_in_lines")
|
|
nbFieldPerFrame := d.FieldU32("nb_field_per_frame")
|
|
d.FieldArray("field_info", func(d *decode.D) {
|
|
for i := 0; i < int(nbFieldPerFrame); i++ {
|
|
d.FieldStruct("field_info", func(d *decode.D) {
|
|
d.FieldU32("compressed_bm_height")
|
|
d.FieldU32("compressed_bm_width")
|
|
d.FieldU32("valid_bm_height")
|
|
d.FieldU32("valid_bm_width")
|
|
d.FieldU32("valid_bmx_offset")
|
|
d.FieldU32("valid_bmy_offset")
|
|
d.FieldU32("video_x_offset_in_t")
|
|
d.FieldU32("video_y_valid_start_line")
|
|
})
|
|
}
|
|
})
|
|
return false, nil
|
|
|
|
default:
|
|
if riffIsStringChunkID(id) {
|
|
d.FieldUTF8NullFixedLen("value", int(d.BitsLeft())/8)
|
|
return false, nil
|
|
}
|
|
|
|
typ, index, _ := aviParseChunkID(id)
|
|
switch {
|
|
case typ == "ix":
|
|
sampleRanges := aviDecodeChunkIndex(d)
|
|
if index < len(streams) {
|
|
s := streams[index]
|
|
s.ixSamples = append(s.ixSamples, sampleRanges...)
|
|
}
|
|
case d.BitsLeft() > 0 &&
|
|
ai.DecodeSamples &&
|
|
aviIsStreamType(typ) &&
|
|
index < len(streams) &&
|
|
streams[index].hasFormat:
|
|
s := streams[index]
|
|
d.FieldFormatLen("data", d.BitsLeft(), s.format, s.formatInArg)
|
|
default:
|
|
d.FieldRawLen("data", d.BitsLeft())
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
},
|
|
)
|
|
|
|
if foundRiffType != requiredRiffType {
|
|
d.Errorf("wrong or no AVI riff type found (%s)", requiredRiffType)
|
|
}
|
|
|
|
if !extendedChunk {
|
|
d.FieldArray("streams", func(d *decode.D) {
|
|
for streamIndex, stream := range streams {
|
|
|
|
d.FieldStruct("stream", func(d *decode.D) {
|
|
d.FieldValueStr("type", stream.typ)
|
|
d.FieldValueStr("handler", stream.handler)
|
|
switch stream.typ {
|
|
case aviStrhTypeAudio:
|
|
d.FieldValueUint("format_tag", stream.formatTag, format.WAVTagNames)
|
|
case aviStrhTypeVideo:
|
|
d.FieldValueStr("compression", stream.compression)
|
|
}
|
|
|
|
var streamIndexSampleRanges []ranges.Range
|
|
if len(stream.indexes) > 0 {
|
|
d.FieldArray("indexes", func(d *decode.D) {
|
|
for _, i := range stream.indexes {
|
|
d.FieldStruct("index", func(d *decode.D) {
|
|
d.RangeFn(i.Start, i.Len, func(d *decode.D) {
|
|
d.FieldUTF8("type", 4)
|
|
d.FieldU32("cb")
|
|
sampleRanges := aviDecodeChunkIndex(d)
|
|
streamIndexSampleRanges = append(streamIndexSampleRanges, sampleRanges...)
|
|
})
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// TODO: palette change
|
|
decodeSample := func(d *decode.D, sr ranges.Range) {
|
|
d.RangeFn(sr.Start, sr.Len, func(d *decode.D) {
|
|
if sr.Len == 0 {
|
|
d.FieldRawLen("sample", d.BitsLeft())
|
|
return
|
|
}
|
|
|
|
subSampleSize := int64(stream.sampleSize) * 8
|
|
// TODO: <= no format and <= 8*8 heuristics to not create separate pcm samples
|
|
if subSampleSize == 0 || (!stream.hasFormat && subSampleSize <= 8*8) {
|
|
subSampleSize = sr.Len
|
|
}
|
|
|
|
for d.BitsLeft() > 0 {
|
|
d.FramedFn(subSampleSize, func(d *decode.D) {
|
|
if ai.DecodeSamples && stream.hasFormat {
|
|
d.FieldFormat("sample", stream.format, stream.formatInArg)
|
|
} else {
|
|
d.FieldRawLen("sample", d.BitsLeft())
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// try only add indexed samples once with priority:
|
|
// stream index
|
|
// ix chunks (might be same as stream index)
|
|
// idx1 chunks
|
|
if len(streamIndexSampleRanges) > 0 {
|
|
d.FieldArray("samples", func(d *decode.D) {
|
|
for _, sr := range streamIndexSampleRanges {
|
|
decodeSample(d, sr)
|
|
}
|
|
})
|
|
} else if len(stream.ixSamples) > 0 {
|
|
d.FieldArray("samples", func(d *decode.D) {
|
|
for _, sr := range stream.ixSamples {
|
|
decodeSample(d, sr)
|
|
}
|
|
})
|
|
} else if len(idx1Samples) > 0 {
|
|
d.FieldArray("samples", func(d *decode.D) {
|
|
for _, is := range idx1Samples {
|
|
if is.streamNr != streamIndex {
|
|
continue
|
|
}
|
|
decodeSample(d, ranges.Range{
|
|
Start: moviListPos + is.offset + 32, // +32 skip size field
|
|
Len: is.size,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func aviDecode(d *decode.D) any {
|
|
var ai format.AVI_In
|
|
d.ArgAs(&ai)
|
|
|
|
d.Endian = decode.LittleEndian
|
|
|
|
aviDecodeEx(d, ai, false)
|
|
|
|
if ai.DecodeExtendedChunks {
|
|
d.FieldArray("extended_chunks", func(d *decode.D) {
|
|
for {
|
|
// TODO: other way? spec says check hdrx chunk but there seems to be none?
|
|
riff, _ := d.TryPeekBytes(4)
|
|
if string(riff) != "RIFF" {
|
|
break
|
|
}
|
|
|
|
d.FieldStruct("chunk", func(d *decode.D) {
|
|
aviDecodeEx(d, ai, true)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}
|