feat: duplicates resolve themselves without Lidarr (M502 #5517)
release / govulncheck (push) Successful in 37s
release / web (push) Successful in 1m13s
release / go (push) Successful in 1m29s
release / integration (push) Successful in 4m46s
release / android (push) Successful in 5m36s
release / Build signed APK (releases and dev) (push) Successful in 5m54s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 6m48s
release / Verify release artifacts (tag releases only) (push) Skipped

Lidarr is off, and with it the resolver had stopped merging: it only
merged when Lidarr had said which copies it mapped. Minstrel is now the
only program writing the library, so the rule is about the copies:

- A song held twice on one album: the extra copies merge into the one
  whose tags fit best (then lossless, larger, older).
- Across releases or under another title: when every copy but one is a
  video rip (held back, or named like one and not marked fine), the rips
  merge into the one that is not.
- Clean copies on different releases are linked as one song, as before.
- Anything else waits for the operator.

Gone with Lidarr: release switches and their settle window, the
unmapped-file read, the rip search, the merge guard and its 409/503,
lidarr.Client's release methods, and duplicate_group_members.lidarr_state
(migration 0078). The duplicates page drops the per-copy Lidarr badge
and the blocked-merge notice; past release changes stay in its history.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 20:12:49 -04:00
co-authored by Claude Opus 5.5
parent 8bd48b4302
commit 5306a261c1
27 changed files with 301 additions and 1743 deletions
+3 -10
View File
@@ -13,11 +13,11 @@ import (
// 498's body) splits into four shapes:
//
// - same_release: every copy is on one album. An album holds a song once, so
// the extra copies are the ones to fold away — when Lidarr does not need
// them (the resolver checks).
// the extra copies are the ones to fold away.
// - cross_release: the copies are on different albums under one title. A
// single and the album it came from, a deluxe edition and the standard one:
// each copy fulfils its own release in Lidarr, so none is deleted.
// each copy belongs to its own release, so none is deleted (unless it is a
// video rip; the resolver decides).
// - mismatch: byte-identical audio on different albums under different
// titles. One file carries another song's tags — a wrong-file import.
// - review: anything else. The operator decides.
@@ -123,13 +123,6 @@ func MatchTitleKey(title, artist string) string {
return alnumKey(title)
}
// ReleaseTitleKey is the lighter comparison for asking whether one release
// lists a song twice: case, "&" and punctuation only. Every word stays, so a
// box set's "Song" and "Song (demo)" are two songs, as the release means them.
func ReleaseTitleKey(title string) string {
return alnumKey(title)
}
// alnumKey lowercases, reads "&" as "and", and keeps only letters and digits.
func alnumKey(s string) string {
s = strings.ReplaceAll(strings.ToLower(s), "&", " and ")