mirror of
https://github.com/wader/fq.git
synced 2024-11-27 06:04:47 +03:00
88 lines
2.1 KiB
Go
88 lines
2.1 KiB
Go
package mp3
|
|
|
|
// TODO: vbri
|
|
// TODO: mime audio/mpeg
|
|
|
|
import (
|
|
"github.com/wader/fq/format"
|
|
"github.com/wader/fq/format/registry"
|
|
"github.com/wader/fq/pkg/decode"
|
|
)
|
|
|
|
var headerFormat decode.Group
|
|
var footerFormat decode.Group
|
|
var mp3Frame decode.Group
|
|
|
|
func init() {
|
|
registry.MustRegister(decode.Format{
|
|
Name: format.MP3,
|
|
ProbeOrder: 20, // after most others (silent samples and jpeg header can look like mp3 sync)
|
|
Description: "MP3 file",
|
|
Groups: []string{format.PROBE},
|
|
DecodeFn: mp3Decode,
|
|
Dependencies: []decode.Dependency{
|
|
{Names: []string{format.ID3V2}, Group: &headerFormat},
|
|
{Names: []string{
|
|
format.ID3V1,
|
|
format.ID3V11,
|
|
format.APEV2,
|
|
}, Group: &footerFormat},
|
|
{Names: []string{format.MP3_FRAME}, Group: &mp3Frame},
|
|
},
|
|
})
|
|
}
|
|
|
|
func mp3Decode(d *decode.D, in interface{}) interface{} {
|
|
// there are mp3s files in the wild with multiple headers, two id3v2 tags etc
|
|
d.FieldArray("headers", func(d *decode.D) {
|
|
for d.NotEnd() {
|
|
if dv, _, _ := d.FieldTryFormat("header", headerFormat, nil); dv == nil {
|
|
return
|
|
}
|
|
}
|
|
})
|
|
|
|
lastValidEnd := int64(0)
|
|
validFrames := 0
|
|
decodeFailures := 0
|
|
d.FieldArray("frames", func(d *decode.D) {
|
|
for d.NotEnd() {
|
|
syncLen, _, err := d.TryPeekFind(16, 8, -1, func(v uint64) bool {
|
|
return (v&0b1111_1111_1110_0000 == 0b1111_1111_1110_0000 && // sync header
|
|
v&0b0000_0000_0001_1000 != 0b0000_0000_0000_1000 && // not reserved mpeg version
|
|
v&0b0000_0000_0000_0110 == 0b0000_0000_0000_0010) // layer 3
|
|
})
|
|
if err != nil {
|
|
break
|
|
}
|
|
if syncLen > 0 {
|
|
d.SeekRel(syncLen)
|
|
}
|
|
|
|
if dv, _, _ := d.FieldTryFormat("frame", mp3Frame, nil); dv == nil {
|
|
decodeFailures++
|
|
d.SeekRel(8)
|
|
continue
|
|
}
|
|
lastValidEnd = d.Pos()
|
|
validFrames++
|
|
}
|
|
})
|
|
// TODO: better validate
|
|
if validFrames == 0 || (validFrames < 2 && decodeFailures > 0) {
|
|
d.Errorf("no frames found")
|
|
}
|
|
|
|
d.SeekAbs(lastValidEnd)
|
|
|
|
d.FieldArray("footers", func(d *decode.D) {
|
|
for d.NotEnd() {
|
|
if dv, _, _ := d.FieldTryFormat("footer", footerFormat, nil); dv == nil {
|
|
return
|
|
}
|
|
}
|
|
})
|
|
|
|
return nil
|
|
}
|