feat(uploads): match release groups with MusicBrainz and AcoustID
ci / module boundaries / boundaries (push) Successful in 10s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m30s
ci / api / api (push) Successful in 58s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m1s
ci / db / db (push) Failing after 25s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m36s
ci / ts / ts (packages + web) (push) Failing after 6m3s

This commit is contained in:
mrleo1nid committed 2026-10-29 20:44:59 +03:00
1 parent 1e3a149aef
commit 39bf7d4b70
45 files changed
+4792 -417

No files matched your search

@@ -0,0 +1,76 @@
package uploads
import (
"context"
"errors"
"sort"
"github.com/google/uuid"
"pipu/api/internal/acoustid"
)
const maxAudioFiles = 50
func chooseAudioMatch(id uuid.UUID, matches []acoustid.Match) AudioRecognition {
// Multiple AcoustIDs can point to the same recording. Compare distinct recordings only.
best := map[string]float64{}
for _, m := range matches {
if parsed, err := uuid.Parse(m.RecordingID); err == nil && parsed != uuid.Nil && m.Score > best[parsed.String()] && m.Score <= 1 {
best[parsed.String()] = m.Score
}
}
unique := []acoustid.Match{}
for id, score := range best {
unique = append(unique, acoustid.Match{RecordingID: id, Score: score})
}
sort.Slice(unique, func(i, j int) bool { return unique[i].Score > unique[j].Score })
r := AudioRecognition{FileID: id, State: "not_found"}
if len(unique) == 0 {
return r
}
r.Score = unique[0].Score
if unique[0].Score < .9 || len(unique) > 1 && unique[0].Score-unique[1].Score < .05 {
r.State = "ambiguous"
return r
}
r.State, r.RecordingID = "matched", unique[0].RecordingID
return r
}
// identifyAudio runs only after an explicit reviewer request. It never modifies file tags.
func (s *Service) identifyAudio(ctx context.Context, files []File) ([]AudioRecognition, map[uuid.UUID]AudioRecognition, []string, error) {
results := []AudioRecognition{}
byFile := map[uuid.UUID]AudioRecognition{}
ids := []string{}
if s.identifier == nil || s.encodedFingerprint == nil {
return results, byFile, ids, errors.New("acoustid: not configured")
}
for _, f := range releaseFiles(files) {
if f.Status != StatusPendingReview && f.Status != StatusImportFailed {
continue
}
if len(results) >= maxAudioFiles {
return results, byFile, ids, errors.New("acoustid: batch too large")
}
r := AudioRecognition{FileID: f.ID, State: "unavailable"}
if f.DurationMs != nil && *f.DurationMs > 0 && f.StagingPath != "" {
fingerprint, err := s.encodedFingerprint(ctx, s.stagingAbs(f.StagingPath))
if err == nil {
matches, err := s.identifier.Lookup(ctx, fingerprint, (*f.DurationMs+500)/1000)
if err != nil {
return results, byFile, ids, err
}
r = chooseAudioMatch(f.ID, matches)
}
}
if err := ctx.Err(); err != nil {
return results, byFile, ids, err
}
results = append(results, r)
byFile[f.ID] = r
if r.State == "matched" {
ids = append(ids, r.RecordingID)
}
}
return results, byFile, ids, nil
}