Files
SpotiFLAC-Mobile/go_backend/metadata_m4a_quality_test.go
T
zarzet d664d46ca4 feat: detect FLAC/ALAC/EAC3/AC3/AC4 codecs inside MP4 containers
GetM4AQuality now recognizes fLaC, alac, ec-3, ac-3, and ac-4 sample entries and parses the MP4 FLACSpecificBox so library entries carry the real codec rather than the container extension. The AudioQuality struct exposes Codec and Bitrate fields (with an estimator for compressed streams), and ReadFileMetadata publishes format + audio_codec so Flutter and Kotlin can make format decisions based on the actual stream.

Downstream: library_scan labels M4A-family items as flac/alac/eac3/ac3/ac4/m4a, zeroes the bitrate for lossless formats, and the filter UI + quality badges use the codec-derived format instead of only the file extension. Scans and SAF importers also accept .mp4 and .aac file extensions. New unit tests cover codec name mapping and MP4 FLACSpecificBox decoding.
2026-05-10 22:14:47 +07:00

100 lines
2.8 KiB
Go

package gobackend
import "testing"
func TestParseALACSpecificConfigStandardPayload(t *testing.T) {
payload := make([]byte, 24)
payload[5] = 24
payload[20] = 0x00
payload[21] = 0x00
payload[22] = 0xac
payload[23] = 0x44
bitDepth, sampleRate, ok := parseALACSpecificConfig(payload)
if !ok {
t.Fatal("expected standard ALAC payload to parse")
}
if bitDepth != 24 {
t.Fatalf("bitDepth = %d, want 24", bitDepth)
}
if sampleRate != 44100 {
t.Fatalf("sampleRate = %d, want 44100", sampleRate)
}
}
func TestParseALACSpecificConfigPayloadWithLeadingFourBytes(t *testing.T) {
payload := make([]byte, 28)
payload[9] = 16
payload[24] = 0x00
payload[25] = 0x00
payload[26] = 0xbb
payload[27] = 0x80
bitDepth, sampleRate, ok := parseALACSpecificConfig(payload)
if !ok {
t.Fatal("expected offset ALAC payload to parse")
}
if bitDepth != 16 {
t.Fatalf("bitDepth = %d, want 16", bitDepth)
}
if sampleRate != 48000 {
t.Fatalf("sampleRate = %d, want 48000", sampleRate)
}
}
func TestParseALACSpecificConfigRejectsShortPayload(t *testing.T) {
if _, _, ok := parseALACSpecificConfig(make([]byte, 12)); ok {
t.Fatal("expected short ALAC payload to be rejected")
}
}
func TestM4ACodecFormatMapping(t *testing.T) {
cases := map[string]string{
"mp4a": "aac",
"alac": "alac",
"fLaC": "flac",
"ec-3": "eac3",
"ac-3": "ac3",
"ac-4": "ac4",
}
for atomType, want := range cases {
if got := normalizeM4AAudioCodec(atomType); got != want {
t.Fatalf("normalizeM4AAudioCodec(%q) = %q, want %q", atomType, got, want)
}
}
if got := libraryFormatForM4ACodec("flac"); got != "flac" {
t.Fatalf("libraryFormatForM4ACodec(flac) = %q", got)
}
if got := libraryFormatForM4ACodec("eac3"); got != "eac3" {
t.Fatalf("libraryFormatForM4ACodec(eac3) = %q", got)
}
if got := libraryFormatForM4ACodec("aac"); got != "m4a" {
t.Fatalf("libraryFormatForM4ACodec(aac) = %q", got)
}
}
func TestParseMP4FLACSpecificConfig(t *testing.T) {
streamInfo := make([]byte, 34)
sampleRate := 48000
bitsPerSample := 24
totalSamples := int64(48000 * 180)
streamInfo[10] = byte(sampleRate >> 12)
streamInfo[11] = byte(sampleRate >> 4)
streamInfo[12] = byte((sampleRate&0x0F)<<4 | ((bitsPerSample-1)>>4)&0x01)
streamInfo[13] = byte(((bitsPerSample-1)&0x0F)<<4 | int((totalSamples>>32)&0x0F))
streamInfo[14] = byte(totalSamples >> 24)
streamInfo[15] = byte(totalSamples >> 16)
streamInfo[16] = byte(totalSamples >> 8)
streamInfo[17] = byte(totalSamples)
payload := append([]byte{0, 0, 0, 0, 0, 0, 0, 34}, streamInfo...)
bitDepth, parsedRate, parsedSamples, ok := parseMP4FLACSpecificConfig(payload)
if !ok {
t.Fatal("expected MP4 FLAC config to parse")
}
if bitDepth != bitsPerSample || parsedRate != sampleRate || parsedSamples != totalSamples {
t.Fatalf("FLAC config = %d/%d/%d", bitDepth, parsedRate, parsedSamples)
}
}