A background Fingerprinter runs after every scan and at startup, one file at a time, filling media_files.sha256 and track_fingerprints (fpcalc raw, first 120 s). Without fpcalc only SHA-256 is computed and files stay queued for Chromaprint. Changing a file's content resets its prints. Similarity() compares raw prints with a ±80-point alignment search; it is the basis for duplicate detection in the upload queue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
1.5 KiB
Go
59 lines
1.5 KiB
Go
package library
|
|
|
|
import (
|
|
"math/rand/v2"
|
|
"testing"
|
|
)
|
|
|
|
func randomPrint(r *rand.Rand, n int) []uint32 {
|
|
out := make([]uint32, n)
|
|
for i := range out {
|
|
out[i] = r.Uint32()
|
|
}
|
|
return out
|
|
}
|
|
|
|
func TestSimilarity(t *testing.T) {
|
|
r := rand.New(rand.NewPCG(1, 2))
|
|
a := randomPrint(r, 950) // ~2 мин
|
|
if s := Similarity(a, a); s != 1 {
|
|
t.Fatalf("same print: %v", s)
|
|
}
|
|
|
|
// Перекодирование: другое вступление (сдвиг на 20 точек) и ~8% перевёрнутых бит.
|
|
b := append(randomPrint(r, 20), a...)
|
|
for i := range b {
|
|
for bit := range 32 {
|
|
if r.IntN(100) < 8 {
|
|
b[i] ^= 1 << bit
|
|
}
|
|
}
|
|
}
|
|
if s := Similarity(a, b); s < SameRecording {
|
|
t.Fatalf("re-encoded copy: %v < %v", s, SameRecording)
|
|
}
|
|
|
|
if s := Similarity(a, randomPrint(r, 950)); s >= SameRecording {
|
|
t.Fatalf("different recordings: %v >= %v", s, SameRecording)
|
|
}
|
|
if s := Similarity(a, a[:40]); s != 0 {
|
|
t.Fatalf("too short overlap must not match: %v", s)
|
|
}
|
|
}
|
|
|
|
func TestParseFPCalc(t *testing.T) {
|
|
fp, err := parseFPCalc([]byte(`{"duration": 215.4, "fingerprint": [4294967295, 1, 2]}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if fp.DurationMs != 215400 || len(fp.Points) != 3 || fp.Points[0] != 4294967295 {
|
|
t.Fatalf("parsed %+v", fp)
|
|
}
|
|
if fp.Prefix() != int64(-4294967295) { // 0xFFFFFFFF_00000001 как int64
|
|
t.Fatalf("prefix %d", fp.Prefix())
|
|
}
|
|
if _, err := parseFPCalc([]byte(`{"duration": 1, "fingerprint": []}`)); err == nil {
|
|
t.Fatal("empty fingerprint must fail")
|
|
}
|
|
}
|