aboutsummaryrefslogtreecommitdiff
path: root/app/backends/audiocpp
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-28 00:59:47 -0400
committerhistoria <historiavg@proton.me>2026-08-28 00:59:47 -0400
commit975053f1789771ba5cb9dbe50ba7fe0aa396f0ab (patch)
tree9e249f025860a8a0bb89751159b412b3d41b25ba /app/backends/audiocpp
parent967b60342af11ada1cd6a27c935dd336523fb1cd (diff)
downloadtts-audiobook-generator-975053f1789771ba5cb9dbe50ba7fe0aa396f0ab.tar.gz
fix: silently failing whisper transcription
Diffstat (limited to 'app/backends/audiocpp')
-rw-r--r--app/backends/audiocpp/voices.py43
-rw-r--r--app/backends/audiocpp/wizard.py13
2 files changed, 42 insertions, 14 deletions
diff --git a/app/backends/audiocpp/voices.py b/app/backends/audiocpp/voices.py
index b26a0be..d19cb18 100644
--- a/app/backends/audiocpp/voices.py
+++ b/app/backends/audiocpp/voices.py
@@ -2,19 +2,22 @@
import argparse
from pathlib import Path
-from typing import Dict, List, Optional, Tuple
+from typing import Dict, Optional, Tuple
from backends.common import (PROMPT_TEXT_FILENAME, find_wav_files,
read_prompt_text)
-from converter.clients import (transcribe_reference_audio,
- whisper_backend_available)
+from converter.clients import (transcribe_reference_audio_detailed,
+ whisper_backend_problem)
def transcribe_wav_dir(wav_files: list, whisper_model: str,
cancel=None) -> Dict[str, str]:
- """Transcribe each wav file and return a mapping of stem -> transcript.
+ """Transcribe each wav file and return a stem -> transcript mapping.
CANCEL (a ``threading.Event``) is checked between files so the in-TUI
- task view can stop a long transcription early.
+ task view can stop a long transcription early. A wav that yields no
+ transcript keeps its '' entry, and the per-file warning says WHY
+ (backend missing, import broken, transcribe error, or no speech)
+ instead of a bare 'no transcript' line.
"""
transcripts: Dict[str, str] = {}
for wav_file in wav_files:
@@ -23,13 +26,15 @@ def transcribe_wav_dir(wav_files: list, whisper_model: str,
break
name = wav_file.stem
print(f"[INFO] Transcribing {wav_file.name} (voice '{name}')...")
- text = transcribe_reference_audio(str(wav_file), model_name=whisper_model)
+ text, reason = transcribe_reference_audio_detailed(
+ str(wav_file), model_name=whisper_model)
if text:
print(f"[OK] {name}: {text}")
else:
- print(f"[WARNING] No transcript for '{name}'; cloning works best "
- "with an accurate transcript — consider editing prompt_text "
- "by hand before starting the server")
+ print(f"[WARNING] No transcript for '{name}': {reason}")
+ print("[WARNING] Cloning works best with an accurate transcript; "
+ "consider editing prompt_text by hand before starting "
+ "the server")
transcripts[name] = text or ""
return transcripts
@@ -79,11 +84,10 @@ def _transcribe(args: argparse.Namespace, plan: Optional[dict],
"already transcribed, nothing new to transcribe")
return existing, False
- if whisper_backend_available() is None:
- print("[WARNING] Neither faster_whisper nor whisper was found, so "
- "reference .wav files cannot be transcribed automatically and "
- "every transcript will be empty.")
- print(" Install whisper (or faster_whisper) in your "
+ problem = whisper_backend_problem()
+ if problem is not None:
+ print(f"[WARNING] No usable Whisper backend: {problem}")
+ print(" Install faster-whisper (or openai-whisper) in your "
"audiobook environment to transcribe automatically; otherwise "
"transcripts must be added by hand (see the warning at the end).")
@@ -95,6 +99,17 @@ def _transcribe(args: argparse.Namespace, plan: Optional[dict],
else:
transcripts = transcribe_wav_dir(wav_files, args.whisper_model,
cancel=cancel)
+
+ # A failed transcription must never wipe a good transcript: keep the
+ # existing text for any voice whose new transcription came back empty.
+ # A blank prompt_text entry makes the server reject every clone request
+ # for that voice, so overwriting known-good text with '' is always a
+ # regression, whatever went wrong with the re-transcription.
+ for name, text in transcripts.items():
+ if not text.strip() and existing.get(name, "").strip():
+ transcripts[name] = existing[name]
+ print(f"[WARNING] Kept the existing transcript for '{name}' "
+ "(the new transcription came back empty)")
return transcripts, True
diff --git a/app/backends/audiocpp/wizard.py b/app/backends/audiocpp/wizard.py
index 91a8522..46b6638 100644
--- a/app/backends/audiocpp/wizard.py
+++ b/app/backends/audiocpp/wizard.py
@@ -600,6 +600,19 @@ def _execute_lanes(settings: dict,
transcripts, write_prompt = {}, False
state["transcripts"] = transcripts
state["write_prompt"] = write_prompt
+ # A blank transcript means the affected clone voices cannot work:
+ # transcript-conditioned families (Qwen3-TTS Base ICL) reject every
+ # request without it. Report the step as failed instead of a silent
+ # [OK]; the setup continues (warn-and-continue) and still writes
+ # server.json and prompt_text.
+ blank = sorted(name for name, text in transcripts.items()
+ if not text.strip())
+ if blank:
+ print("[ERROR] No transcript for: " + ", ".join(blank))
+ print("[ERROR] Those clone voices will NOT work until prompt_text "
+ "carries an accurate transcript for each one; see the "
+ "summary below for how to fix them by hand.")
+ return 1
return 0
def write(emit, cancel):