feat(qmc): improve m4a & mp4 sniff

This commit is contained in:
Unlock Music Dev 2022-12-06 21:47:33 +08:00
parent 2c378d4d46
commit e9e63ce175
No known key found for this signature in database
GPG Key ID: 95202E10D3413A1D
4 changed files with 52 additions and 9 deletions

View File

@ -90,7 +90,7 @@ func (d *Decoder) validateDecode() error {
return fmt.Errorf("qmc seek to start: %w", err) return fmt.Errorf("qmc seek to start: %w", err)
} }
buf := make([]byte, 16) buf := make([]byte, 64)
if _, err := io.ReadFull(d.raw, buf); err != nil { if _, err := io.ReadFull(d.raw, buf); err != nil {
return fmt.Errorf("qmc read header: %w", err) return fmt.Errorf("qmc read header: %w", err)
} }

View File

@ -186,7 +186,7 @@ func tryDecFile(inputFile string, outputDir string, allDec []common.NewDecoderFu
} }
header := bytes.NewBuffer(nil) header := bytes.NewBuffer(nil)
_, err = io.CopyN(header, dec, 16) _, err = io.CopyN(header, dec, 64)
if err != nil { if err != nil {
return fmt.Errorf("read header failed: %w", err) return fmt.Errorf("read header failed: %w", err)
} }

2
go.sum
View File

@ -37,8 +37,6 @@ golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/exp v0.0.0-20221204150635-6dcec336b2bb h1:QIsP/NmClBICkqnJ4rSIhnrGiGR7Yv9ZORGGnmmLTPk= golang.org/x/exp v0.0.0-20221204150635-6dcec336b2bb h1:QIsP/NmClBICkqnJ4rSIhnrGiGR7Yv9ZORGGnmmLTPk=
golang.org/x/exp v0.0.0-20221204150635-6dcec336b2bb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20221204150635-6dcec336b2bb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -1,6 +1,11 @@
package sniff package sniff
import "bytes" import (
"bytes"
"encoding/binary"
"golang.org/x/exp/slices"
)
type Sniffer interface { type Sniffer interface {
Sniff(header []byte) bool Sniff(header []byte) bool
@ -19,8 +24,8 @@ var audioExtensions = map[string]Sniffer{
}, },
// ref: https://www.garykessler.net/library/file_sigs.html // ref: https://www.garykessler.net/library/file_sigs.html
".m4a": mpeg4Sniffer{}, // MPEG-4 container, m4a treat as audio ".m4a": m4aSniffer{}, // MPEG-4 container, Apple Lossless Audio Codec
".aac": prefixSniffer{0xFF, 0xF1}, // MPEG-4 AAC-LC ".mp4": &mpeg4Sniffer{}, // MPEG-4 container, other fallback
".flac": prefixSniffer("fLaC"), // ref: https://xiph.org/flac/format.html ".flac": prefixSniffer("fLaC"), // ref: https://xiph.org/flac/format.html
".dff": prefixSniffer("FRM8"), // DSDIFF, ref: https://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf ".dff": prefixSniffer("FRM8"), // DSDIFF, ref: https://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf
@ -54,8 +59,48 @@ func (s prefixSniffer) Sniff(header []byte) bool {
return bytes.HasPrefix(header, s) return bytes.HasPrefix(header, s)
} }
type m4aSniffer struct{}
func (m4aSniffer) Sniff(header []byte) bool {
box := readMpeg4FtypBox(header)
if box == nil {
return false
}
return box.majorBrand == "M4A " || slices.Contains(box.compatibleBrands, "M4A ")
}
type mpeg4Sniffer struct{} type mpeg4Sniffer struct{}
func (mpeg4Sniffer) Sniff(header []byte) bool { func (s *mpeg4Sniffer) Sniff(header []byte) bool {
return len(header) >= 8 && bytes.Equal([]byte("ftyp"), header[4:8]) return readMpeg4FtypBox(header) != nil
}
type mpeg4FtpyBox struct {
majorBrand string
minorVersion uint32
compatibleBrands []string
}
func readMpeg4FtypBox(header []byte) *mpeg4FtpyBox {
if (len(header) < 8) || !bytes.Equal([]byte("ftyp"), header[4:8]) {
return nil // not a valid ftyp box
}
size := binary.BigEndian.Uint32(header[0:4]) // size
if size < 16 || size%4 != 0 {
return nil // invalid ftyp box
}
box := mpeg4FtpyBox{
majorBrand: string(header[8:12]),
minorVersion: binary.BigEndian.Uint32(header[12:16]),
}
// compatible brands
for i := 16; i < int(size) && i+4 < len(header); i += 4 {
box.compatibleBrands = append(box.compatibleBrands, string(header[i:i+4]))
}
return &box
} }