align LRC from ready text.txt via stable-ts and auto-detect language

- add align_text_to_audio(): forced alignment without ASR, fast
- add _StableTSModels cache; detect language via stable-ts when language=None
- LRC stage: use existing text.txt, copy it if already LRC, fall back to ASR only
- filter junk lyrics from tags and ASR hallucinations (promo phrases, tics)
- mark empty ASR results with metadata.empty_text and skip them on resume
- warn in GUI when LRC is run with transcription disabled
- add mark_no_speech.py one-shot script for existing tracks
This commit is contained in:
jadssss
2026-11-02 01:30:30 +03:00
parent a2e0070ace
commit f023653c71
7 changed files with 346 additions and 26 deletions

View File

@@ -70,6 +70,35 @@ def is_lrc(content: str) -> bool:
# ---------------------------------------------------------------- read embedded
_JUNK_LYRIC_MARKERS = (
"created with",
"russian-area.de",
"http://", "https://",
"www.",
"mp3tag",
"tag&rename",
"foobar2000",
"itunes",
)
def _is_junk_lyrics(text: str) -> bool:
"""Reject tag values that are really URL/signature junk, not lyrics."""
if not text or len(text.strip()) < 3:
return True
low = text.lower()
if any(m in low for m in _JUNK_LYRIC_MARKERS):
return True
# A "lyrics" blob that is just an email or URL — reject.
import re
stripped = re.sub(r"\s+", "", low)
if re.fullmatch(r"[a-z0-9.\-_/]+", stripped) and (
"." in stripped or "/" in stripped
):
return True
return False
def read_embedded_lyrics(audio_path: Path) -> Optional[str]:
"""Return lyrics stored in the file's tags, or None.
@@ -78,14 +107,18 @@ def read_embedded_lyrics(audio_path: Path) -> Optional[str]:
suffix = audio_path.suffix.lower()
try:
if suffix in (".mp3", ".mp2"):
return _read_mp3(audio_path)
if suffix in (".flac", ".ogg", ".opus"):
return _read_vorbis(audio_path)
if suffix in (".m4a", ".mp4", ".m4b", ".aac"):
return _read_mp4(audio_path)
except Exception: # noqa: BLE001 — never break the pipeline on tag issues
text = _read_mp3(audio_path)
elif suffix in (".flac", ".ogg", ".opus"):
text = _read_vorbis(audio_path)
elif suffix in (".m4a", ".mp4", ".m4b", ".aac"):
text = _read_mp4(audio_path)
else:
return None
except Exception: # noqa: BLE001
return None
return None
if text and _is_junk_lyrics(text):
return None
return text
def _read_mp3(path: Path) -> Optional[str]: