improve GUI layout and LRC stage controls
- make the main page scrollable and keep the console reachable on small screens - add fullscreen toggle (button + F11, Escape to exit) - add enable switches for LRC generation/embedding and skip ready text.lrc - sanitize multi-line ASR text before writing LRC timestamps - emit LRC/embed progress once per file instead of twice - increase progress-bar contrast
This commit is contained in:
@@ -66,6 +66,9 @@ class LyricsConfig:
|
||||
|
||||
@dataclass
|
||||
class LrcConfig:
|
||||
enabled: bool = True # LRC generation stage
|
||||
embed_enabled: bool = True # LRC embedding stage
|
||||
skip_existing: bool = True # skip tracks with a ready text.lrc
|
||||
language: Optional[str] = "ru"
|
||||
align_model: Optional[str] = None
|
||||
device: str = "auto"
|
||||
|
||||
@@ -37,6 +37,10 @@ def segments_to_lrc(segments: list[dict]) -> str:
|
||||
text = (seg.get("text") or "").strip()
|
||||
if not text:
|
||||
continue
|
||||
# WhisperX can return multi-line text on long segments. An LRC line
|
||||
# must be a single line: any newline would produce a broken line
|
||||
# without a timestamp. Collapse all whitespace into single spaces.
|
||||
text = re.sub(r"\s+", " ", text)
|
||||
start = float(seg.get("start", 0.0))
|
||||
lines.append(f"{format_timestamp(start)}{text}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
@@ -11,7 +11,9 @@ from .config import PipelineConfig
|
||||
from .convert import convert_to_wav
|
||||
from .lrc import embed_lrc, is_lrc, read_embedded_lyrics, segments_to_lrc
|
||||
from .metadata import read_metadata
|
||||
from .pipeline import FileResult, _save_unrecognized, collect_audio_files
|
||||
from .pipeline import (
|
||||
FileResult, _artifact_belongs_to, _save_unrecognized, collect_audio_files,
|
||||
)
|
||||
from .transcribe import transcribe_aligned
|
||||
|
||||
|
||||
@@ -45,6 +47,20 @@ def _pick_audio_source(
|
||||
return target
|
||||
|
||||
|
||||
def _ready_lrc(track_out: Path, source: Path) -> Optional[str]:
|
||||
"""Return existing valid LRC text for this exact source track, if any."""
|
||||
if not _artifact_belongs_to(track_out, source):
|
||||
return None
|
||||
lrc_path = track_out / "text.lrc"
|
||||
if not lrc_path.is_file():
|
||||
return None
|
||||
try:
|
||||
content = lrc_path.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
return None
|
||||
return content if content.strip() and is_lrc(content) else None
|
||||
|
||||
|
||||
def run_lrc_pipeline(
|
||||
config: PipelineConfig,
|
||||
log: LogFn = print,
|
||||
@@ -58,6 +74,10 @@ def run_lrc_pipeline(
|
||||
output_dir = Path(config.output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if not config.lrc.enabled:
|
||||
log("LRC: генерация отключена — пропуск этапа")
|
||||
return []
|
||||
|
||||
files = collect_audio_files(input_dir, config.recursive)
|
||||
if not files:
|
||||
log("Аудиофайлы не найдены.")
|
||||
@@ -69,6 +89,8 @@ def run_lrc_pipeline(
|
||||
|
||||
results: list[FileResult] = []
|
||||
total = len(files)
|
||||
if on_progress:
|
||||
on_progress("lrc", 0, total)
|
||||
if on_overall_progress:
|
||||
on_overall_progress(0, total)
|
||||
|
||||
@@ -76,8 +98,6 @@ def run_lrc_pipeline(
|
||||
if stop_event is not None and stop_event.is_set():
|
||||
log("⏹ Отменено")
|
||||
break
|
||||
if on_progress:
|
||||
on_progress("lrc", idx - 1, total)
|
||||
log(f"\n▶ [{idx}/{total}] {f.name}")
|
||||
|
||||
track_out = output_dir / f.stem
|
||||
@@ -85,6 +105,17 @@ def run_lrc_pipeline(
|
||||
lrc_path = track_out / "text.lrc"
|
||||
error: Optional[str] = None
|
||||
|
||||
if config.lrc.skip_existing:
|
||||
ready = _ready_lrc(track_out, f)
|
||||
if ready is not None:
|
||||
log(" ⊘ text.lrc уже готов — пропуск")
|
||||
results.append(FileResult(f, None, track_out, None, "existing+lrc"))
|
||||
if on_progress:
|
||||
on_progress("lrc", idx, total)
|
||||
if on_overall_progress:
|
||||
on_overall_progress(idx, total)
|
||||
continue
|
||||
|
||||
tmp = Path(tempfile.mkdtemp(prefix=f"at_lrc_{f.stem}_"))
|
||||
try:
|
||||
embedded = read_embedded_lyrics(f) if config.lyrics.use_embedded_lyrics else None
|
||||
@@ -151,6 +182,10 @@ def run_embed_pipeline(
|
||||
output_dir = Path(config.output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if not config.lrc.embed_enabled:
|
||||
log("LRC embed: встраивание отключено — пропуск этапа")
|
||||
return []
|
||||
|
||||
files = collect_audio_files(input_dir, config.recursive)
|
||||
if not files:
|
||||
log("Аудиофайлы не найдены.")
|
||||
@@ -161,6 +196,8 @@ def run_embed_pipeline(
|
||||
|
||||
results: list[FileResult] = []
|
||||
total = len(files)
|
||||
if on_progress:
|
||||
on_progress("embed", 0, total)
|
||||
if on_overall_progress:
|
||||
on_overall_progress(0, total)
|
||||
|
||||
@@ -168,8 +205,6 @@ def run_embed_pipeline(
|
||||
if stop_event is not None and stop_event.is_set():
|
||||
log("⏹ Отменено")
|
||||
break
|
||||
if on_progress:
|
||||
on_progress("embed", idx - 1, total)
|
||||
|
||||
lrc_path = output_dir / f.stem / "text.lrc"
|
||||
error: Optional[str] = None
|
||||
|
||||
Reference in New Issue
Block a user