2021-08-20 16:10:16 +03:00
|
|
|
//go:build ignore
|
2020-06-08 03:29:51 +03:00
|
|
|
|
|
|
|
package flv
|
|
|
|
|
|
|
|
// TODO: make it useful
|
2022-04-04 15:36:50 +03:00
|
|
|
// https://rtmp.veriskope.com/pdf/video_file_format_spec_v10.pdf
|
2020-06-08 03:29:51 +03:00
|
|
|
|
|
|
|
import (
|
2021-08-17 13:06:32 +03:00
|
|
|
"github.com/wader/fq/format"
|
|
|
|
"github.com/wader/fq/format/registry"
|
|
|
|
"github.com/wader/fq/pkg/decode"
|
2021-12-02 00:48:25 +03:00
|
|
|
"github.com/wader/fq/pkg/scalar"
|
2020-06-08 03:29:51 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2021-11-17 18:46:10 +03:00
|
|
|
registry.MustRegister(decode.Format{
|
2020-06-08 03:29:51 +03:00
|
|
|
Name: format.FLV,
|
|
|
|
Description: "Flash video",
|
|
|
|
Groups: []string{format.PROBE},
|
|
|
|
DecodeFn: flvDecode,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
audioData = 8
|
|
|
|
videoData = 9
|
|
|
|
scriptDataObject = 18
|
|
|
|
)
|
|
|
|
|
2021-12-02 00:48:25 +03:00
|
|
|
var tagTypeNames = scalar.UToSymStr{
|
2020-06-08 03:29:51 +03:00
|
|
|
audioData: "audioData",
|
|
|
|
videoData: "videoData",
|
|
|
|
scriptDataObject: "scriptDataObject",
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
typeNumber = 0
|
|
|
|
typeBoolean = 1
|
|
|
|
typeString = 2
|
|
|
|
typeObject = 3
|
|
|
|
typeMovieClip = 4
|
|
|
|
typeNull = 5
|
|
|
|
typeUndefined = 6
|
|
|
|
typeReference = 7
|
|
|
|
typeECMAArray = 8
|
|
|
|
typeObjectEnd = 9
|
|
|
|
typeStrictArray = 10
|
|
|
|
typeDate = 11
|
|
|
|
typeLongString = 12
|
|
|
|
)
|
|
|
|
|
2021-12-02 00:48:25 +03:00
|
|
|
var typeNames = scalar.UToSymStr{
|
2020-06-08 03:29:51 +03:00
|
|
|
typeNumber: "Number",
|
|
|
|
typeBoolean: "Boolean",
|
|
|
|
typeString: "String",
|
|
|
|
typeObject: "Object",
|
|
|
|
typeMovieClip: "MovieClip",
|
|
|
|
typeNull: "Null",
|
|
|
|
typeUndefined: "Undefined",
|
|
|
|
typeReference: "Reference",
|
|
|
|
typeECMAArray: "ECMAArray",
|
|
|
|
typeObjectEnd: "ObjectEnd",
|
|
|
|
typeStrictArray: "StrictArray",
|
|
|
|
typeDate: "Date",
|
|
|
|
typeLongString: "LongString",
|
|
|
|
}
|
|
|
|
|
2022-05-20 16:10:41 +03:00
|
|
|
func flvDecode(d *decode.D, in any) any {
|
2020-06-08 03:29:51 +03:00
|
|
|
var fieldScriptDataObject func()
|
|
|
|
var fieldScriptDataVariable func(d *decode.D, name string)
|
|
|
|
|
|
|
|
fieldScriptDataString := func(d *decode.D, name string) {
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldStrFn(name, func(d *decode.D) string {
|
2020-06-08 03:29:51 +03:00
|
|
|
l := d.U16()
|
2021-11-05 17:04:26 +03:00
|
|
|
return d.UTF8(int(l))
|
2020-06-08 03:29:51 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
fieldScriptDataStringLong := func(d *decode.D, name string) {
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldStrFn(name, func(d *decode.D) string {
|
2020-06-08 03:29:51 +03:00
|
|
|
l := d.U32()
|
2021-11-05 17:04:26 +03:00
|
|
|
return d.UTF8(int(l))
|
2020-06-08 03:29:51 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldScriptDataVariable = func(d *decode.D, name string) {
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldStruct(name, func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
fieldScriptDataString(d, "name")
|
|
|
|
fieldScriptDataString(d, "data")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldScriptDataValue := func(d *decode.D, _ string) uint64 {
|
2021-12-02 00:48:25 +03:00
|
|
|
typ := d.FieldU8("type", typeNames)
|
2020-06-08 03:29:51 +03:00
|
|
|
if typ == typeECMAArray {
|
|
|
|
d.FieldU32("ecma_array_length")
|
|
|
|
}
|
|
|
|
|
|
|
|
switch typ {
|
|
|
|
case typeNumber:
|
|
|
|
d.FieldF64("number")
|
|
|
|
case typeBoolean:
|
|
|
|
d.FieldU8("boolean")
|
|
|
|
case typeString:
|
|
|
|
fieldScriptDataString(d, "string")
|
|
|
|
case typeObject:
|
|
|
|
fieldScriptDataObject()
|
|
|
|
case typeMovieClip:
|
|
|
|
fieldScriptDataString(d, "path")
|
|
|
|
case typeNull:
|
|
|
|
case typeUndefined:
|
|
|
|
case typeReference:
|
|
|
|
d.FieldU16("reference")
|
|
|
|
case typeECMAArray:
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldArray("array", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
for {
|
|
|
|
if d.PeekBits(24) == typeObjectEnd { // variableEnd?
|
|
|
|
d.FieldU24("end")
|
|
|
|
break
|
|
|
|
}
|
|
|
|
fieldScriptDataVariable(d, "sasdadas")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
case typeStrictArray:
|
|
|
|
length := d.FieldU32("length")
|
|
|
|
for i := uint64(0); i < length; i++ {
|
|
|
|
fieldScriptDataVariable(d, "sasdadas")
|
|
|
|
}
|
|
|
|
case typeDate:
|
|
|
|
d.FieldF64("date_time")
|
|
|
|
d.FieldS16("local_data_time_offset")
|
|
|
|
|
|
|
|
case typeLongString:
|
|
|
|
fieldScriptDataStringLong(d, "asdsad")
|
|
|
|
|
|
|
|
case typeObjectEnd: // variableEnd also?
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return typ
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldScriptDataObject = func() {
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldStruct("object", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
fieldScriptDataString(d, "name")
|
|
|
|
fieldScriptDataValue(d, "data")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldUTF8("signature", 3, d.AssertStr("FLV"))
|
2020-06-08 03:29:51 +03:00
|
|
|
d.FieldU8("version")
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldU5("type_flags_reserved", d.AssertU(0))
|
2020-06-08 03:29:51 +03:00
|
|
|
d.FieldU1("type_flags_audio")
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldU1("type_flags_reserved", d.AssertU(0))
|
2020-06-08 03:29:51 +03:00
|
|
|
d.FieldU1("type_flags_video")
|
|
|
|
dataOffset := d.FieldU32("data_offset")
|
|
|
|
|
|
|
|
d.SeekAbs(int64(dataOffset) * 8)
|
|
|
|
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldArray("tags", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
for !d.End() {
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldStruct("tag", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
d.FieldU32("previous_tag_size")
|
2021-12-02 00:48:25 +03:00
|
|
|
tagType := d.FieldU8("tag_type", tagTypeNames)
|
2020-06-08 03:29:51 +03:00
|
|
|
dataSize := d.FieldU24("data_size")
|
|
|
|
d.FieldU24("timestamp")
|
|
|
|
d.FieldU8("timestamp_extended")
|
|
|
|
d.FieldU24("stream_id")
|
|
|
|
|
|
|
|
switch tagType {
|
|
|
|
case audioData, videoData:
|
|
|
|
d.SeekRel(int64(dataSize) * 8)
|
|
|
|
case scriptDataObject:
|
|
|
|
for {
|
|
|
|
if d.PeekBits(24) == typeObjectEnd {
|
|
|
|
d.FieldU24("end")
|
|
|
|
break
|
|
|
|
}
|
|
|
|
fieldScriptDataObject()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|