diff --git a/README.md b/README.md
index 2957a56ff..4277eec52 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,6 @@ It was originally designed to query, inspect and debug media codecs and containe
[aac_frame](doc/formats.md#aac_frame),
adts,
adts_frame,
-[adm](doc/formats.md#adm),
aiff,
amf0,
apev2,
@@ -54,7 +53,6 @@ avc_sei,
avc_sps,
[avi](doc/formats.md#avi),
[avro_ocf](doc/formats.md#avro_ocf),
-[axml](doc/formats.md#adm),
[bencode](doc/formats.md#bencode),
bitcoin_blkdat,
[bitcoin_block](doc/formats.md#bitcoin_block),
@@ -68,11 +66,10 @@ bsd_loopback_frame,
bzip2,
[caff](doc/formats.md#caff),
[cbor](doc/formats.md#cbor),
-[chna](doc/formats.md#adm),
[csv](doc/formats.md#csv),
-[dolby_metadata](doc/formats.md#dolby_metadata),
dns,
dns_tcp,
+[dolby_metadata](doc/formats.md#dolby_metadata),
elf,
ether8023_frame,
exif,
diff --git a/doc/formats.md b/doc/formats.md
index ab6e3c03e..34587fab7 100644
--- a/doc/formats.md
+++ b/doc/formats.md
@@ -7,7 +7,6 @@
|[`aac_frame`](#aac_frame) |Advanced Audio Coding frame ||
|`adts` |Audio Data Transport Stream |`adts_frame`|
|`adts_frame` |Audio Data Transport Stream frame |`aac_frame`|
-|[`adm`](#adm) |Audio Definition Model |`riff`|
|`aiff` |Audio Interchange File Format ||
|`amf0` |Action Message Format 0 ||
|`apev2` |APEv2 metadata tag |`image`|
@@ -26,7 +25,6 @@
|`avc_sps` |H.264/AVC Sequence Parameter Set ||
|[`avi`](#avi) |Audio Video Interleaved |`avc_au` `hevc_au` `mp3_frame` `flac_frame`|
|[`avro_ocf`](#avro_ocf) |Avro object container file ||
-|[`axml`](#adm) |Audio Definition Model Chunk |`riff`|
|[`bencode`](#bencode) |BitTorrent bencoding ||
|`bitcoin_blkdat` |Bitcoin blk.dat |`bitcoin_block`|
|[`bitcoin_block`](#bitcoin_block) |Bitcoin block |`bitcoin_transaction`|
@@ -40,11 +38,10 @@
|`bzip2` |bzip2 compression |`probe`|
|[`caff`](#caff) |Live2D Cubism archive |`probe`|
|[`cbor`](#cbor) |Concise Binary Object Representation ||
-|[`chna`](#adm) |Audio Definition Model Chunk |`riff`|
|[`csv`](#csv) |Comma separated values ||
-|[`dolby_metadata`](#dolby_metadata) |Dolby Metadata (Atmos, AC3, Digital Plus) |`riff`|
|`dns` |DNS packet ||
|`dns_tcp` |DNS packet (TCP) ||
+|[`dolby_metadata`](#dolby_metadata) |Dolby Metadata (Atmos, AC3, Dolby Digital) ||
|`elf` |Executable and Linkable Format ||
|`ether8023_frame` |Ethernet 802.3 frame |`inet_packet`|
|`exif` |Exchangeable Image File Format ||
@@ -133,7 +130,7 @@
|`vp9_frame` |VP9 frame ||
|`vpx_ccr` |VPX Codec Configuration Record ||
|[`wasm`](#wasm) |WebAssembly Binary Format ||
-|`wav` |WAV file |`id3v2` `id3v1` `id3v11`|
+|`wav` |WAV file |`id3v2` `id3v1` `id3v11` `dolby_metadata`|
|`webp` |WebP image |`exif` `vp8_frame` `icc_profile` `xml`|
|[`xml`](#xml) |Extensible Markup Language ||
|`yaml` |YAML Ain't Markup Language ||
@@ -183,27 +180,6 @@ Decode value as aac_frame
... | aac_frame({object_type:1})
```
-## adm
-[Audio Definition Model](https://adm.ebu.io/background/what_is_the_adm.html) including 3D Audio.
-
-RIFF / WAV / Broadcast Wave Format (BWF) chunks:
-- `` Chunk, Track UIDs of Audio Definition Model
-- `` Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements
-
-### Examples
-Decode ADM configuration from `` and `` chunks:
-```
-$ fq -d wav '.chunks[] | select(.id | IN("chna", "axml")) | tovalue' bwf.wav
-```
-
-### Authors
-- [@johnnymarnell](https://johnnymarnell.github.io), original author
-
-### References
-- https://adm.ebu.io/background/what_is_the_adm.html
-- https://tech.ebu.ch/publications/tech3285s7
-- https://tech.ebu.ch/publications/tech3285s5
-
## apple_bookmark
Apple BookmarkData.
@@ -616,6 +592,8 @@ $ fq -d csv '.[0] as $t | .[1:] | map(with_entries(.key = $t[.key]))' file.csv
```
## dolby_metadata
+Dolby Metadata (Atmos, AC3, Dolby Digital).
+
Dolby Metadata from `` chunk of RIFF / WAV / Broadcast Wave Format (BWF),
including Dolby Atmos, AC3, Dolby Digital \[Plus\], and Dolby Audio Info (e.g. LUFS, True Peak).
@@ -625,13 +603,20 @@ Decode Dolby metadata from `` chunk:
$ fq -d wav '.chunks[] | select(.id | IN("dbmd")) | tovalue' bwf.wav
```
-### References
-- https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf
-- https://github.com/DolbyLaboratories/dbmd-atmos-parser
+RIFF / WAV / Broadcast Wave Format (BWF) chunks:
+- `` Track UIDs of Audio Definition Model
+- `` BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements
### Authors
- [@johnnymarnell](https://johnnymarnell.github.io), original author
+### References
+- https://adm.ebu.io/background/what_is_the_adm.html
+- https://tech.ebu.ch/publications/tech3285s7
+- https://tech.ebu.ch/publications/tech3285s5
+- https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf
+- https://github.com/DolbyLaboratories/dbmd-atmos-parser
+
## fit
Garmin Flexible and Interoperable Data Transfer.
@@ -824,6 +809,7 @@ LevelDB Table.
- Zstandard uncompression is not implemented yet.
### Authors
+
- [@mikez](https://github.com/mikez), original author
### References
diff --git a/format/format.go b/format/format.go
index a87ff0e02..006e6878f 100644
--- a/format/format.go
+++ b/format/format.go
@@ -92,6 +92,7 @@ var (
CSV = &decode.Group{Name: "csv"}
DNS = &decode.Group{Name: "dns"}
DNS_TCP = &decode.Group{Name: "dns_tcp"}
+ Dolby_Metadata = &decode.Group{Name: "dolby_metadata"}
ELF = &decode.Group{Name: "elf"}
Ether_8023_Frame = &decode.Group{Name: "ether8023_frame"}
Exif = &decode.Group{Name: "exif"}
diff --git a/format/riff/adm.go b/format/riff/adm.go
index 57b4193f0..b8955263c 100644
--- a/format/riff/adm.go
+++ b/format/riff/adm.go
@@ -9,21 +9,22 @@ import (
"github.com/wader/fq/pkg/decode"
)
-func chnaDecode(d *decode.D, size int64) {
+func chnaDecode(d *decode.D) {
d.FieldU16("num_tracks")
d.FieldU16("num_uids")
-
- audioIdLen := (size - 4) / 40
- d.FieldStructNArray("audio_ids", "audio_id", int64(audioIdLen), func(d *decode.D) {
- d.FieldU16("track_index")
- d.FieldUTF8("uid", 12)
- d.FieldUTF8("track_format_id_reference", 14)
- d.FieldUTF8("pack_format_id_reference", 11)
- d.FieldRawLen("padding", 8)
+ d.FieldArray("audio_ids", func(d *decode.D) {
+ for !d.End() {
+ d.FieldStruct("audio_id", func(d *decode.D) {
+ d.FieldU16("track_index")
+ d.FieldUTF8("uid", 12)
+ d.FieldUTF8("track_format_id_reference", 14)
+ d.FieldUTF8("pack_format_id_reference", 11)
+ d.FieldRawLen("padding", 8)
+ })
+ }
})
}
-func axmlDecode(d *decode.D, size int64) {
- // TODO(jmarnell): this chunk is all variable xml, so leave as is?
- d.FieldUTF8("xml", int(size))
+func axmlDecode(d *decode.D) {
+ d.FieldUTF8("xml", int(d.BitsLeft())/8)
}
diff --git a/format/riff/aiff.go b/format/riff/aiff.go
index d02346176..6efcd68c7 100644
--- a/format/riff/aiff.go
+++ b/format/riff/aiff.go
@@ -54,7 +54,7 @@ func aiffDecode(d *decode.D) any {
}
return id, size
},
- func(d *decode.D, id string, path path, size int64) (bool, any) {
+ func(d *decode.D, id string, path path) (bool, any) {
switch id {
case "FORM":
riffType = d.FieldUTF8("format", 4, d.StrAssert(aiffRiffType))
diff --git a/format/riff/avi.go b/format/riff/avi.go
index f2c57406b..d35ae3a85 100644
--- a/format/riff/avi.go
+++ b/format/riff/avi.go
@@ -238,7 +238,7 @@ func aviDecodeEx(d *decode.D, ai format.AVI_In, extendedChunk bool) {
size := d.FieldU32("size")
return id, int64(size)
},
- func(d *decode.D, id string, path path, size int64) (bool, any) {
+ func(d *decode.D, id string, path path) (bool, any) {
switch id {
case "RIFF":
foundRiffType = d.FieldUTF8("type", 4, d.StrAssert(requiredRiffType))
diff --git a/format/riff/common.go b/format/riff/common.go
index 5415390a3..f00d125dc 100644
--- a/format/riff/common.go
+++ b/format/riff/common.go
@@ -19,11 +19,11 @@ func (p path) topData() any {
return p[len(p)-1].data
}
-func riffDecode(d *decode.D, path path, headFn func(d *decode.D, path path) (string, int64), chunkFn func(d *decode.D, id string, path path, size int64) (bool, any)) {
+func riffDecode(d *decode.D, path path, headFn func(d *decode.D, path path) (string, int64), chunkFn func(d *decode.D, id string, path path) (bool, any)) {
id, size := headFn(d, path)
d.FramedFn(size*8, func(d *decode.D) {
- hasChildren, data := chunkFn(d, id, path, size)
+ hasChildren, data := chunkFn(d, id, path)
if hasChildren {
np := append(path, pathEntry{id: id, data: data})
d.FieldArray("chunks", func(d *decode.D) {
diff --git a/format/riff/dolby.go b/format/riff/dolby_metadata.go
similarity index 68%
rename from format/riff/dolby.go
rename to format/riff/dolby_metadata.go
index 2f27e5372..5d8cd4414 100644
--- a/format/riff/dolby.go
+++ b/format/riff/dolby_metadata.go
@@ -5,11 +5,31 @@ package riff
// https://github.com/DolbyLaboratories/dbmd-atmos-parser
import (
+ "embed"
+
+ "github.com/wader/fq/format"
"github.com/wader/fq/pkg/decode"
+ "github.com/wader/fq/pkg/interp"
"github.com/wader/fq/pkg/scalar"
)
+//go:embed dolby_metadata.md
+var dolbyMetadataFS embed.FS
+
+func init() {
+ interp.RegisterFormat(
+ format.Dolby_Metadata,
+ &decode.Format{
+ Description: "Dolby Metadata (Atmos, AC3, Dolby Digital)",
+ DecodeFn: dbmdDecode,
+ },
+ )
+ interp.RegisterFS(dolbyMetadataFS)
+}
+
func dbmdDecode(d *decode.D) any {
+ d.Endian = decode.LittleEndian
+
d.FieldStruct("version", func(d *decode.D) {
d.FieldU8("major")
d.FieldU8("minor")
@@ -30,15 +50,15 @@ func dbmdDecode(d *decode.D) any {
segmentSize := d.FieldU16("size")
switch segmentID {
- case metadataSegmentTypeDolyEMetadata:
+ case metadataSegmentTypeDolbyEMetadata:
parseDolbyE(d)
- case metadataSegmentTypeDolyEDigitaletadata:
+ case metadataSegmentTypeDolbyEDigitaletadata:
parseDolbyDigital(d)
- case metadataSegmentTypeDolyDigitalPlusMetadata:
+ case metadataSegmentTypeDolbyDigitalPlusMetadata:
parseDolbyDigitalPlus(d)
case metadataSegmentTypeAudioInfo:
parseAudioInfo(d)
- case metadataSegmentTypeDolyAtmos:
+ case metadataSegmentTypeDolbyAtmos:
parseDolbyAtmos(d)
case metadataSegmentTypeDolbyAtmosSupplemental:
parseDolbyAtmosSupplemental(d)
@@ -63,7 +83,7 @@ var compressionDescMap = scalar.UintMapSymStr{
5: "speech",
}
-var bitstreamMode = scalar.UintMapDescription{
+var bitstreamModeMap = scalar.UintMapDescription{
0b000: "main audio service: complete main (CM)",
0b001: "main audio service: music and effects (ME)",
0b010: "associated service: visually impaired (VI)",
@@ -104,43 +124,38 @@ var trimConfigName = scalar.UintMapDescription{
8: "7.1.4",
}
-var trimType = scalar.UintMapDescription{
- 0: "manual",
- 1: "automatic",
-}
-
const (
- metadataSegmentTypeEnd = 0
- metadataSegmentTypeDolyEMetadata = 1
- metadataSegmentTypeDolyReserved2 = 2
- metadataSegmentTypeDolyEDigitaletadata = 3
- metadataSegmentTypeDolyReserved4 = 4
- metadataSegmentTypeDolyReserved5 = 5
- metadataSegmentTypeDolyReserved6 = 6
- metadataSegmentTypeDolyDigitalPlusMetadata = 7
- metadataSegmentTypeAudioInfo = 8
- metadataSegmentTypeDolyAtmos = 9
- metadataSegmentTypeDolbyAtmosSupplemental = 10
+ metadataSegmentTypeEnd = 0
+ metadataSegmentTypeDolbyEMetadata = 1
+ metadataSegmentTypeDolbyReserved2 = 2
+ metadataSegmentTypeDolbyEDigitaletadata = 3
+ metadataSegmentTypeDolbyReserved4 = 4
+ metadataSegmentTypeDolbyReserved5 = 5
+ metadataSegmentTypeDolbyReserved6 = 6
+ metadataSegmentTypeDolbyDigitalPlusMetadata = 7
+ metadataSegmentTypeAudioInfo = 8
+ metadataSegmentTypeDolbyAtmos = 9
+ metadataSegmentTypeDolbyAtmosSupplemental = 10
)
var metadataSegmentTypeMap = scalar.UintMapSymStr{
- metadataSegmentTypeEnd: "end",
- metadataSegmentTypeDolyEMetadata: "doly_e_metadata",
- metadataSegmentTypeDolyReserved2: "reserved2",
- metadataSegmentTypeDolyEDigitaletadata: "doly_e_digitale_tadata",
- metadataSegmentTypeDolyReserved4: "reserved4",
- metadataSegmentTypeDolyReserved5: "reserved5",
- metadataSegmentTypeDolyReserved6: "reserved6",
- metadataSegmentTypeDolyDigitalPlusMetadata: "doly_digital_plus_metadata",
- metadataSegmentTypeAudioInfo: "audio_info",
- metadataSegmentTypeDolyAtmos: "doly_atmos",
- metadataSegmentTypeDolbyAtmosSupplemental: "dolby_atmos_supplemental",
+ metadataSegmentTypeEnd: "end",
+ metadataSegmentTypeDolbyEMetadata: "dolby_e_metadata",
+ metadataSegmentTypeDolbyReserved2: "reserved2",
+ metadataSegmentTypeDolbyEDigitaletadata: "dolby_e_digitale_tadata",
+ metadataSegmentTypeDolbyReserved4: "reserved4",
+ metadataSegmentTypeDolbyReserved5: "reserved5",
+ metadataSegmentTypeDolbyReserved6: "reserved6",
+ metadataSegmentTypeDolbyDigitalPlusMetadata: "dolby_digital_plus_metadata",
+ metadataSegmentTypeAudioInfo: "audio_info",
+ metadataSegmentTypeDolbyAtmos: "dolby_atmos",
+ metadataSegmentTypeDolbyAtmosSupplemental: "dolby_atmos_supplemental",
}
func parseDolbyE(d *decode.D) {
d.FieldU8("program_config")
d.FieldU8("frame_rate_code")
- d.FieldRawLen("e_SMPTE_time_code", 8*8)
+ d.FieldRawLen("e_smpte_time_code", 8*8)
d.FieldRawLen("e_reserved", 1*8)
d.FieldRawLen("e_reserved2", 25*8)
d.FieldRawLen("reserved_for_future_use", 80*8)
@@ -176,9 +191,9 @@ func parseDolbyDigitalPlus(d *decode.D) {
if bsmod == 0b111 && acmod != 0b001 {
bsmod = 0b1000
}
- d.FieldValueStr("bitstream_mode", bitstreamMode[bsmod])
+ d.FieldValueStr("bitstream_mode", bitstreamModeMap[bsmod])
- d.FieldU16LE("ddplus_reserved_a")
+ d.FieldU16("ddplus_reserved_a")
d.FieldU8("surround_config")
d.FieldU8("dialnorm_info")
@@ -188,32 +203,32 @@ func parseDolbyDigitalPlus(d *decode.D) {
d.FieldU8("ext_bsi1_word2")
d.FieldU8("ext_bsi2_word1")
- d.FieldU24LE("ddplus_reserved_b")
+ d.FieldU24("ddplus_reserved_b")
d.FieldU8("compr1", scalar.UintSym("reserved"), compressionDescMap)
d.FieldU8("dynrng1", scalar.UintSym("reserved"), compressionDescMap)
- d.FieldU24LE("ddplus_reserved_c")
+ d.FieldU24("ddplus_reserved_c")
d.FieldU8("ddplus_info1")
- d.FieldU40LE("ddplus_reserved_d")
+ d.FieldU40("ddplus_reserved_d")
- d.FieldU16LE("datarate")
+ d.FieldU16("datarate")
d.FieldRawLen("reserved_for_future_use", 69*8)
}
func parseAudioInfo(d *decode.D) {
d.FieldU8("program_id")
d.FieldUTF8("audio_origin", 32)
- d.FieldU32LE("largest_sample_value")
- d.FieldU32LE("largest_sample_value_2")
- d.FieldU32LE("largest_true_peak_value")
- d.FieldU32LE("largest_true_peak_value_2")
- d.FieldU32LE("dialogue_loudness")
- d.FieldU32LE("dialogue_loudness_2")
- d.FieldU32LE("speech_content")
- d.FieldU32LE("speech_content_2")
+ d.FieldU32("largest_sample_value")
+ d.FieldU32("largest_sample_value_2")
+ d.FieldU32("largest_true_peak_value")
+ d.FieldU32("largest_true_peak_value_2")
+ d.FieldU32("dialogue_loudness")
+ d.FieldU32("dialogue_loudness_2")
+ d.FieldU32("speech_content")
+ d.FieldU32("speech_content_2")
d.FieldUTF8("last_processed_by", 32)
d.FieldUTF8("last_operation", 32)
d.FieldUTF8("segment_creation_date", 32)
@@ -241,19 +256,20 @@ func parseDolbyAtmos(d *decode.D) {
}
func parseDolbyAtmosSupplemental(d *decode.D) {
- d.FieldU32LE("dasms_sync", d.UintAssert(0xf8726fbd), scalar.UintHex)
+ d.FieldU32("dasms_sync", d.UintAssert(0xf8726fbd), scalar.UintHex)
// TODO: wav.go sets LE default i think?
- objectCount := int64(d.FieldU16LE("object_count"))
- d.FieldU8LE("reserved")
+ objectCount := int64(d.FieldU16("object_count"))
+ d.FieldU8("reserved")
i := 0
d.FieldStructNArray("trim_configs", "trim_config", 9, func(d *decode.D) {
- autoTrimReserved := d.FieldU8LE("auto_trim_reserved")
- autoTrim := autoTrimReserved & 0x01
- d.FieldValueBool("auto_trim", autoTrim == 1)
- d.FieldValueStr("trim_type", trimType[autoTrim])
- d.FieldValueStr("trim_config_name", trimConfigName[uint64(i)])
+ d.FieldRawLen("reserved", 7)
+ d.FieldU1("type", scalar.UintMapSymStr{
+ 0: "manual",
+ 1: "automatic",
+ })
+ d.FieldValueStr("config_name", trimConfigName[uint64(i)])
// TODO: this is null separted list of def strings?
d.FieldUTF8("raw", 14)
diff --git a/format/riff/dolby_metadata.md b/format/riff/dolby_metadata.md
new file mode 100644
index 000000000..61fea4242
--- /dev/null
+++ b/format/riff/dolby_metadata.md
@@ -0,0 +1,22 @@
+Dolby Metadata from `` chunk of RIFF / WAV / Broadcast Wave Format (BWF),
+including Dolby Atmos, AC3, Dolby Digital \[Plus\], and Dolby Audio Info (e.g. LUFS, True Peak).
+
+### Examples
+Decode Dolby metadata from `` chunk:
+```
+$ fq -d wav '.chunks[] | select(.id | IN("dbmd")) | tovalue' bwf.wav
+```
+
+RIFF / WAV / Broadcast Wave Format (BWF) chunks:
+- `` Track UIDs of Audio Definition Model
+- `` BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements
+
+### Authors
+- [@johnnymarnell](https://johnnymarnell.github.io), original author
+
+### References
+- https://adm.ebu.io/background/what_is_the_adm.html
+- https://tech.ebu.ch/publications/tech3285s7
+- https://tech.ebu.ch/publications/tech3285s5
+- https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf
+- https://github.com/DolbyLaboratories/dbmd-atmos-parser
diff --git a/format/riff/wav.go b/format/riff/wav.go
index 98329d9ff..4c87b59a7 100644
--- a/format/riff/wav.go
+++ b/format/riff/wav.go
@@ -16,6 +16,7 @@ import (
var wavHeaderGroup decode.Group
var wavFooterGroup decode.Group
+var wavDolbyMetadataGroup decode.Group
func init() {
interp.RegisterFormat(
@@ -28,6 +29,7 @@ func init() {
Dependencies: []decode.Dependency{
{Groups: []*decode.Group{format.ID3v2}, Out: &wavHeaderGroup},
{Groups: []*decode.Group{format.ID3v1, format.ID3v11}, Out: &wavFooterGroup},
+ {Groups: []*decode.Group{format.Dolby_Metadata}, Out: &wavDolbyMetadataGroup},
},
})
}
@@ -76,7 +78,7 @@ func wavDecode(d *decode.D) any {
return id, size
},
- func(d *decode.D, id string, path path, size int64) (bool, any) {
+ func(d *decode.D, id string, path path) (bool, any) {
switch id {
case "RIFF":
riffType = d.FieldUTF8("format", 4, d.StrAssert(wavRiffType))
@@ -159,13 +161,13 @@ func wavDecode(d *decode.D) any {
return false, nil
case "chna":
- chnaDecode(d, size)
+ chnaDecode(d)
return false, nil
case "axml":
- axmlDecode(d, size)
+ axmlDecode(d)
return false, nil
case "dbmd":
- dbmdDecode(d)
+ d.Format(&wavDolbyMetadataGroup, nil)
return false, nil
default:
diff --git a/format/riff/webp.go b/format/riff/webp.go
index de69d6238..ebc0bbc59 100644
--- a/format/riff/webp.go
+++ b/format/riff/webp.go
@@ -44,7 +44,7 @@ func webpDecode(d *decode.D) any {
size := d.FieldU32("size")
return id, int64(size)
},
- func(d *decode.D, id string, path path, size int64) (bool, any) {
+ func(d *decode.D, id string, path path) (bool, any) {
switch id {
case "RIFF":
riffType = d.FieldUTF8("format", 4, d.StrAssert(webpRiffType))