e1ad1c9ba25e49009f6691c6c7c6af4ca75ea5f4
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
53eb954c86 |
feat: video rips and stray copies handle themselves (M498 #5439)
release / web (push) Failing after 26s
release / govulncheck (push) Successful in 25s
release / go (push) Failing after 58s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / integration (push) Canceled after 3m25s
release / Build signed APK (releases and dev) (push) Canceled after 3m26s
release / android (push) Canceled after 3m28s
- The resolver also merges cross-release and mismatch groups where Lidarr
maps exactly one copy: the others fulfil nothing, so removing them opens no
hole (D-a rule 1). That covers a rip beside the clean copy on another
release and a wrong-file import Lidarr holds unmapped. With two or more
mapped copies each fulfils its own release and nothing is removed.
- A track whose file name carries a video-rip marker is held back from radio
and the system mixes (tracks.source_verdict, migration 0077). It still plays
when chosen. A renamed file is released; the operator's "fine" sticks.
- Suspect sources shows what was done to each track, with "This one is fine"
and "Hold back again" (PUT /api/admin/library/suspect-sources/{id}).
- The Liked list prefers a copy that is not held back.
Replacing a rip that has no clean copy is left for the operator to decide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
5b372f61d7 |
feat: the same song on several releases counts as one song (M498 #5438)
release / govulncheck (push) Successful in 21s
release / web (push) Successful in 1m7s
release / go (push) Successful in 1m29s
release / integration (push) Successful in 4m11s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / android (push) Canceled after 5m58s
release / Build signed APK (releases and dev) (push) Canceled after 5m59s
A single and the album it is on stay two files, since each fulfils its own
release in Lidarr, but they are one song to the listener.
- tracks.song_id links copies; the generated song_key (song_id, else the
track's own id) is what they share (migration 0076).
- The resolver links each cross-release group every pass (idempotent; only
with auto-resolve on) and, when a link is new, shares existing likes across
the song and logs them for sync.
- A like or unlike (web and Subsonic) reaches every copy; each change is
logged and published so clients update every heart.
- The Liked list and its count show the song once; Shuffle and the mix writer
take one copy per song.
- A merge keeps the removed copy's song link; "Not the same song" on the
Across releases tab dismisses the group and undoes the link.
Shared plays ("heard via another copy") are left for a later step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
633d4f591f |
fix(radio): cap any one artist's share of a radio session
test-go / test (push) Successful in 1m13s
test-go / integration (push) Successful in 3m53s
release / Build signed APK (releases and dev) (push) Successful in 5m9s
release / Build + push container image (push) Successful in 1m52s
release / Verify release artifacts (tag releases only) (push) Skipped
Operator, 2026-09-10: started radio from a song and "literally all of the songs in the playlist after that were from a single artist which was not expected." There was no per-artist cap anywhere in the radio path. radio.go built the pool and handed it straight to Shuffle, which scores, sorts and takes the top N — nothing between those steps bounded any artist's share, so a pool dominated by one artist produced an output dominated by it. The asymmetry was the tell: discover.go, you_might_like.go and home.go all cap; radio never got one. With the fixture that reproduces it — 20 liked tracks by one artist plus 10 by ten others — the old path returns 10 tracks from 1 artist. It now returns 10 from 8. TWO PASSES, and that is the whole design. A hard cap was the easy mistake: radio asks for 50 tracks by default and 200 at most, so capping at three per artist over a concentrated pool would hand back a six-track "radio". Pass one takes candidates that fit under the caps; pass two fills any remaining slots from those it skipped, still in score order. The result always holds min(limit, len(candidates)) — the caps change WHICH tracks are picked, never HOW MANY. Rule 131's principle past the system mixes it was written for. The caps SCALE with the requested length rather than being a constant. Three-per-artist is a sensible 12% of a 25-track mix and an absurd 1.5% of a 200-track radio, where every selection would sit in the relaxation path and the cap would be decorative. RadioDiversityCaps holds the system mixes' proportion at any length: 3/2 at 25, 6/4 at 50, 24/16 at 200, with floors so a very short radio is not capped down to one track per artist. A BOUND, NOT AN EXCLUSION — the operator asked for the opposite of removal: "again it should be able to add songs from the same artist." The dominant artist still appears, just not exclusively. Guarded, because the tempting wrong fix is the filter songs-like used to carry. Shuffle grew the parameter rather than gaining a capped twin: radio is its only production caller, so a second function would have left the original dead (rule 22). Falsified against each named regression: uncapped gives 10/10 to one artist; a hard cap returns 3 of 10 on a single-artist pool; a cap-as-exclusion drops the artist entirely; a fixed cap stays 3 where the scaled one reaches 24. Caught while writing the guards: the artist-key constant was hand-written hex and wrong — the fixture's artist UUID carries 0001 in its fourth group, so the lookup missed and the assertion measured nothing. Derived from the same construction the fixture uses now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH |
||
|
|
8c3d06c0a1 |
feat(recommendation): add Shuffle orchestrator
Composes Score over a candidate slice, sorts descending, truncates. Pure — no IO. Liked-rank-higher and high-skip-ranks-last behaviors are stable across RNG seeds (jitter band is smaller than LikeBoost and SkipPenalty). |