diff --git a/backend/src/main/java/com/music/service/LyricsService.java b/backend/src/main/java/com/music/service/LyricsService.java index 03178ed..ebe8edf 100644 --- a/backend/src/main/java/com/music/service/LyricsService.java +++ b/backend/src/main/java/com/music/service/LyricsService.java @@ -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(); } } diff --git a/backend/src/test/java/com/music/service/LibraryHealthServiceTest.java b/backend/src/test/java/com/music/service/LibraryHealthServiceTest.java index 2a819dc..9c81960 100644 --- a/backend/src/test/java/com/music/service/LibraryHealthServiceTest.java +++ b/backend/src/test/java/com/music/service/LibraryHealthServiceTest.java @@ -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); diff --git a/backend/src/test/java/com/music/service/LyricsServiceTest.java b/backend/src/test/java/com/music/service/LyricsServiceTest.java index aabed2a..a7a2848 100644 --- a/backend/src/test/java/com/music/service/LyricsServiceTest.java +++ b/backend/src/test/java/com/music/service/LyricsServiceTest.java @@ -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 { diff --git a/scripts/backfill-lyrics.sh b/scripts/backfill-lyrics.sh index 97270e7..e0041f8 100755 --- a/scripts/backfill-lyrics.sh +++ b/scripts/backfill-lyrics.sh @@ -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','')