fix: tighten lyric placeholder detection

This commit is contained in:
2026-07-22 00:13:39 +08:00
parent b0d1b1766d
commit 2ba121b0c1
4 changed files with 17 additions and 11 deletions
@@ -127,6 +127,7 @@ public class LyricsService {
String normalized = lyrics.replace("\uFEFF", "").replace("\r", "");
int lines = 0;
int chars = 0;
int timedLines = 0;
for (String raw : normalized.split("\\n")) {
String line = raw.trim();
if (line.isEmpty() || line.matches("^\\[(?:ti|ar|al|by|re|ve|offset):.*$")) continue;
@@ -134,8 +135,10 @@ public class LyricsService {
String body = timed.matches() ? timed.group(1).trim() : line;
if (body.isEmpty() || META_LINE.matcher(body).matches()) continue;
lines++;
if (timed.matches()) timedLines++;
chars += body.replaceAll("\\s+", "").length();
}
if (timedLines > 0) return lines >= 3 && chars >= 12;
return lines >= 2 && chars >= 8;
}
private static byte[] readLimited(InputStream in) throws Exception { try (InputStream x = in; ByteArrayOutputStream b = new ByteArrayOutputStream()) { byte[] buf = new byte[8192]; int n, total = 0; while ((n = x.read(buf)) >= 0) { total += n; if (total > MAX_BYTES) throw new IllegalArgumentException("歌词响应过大"); b.write(buf, 0, n); } return b.toByteArray(); } }
@@ -30,7 +30,7 @@ class LibraryHealthServiceTest {
Path album = root.resolve("Artist").resolve("Album");
Files.createDirectories(album);
Files.write(album.resolve("01 - Song.mp3"), new byte[]{1, 2, 3});
Files.write(album.resolve("01 - Song.lrc"), "[00:01]first line\n[00:02]second line".getBytes(StandardCharsets.UTF_8));
Files.write(album.resolve("01 - Song.lrc"), "[00:01]first line\n[00:02]second line\n[00:03]third line".getBytes(StandardCharsets.UTF_8));
Files.write(album.resolve("orphan.lrc"), "[00:01]orphan".getBytes(StandardCharsets.UTF_8));
Files.write(album.resolve("cover.jpg"), new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF});
return album;
@@ -244,7 +244,7 @@ class LibraryHealthServiceTest {
Files.createDirectories(album);
// 大写 .LRC 侧车:既不应算缺歌词,也不应算孤立(与 orphan 判定保持一致)
Files.write(album.resolve("01 - Song.mp3"), new byte[]{1, 2, 3});
Files.write(album.resolve("01 - Song.LRC"), "[00:01]first line\n[00:02]second line".getBytes(StandardCharsets.UTF_8));
Files.write(album.resolve("01 - Song.LRC"), "[00:01]first line\n[00:02]second line\n[00:03]third line".getBytes(StandardCharsets.UTF_8));
LibraryHealthService svc = newService(root);
LibraryHealthReport report = svc.scan(false);
@@ -22,8 +22,8 @@ class LyricsServiceTest {
@Test
void selectsExactArtistInsteadOfFirstCoverCandidate() throws Exception {
String json = "["
+ "{\"title\":\"夜曲 (网易云音乐)\",\"artist\":\"Xai小爱\",\"lyrics\":\"[00:01]翻唱第一句\\n[00:02]翻唱第二句\",\"type\":\"lrc\",\"source\":\"netease\"},"
+ "{\"title\":\"夜曲 (酷狗音乐)\",\"artist\":\"周杰伦\",\"lyrics\":\"[00:01]一群嗜血的蚂蚁\\n[00:02]被腐肉所吸引\",\"type\":\"lrc\",\"source\":\"kugou\",\"isComplete\":true}]";
+ "{\"title\":\"夜曲 (网易云音乐)\",\"artist\":\"Xai小爱\",\"lyrics\":\"[00:01]翻唱第一句\\n[00:02]翻唱第二句\\n[00:03]翻唱第三句\",\"type\":\"lrc\",\"source\":\"netease\"},"
+ "{\"title\":\"夜曲 (酷狗音乐)\",\"artist\":\"周杰伦\",\"lyrics\":\"[00:01]一群嗜血的蚂蚁\\n[00:02]被腐肉所吸引\\n[00:03]我面无表情\",\"type\":\"lrc\",\"source\":\"kugou\",\"isComplete\":true}]";
start(json, 200);
LyricsService.Candidate result = new LyricsService().lookup("夜曲", "周杰伦");
assertNotNull(result);
@@ -45,7 +45,7 @@ class LyricsServiceTest {
@Test
void acceptsTimestampedLyrics() {
assertTrue(LyricsService.hasMeaningfulLyrics("[00:01.00]第一句歌词\n[00:03.00]第二句歌词\n"));
assertTrue(LyricsService.hasMeaningfulLyrics("[00:01.00]第一句歌词\n[00:03.00]第二句歌词\n[00:05.00]第三句歌词\n"));
}
private void start(String body, int status) throws Exception {
+9 -6
View File
@@ -14,13 +14,15 @@ text=open(sys.argv[1],encoding='utf-8-sig',errors='replace').read().replace('\r'
meta=re.compile(r'^\[(?:ti|ar|al|by|re|ve|offset):',re.I)
timed=re.compile(r'^\s*\[(?:\d{1,2}:)?\d{1,2}:\d{2}(?:\.\d+)?\]\s*(.*)$')
labels=re.compile(r'^(?:作词|作曲|词曲|编曲|制作人?|混音(?:师|助理)?|录音(?:师)?|工程师|弦乐|鼓和钢琴|电贝斯|额外编程|数字编辑|原唱|演唱|出品|发行|版权|produced|written|composer|arranged|lyrics?|music|artist|title)\s*[:\-].*$',re.I)
body=[]
body=[]; timed_lines=0
for raw in text.split('\n'):
line=raw.strip()
if not line or meta.match(line): continue
match=timed.match(line); line=(match.group(1).strip() if match else line)
if line and not labels.match(line): body.append(line)
sys.exit(0 if len(body)>=2 and len(''.join(body).replace(' ',''))>=8 else 1)
if line and not labels.match(line):
body.append(line); timed_lines += 1 if match else 0
valid=(len(body)>=3 and len(''.join(body).replace(' ',''))>=12) if timed_lines else (len(body)>=2 and len(''.join(body).replace(' ',''))>=8)
sys.exit(0 if valid else 1)
PY
then
echo "SKIP $audio"
@@ -42,13 +44,14 @@ def meaningful(text):
meta=re.compile(r'^\[(?:ti|ar|al|by|re|ve|offset):',re.I)
timed=re.compile(r'^\s*\[(?:\d{1,2}:)?\d{1,2}:\d{2}(?:\.\d+)?\]\s*(.*)$')
labels=re.compile(r'^(?:作词|作曲|词曲|编曲|制作人?|混音(?:师|助理)?|录音(?:师)?|工程师|弦乐|鼓和钢琴|电贝斯|额外编程|数字编辑|原唱|演唱|出品|发行|版权|produced|written|composer|arranged|lyrics?|music|artist|title)\s*[:\-].*$',re.I)
body=[]
body=[]; timed_lines=0
for raw in text.replace('\r','').split('\n'):
line=raw.strip()
if not line or meta.match(line): continue
match=timed.match(line); line=(match.group(1).strip() if match else line)
if line and not labels.match(line): body.append(line)
return len(body)>=2 and len(''.join(body).replace(' ',''))>=8
if line and not labels.match(line):
body.append(line); timed_lines += 1 if match else 0
return (len(body)>=3 and len(''.join(body).replace(' ',''))>=12) if timed_lines else (len(body)>=2 and len(''.join(body).replace(' ',''))>=8)
best=None
for x in rows if isinstance(rows,list) else []:
l=x.get('lyrics','').strip(); ct=x.get('title',''); ca=x.get('artist','')