feat: Lidarr searches for a better copy of a rip that has none (M498 #5447)
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m26s
release / go (push) Successful in 1m45s
release / integration (push) Successful in 5m6s
release / android (push) Successful in 5m58s
release / Build signed APK (releases and dev) (push) Successful in 6m17s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 26s
release / Verify release artifacts (tag releases only) (push) Skipped

The operator chose option 2: an album holding a video rip that is the only
copy of its song is searched in Lidarr (AlbumSearch), at most once a week
and ten albums a pass. Lidarr grabs only when the quality profile allows an
upgrade, so nothing is removed and the rip stays held back either way.

- lidarr.Client.SearchAlbums posts the AlbumSearch command.
- ListAlbumsWithSoleCopyRips finds the albums: a held-back rip with no
  present clean copy sharing its song or its pending group, and not searched
  in the last week (audit action lidarr_rip_search).
- Suspect sources shows when Lidarr last searched the track's album.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 22:32:30 -04:00
co-authored by Claude Opus 5.5
parent d59b7fdc2a
commit e17556dce9
12 changed files with 337 additions and 7 deletions
+45 -1
View File
@@ -287,10 +287,17 @@ SELECT t.id,
artists.id AS artist_id,
artists.name AS artist_name,
t.source_verdict,
t.source_verdict_at
t.source_verdict_at,
searched.at AS searched_at
FROM tracks t
JOIN albums ON albums.id = t.album_id
JOIN artists ON artists.id = t.artist_id
-- When the resolver last asked Lidarr to search the album for a better copy
-- (#5447).
LEFT JOIN LATERAL (
SELECT max(l.created_at)::timestamptz AS at FROM audit_log l
WHERE l.action = 'lidarr_rip_search' AND l.metadata->>'album_id' = albums.id::text
) searched ON true
WHERE t.missing_since IS NULL
AND regexp_replace(t.file_path, '^.*/', '') ~* sqlc.arg(pattern)::text
ORDER BY directory, t.disc_number NULLS FIRST, t.track_number NULLS FIRST, t.title
@@ -327,3 +334,40 @@ UPDATE tracks
SET source_verdict = sqlc.arg(verdict)::text, source_verdict_at = now()
WHERE id = sqlc.arg(id)
AND source_verdict IS NOT NULL;
-- name: ListAlbumsWithSoleCopyRips :many
-- Albums holding a held-back video rip that is the only copy of its song
-- (#5447): no present copy that is not a rip shares its song, and no pending
-- duplicate group pairs it with one. Lidarr counts such a track fulfilled, so
-- the resolver asks it to search the album for a better release. Albums
-- searched within the last week are left out, most rips first.
SELECT a.id AS album_id,
a.release_group_mbid::text AS release_group_mbid,
a.title AS album_title,
ar.name AS artist_name,
count(*)::bigint AS rips
FROM tracks t
JOIN albums a ON a.id = t.album_id
JOIN artists ar ON ar.id = a.artist_id
WHERE t.source_verdict = 'suspect'
AND t.missing_since IS NULL
AND a.release_group_mbid IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM tracks c
WHERE c.song_key = t.song_key AND c.id <> t.id
AND c.missing_since IS NULL AND c.source_verdict IS DISTINCT FROM 'suspect')
AND NOT EXISTS (
SELECT 1 FROM duplicate_group_members m
JOIN duplicate_groups g ON g.id = m.group_id AND g.status = 'pending'
JOIN duplicate_group_members m2 ON m2.group_id = g.id AND m2.track_id <> t.id
JOIN tracks c ON c.id = m2.track_id
WHERE m.track_id = t.id
AND c.missing_since IS NULL AND c.source_verdict IS DISTINCT FROM 'suspect')
AND NOT EXISTS (
SELECT 1 FROM audit_log l
WHERE l.action = 'lidarr_rip_search'
AND l.metadata->>'album_id' = a.id::text
AND l.created_at > now() - interval '7 days')
GROUP BY a.id, a.release_group_mbid, a.title, ar.name
ORDER BY count(*) DESC, a.id
LIMIT sqlc.arg(page_limit);