fix(backend): Live-Mitschnitte aus der Suche ausschliessen
is_live meint nur "laeuft gerade als Stream" — eine Konzert-AUFNAHME kam
ungehindert durch und landete als Albumtrack in der Bibliothek ("Dat Adam - Ufo
Video Days 2015 Live" auf dem echten Kuenstlerkanal, Dauer im Fenster). Der
Variantenfilter bekommt daher eine live-Regel. Sie schaltet sich ab, wenn der
gesuchte Titel selbst live enthaelt; die Pruefung nutzt \blive\b statt includes,
sonst wuerde "deliver" die Regel aushebeln. Drei Tests dazu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6d8a8a1d5e
commit
33b3df609a
4 files changed
+54
-2
No files matched your search
@@ -78,6 +78,12 @@ Versioning: two-part `MAJOR.MINOR` — each release bumps the minor (features an
|
|||||||
to a qualifier ("… - 2020 Remaster") is exempt from the number rule: it appears on the release but
|
to a qualifier ("… - 2020 Remaster") is exempt from the number rule: it appears on the release but
|
||||||
never in the YouTube title. Measured against a 29-title sample, 24 titles keep their previous
|
never in the YouTube title. Measured against a 29-title sample, 24 titles keep their previous
|
||||||
behaviour exactly.
|
behaviour exactly.
|
||||||
|
- **Downloader saved a concert recording as the album track** — searching DAT ADAM's "UFO" stored
|
||||||
|
"Dat Adam - Ufo Video Days 2015 Live". It passed every gate legitimately: right channel, 209 s
|
||||||
|
inside the duration window, "Ufo" in the title, and `!is_live` rejects only *running streams*, not
|
||||||
|
live recordings. The variant blocklist (remix/cover/acoustic/…) now includes `live`. As with the
|
||||||
|
other rules it switches itself off when the searched title says live, so "Live Forever" and
|
||||||
|
deliberately requested live versions are unaffected.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,13 @@ On the deploy host the music lives at `/opt/navidrome/music`.
|
|||||||
next to a qualifier** ("… - 2020 Remaster") is dropped again — it sits on the release but never in
|
next to a qualifier** ("… - 2020 Remaster") is dropped again — it sits on the release but never in
|
||||||
the YouTube title, so requiring it would reject the correct upload. A title that IS a year ("1999",
|
the YouTube title, so requiring it would reject the correct upload. A title that IS a year ("1999",
|
||||||
"Berlin 2020") keeps it. Unit-tested in `backend/test/ytdlp.test.js`.
|
"Berlin 2020") keeps it. Unit-tested in `backend/test/ytdlp.test.js`.
|
||||||
|
- **Live recordings are excluded by title, not by `is_live`.** `!is_live` only means "not a running
|
||||||
|
stream" — a concert RECORDING passes it. On the artist's own channel nothing else distinguishes it
|
||||||
|
from the studio track, which is how "Dat Adam - Ufo Video Days 2015 Live" (channel `Dat Adam`,
|
||||||
|
209 s, title contains UFO) got saved as the album track. `buildVariantFilter` therefore carries a
|
||||||
|
`live` rule like remix/cover/…. It self-disables when the searched title itself says live, so
|
||||||
|
"Live Forever" or a deliberately requested live version still works — that check uses a `\blive\b`
|
||||||
|
regex (`skipRe`), because a plain substring test would let "deliver"/"delivery" switch the rule off.
|
||||||
- **Size cap:** `--max-filesize 30M` on every yt-dlp call; Mittelweg steps also cap at 2×
|
- **Size cap:** `--max-filesize 30M` on every yt-dlp call; Mittelweg steps also cap at 2×
|
||||||
expected duration. Prevents saving a 28-min video (52 MB) when the wrong result slips through.
|
expected duration. Prevents saving a 28-min video (52 MB) when the wrong result slips through.
|
||||||
- **Leading aesthetic characters:** artists often style their name with leading punctuation
|
- **Leading aesthetic characters:** artists often style their name with leading punctuation
|
||||||
|
|||||||
+10
-2
@@ -71,8 +71,16 @@ function buildVariantFilter(title) {
|
|||||||
{ key: 'cover', expr: 'title !~= (?i)\\bcover\\b' },
|
{ key: 'cover', expr: 'title !~= (?i)\\bcover\\b' },
|
||||||
{ key: 'acoustic', expr: 'title !~= (?i)\\bacoustic\\b' },
|
{ key: 'acoustic', expr: 'title !~= (?i)\\bacoustic\\b' },
|
||||||
{ key: 'instrumental', expr: 'title !~= (?i)\\binstrumental\\b' },
|
{ key: 'instrumental', expr: 'title !~= (?i)\\binstrumental\\b' },
|
||||||
|
// Konzertmitschnitte: `!is_live` heisst nur "laeuft nicht gerade als Stream" und laesst eine
|
||||||
|
// Live-AUFNAHME durch. Auf dem echten Kuenstlerkanal ist die sonst nicht vom Studiotrack zu
|
||||||
|
// unterscheiden — so landete "Dat Adam - Ufo Video Days 2015 Live" in der Bibliothek.
|
||||||
|
// skipRe statt includes(): "deliver"/"delivery" enthalten "live" als Teilstring und wuerden
|
||||||
|
// die Regel sonst faelschlich abschalten.
|
||||||
|
{ key: 'live', expr: 'title !~= (?i)\\blive\\b', skipRe: /\blive\b/i },
|
||||||
];
|
];
|
||||||
const parts = RULES.filter(r => !t.includes(r.key)).map(r => r.expr);
|
// Eine Regel entfaellt, wenn der GESUCHTE Titel das Wort selbst traegt — wer "Live Forever" oder
|
||||||
|
// bewusst eine Live-Version sucht, soll sie auch bekommen.
|
||||||
|
const parts = RULES.filter(r => r.skipRe ? !r.skipRe.test(t) : !t.includes(r.key)).map(r => r.expr);
|
||||||
return parts.length ? parts.join(' & ') : '';
|
return parts.length ? parts.join(' & ') : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,4 +453,4 @@ async function embedMetadata({ jobId, audioFile, dir, trackPrefix, safeTitle, ra
|
|||||||
return { success: true, dir };
|
return { success: true, dir };
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { downloadTrack, embedMetadata, buildTitleKeywords };
|
module.exports = { downloadTrack, embedMetadata, buildTitleKeywords, buildVariantFilter };
|
||||||
@@ -45,3 +45,34 @@ test('a year that IS the title stays required', () => {
|
|||||||
assert.equal(accepts('1999', 'Prince - 1999 (Official Music Video)'), true);
|
assert.equal(accepts('1999', 'Prince - 1999 (Official Music Video)'), true);
|
||||||
assert.equal(accepts('1999', 'Prince - 1984 (Official Music Video)'), false);
|
assert.equal(accepts('1999', 'Prince - 1984 (Official Music Video)'), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { buildVariantFilter } = require('../lib/ytdlp.js');
|
||||||
|
|
||||||
|
// Der Variantenfilter ist eine yt-dlp-Ausdruckskette "title !~= (?i)\bx\b & …".
|
||||||
|
// Diese Hilfsfunktion wertet sie so aus, wie yt-dlp es tut.
|
||||||
|
const variantAccepts = (searchTitle, candidate) => {
|
||||||
|
const f = buildVariantFilter(searchTitle);
|
||||||
|
if (!f) return true;
|
||||||
|
return f.split(' & ').every(part => {
|
||||||
|
const m = /^title !~= \(\?i\)(.+)$/.exec(part);
|
||||||
|
return !new RegExp(m[1], 'i').test(candidate);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
test('rejects a live recording — is_live only covers running streams', () => {
|
||||||
|
// Der echte Fall: auf dem Kuenstlerkanal, Dauer im Fenster, Titel passend.
|
||||||
|
assert.equal(variantAccepts('UFO', 'Dat Adam - Ufo Video Days 2015 Live'), false);
|
||||||
|
assert.equal(variantAccepts('UFO', 'DAT ADAM - 700 Main St | ROCK AM RING 2017 [LIVE]'), false);
|
||||||
|
assert.equal(variantAccepts('UFO', 'UFO'), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a deliberately live title keeps its live results', () => {
|
||||||
|
assert.equal(variantAccepts('Live Forever', 'Oasis - Live Forever'), true);
|
||||||
|
assert.equal(variantAccepts('Unplugged in New York (Live)', 'Nirvana - All Apologies (Live)'), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('"live" inside another word does not disable the rule', () => {
|
||||||
|
// "deliver" enthaelt "live" als Teilstring — die Regel muss trotzdem greifen.
|
||||||
|
assert.equal(variantAccepts('Deliver', 'Artist - Deliver (Live at Wembley)'), false);
|
||||||
|
assert.equal(variantAccepts('Deliver', 'Artist - Deliver'), true);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user