fix: explain why designed-voice benchmarks fail and stop batches on an unreachable service

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-09-29 16:03:23 +02:00
parent f1b12993a1
commit 4d7434f9af
4 changed files with 57 additions and 3 deletions

View File

@ -6,6 +6,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
--- ---
## [Unreleased] ## [Unreleased]
### Fixed
- **Voice benchmark failures now say why.** Designed voices are benchmarked through the Voice Design engine; when that service is down the row showed a bare "-" and the raw "Max retries exceeded" text. The error now names the service and the container to start, the Factor and WPM columns show `ERR` beside the time column, and a batch stops after three identical "unreachable" failures instead of retrying every remaining voice.
### Added ### Added
- `scripts/compare_magazines.py`: read-only comparison of two magazine folders (words, boxes, reading order, articles, quality numbers) for re-import checks. - `scripts/compare_magazines.py`: read-only comparison of two magazine folders (words, boxes, reading order, articles, quality numbers) for re-import checks.

View File

@ -310,7 +310,14 @@ def _voice_design_voice_request_audio(
# randomness. See the config-side comment for the full story. # randomness. See the config-side comment for the full story.
_apply_tts_extra_params(payload, settings, "voice_design_playback") _apply_tts_extra_params(payload, settings, "voice_design_playback")
_apply_voice_pinned_seed(payload, voice, settings) _apply_voice_pinned_seed(payload, voice, settings)
resp = _post_tts_with_fallback(f"{vd_url}/v1/audio/speech", payload, vd_hdrs, timeout=180) try:
resp = _post_tts_with_fallback(f"{vd_url}/v1/audio/speech", payload, vd_hdrs, timeout=180)
except (requests.ConnectionError, requests.Timeout) as e:
# The raw "Max retries exceeded with url ..." text told the user nothing about what to do.
raise RuntimeError(
f"Voice Design service unreachable at {vd_url} — start the "
f"'tts-faster-qwen3-tts-voicedesign' container (Engines tab) and retry"
) from e
resp.raise_for_status() resp.raise_for_status()
audio = resp.content audio = resp.content
if not audio: if not audio:

View File

@ -434,7 +434,9 @@ function voiceFactor(v) {
function fmtFactor(v) { function fmtFactor(v) {
const f = voiceFactor(v); const f = voiceFactor(v);
return f != null ? f.toFixed(2) + 'x' : '-'; if (f != null) return f.toFixed(2) + 'x';
const b = voiceBenchmark(v);
return b && !b.ok ? 'ERR' : '-';
} }
function fmtElapsed(v) { function fmtElapsed(v) {
@ -932,7 +934,8 @@ async function runVoiceBenchmarkBatch() {
}); });
setBenchmarkProgress(0, total, 'Starting benchmark...', stats); setBenchmarkProgress(0, total, 'Starting benchmark...', stats);
for (let i = 0; i < voices.length; i++) { let lastFailure = '', sameFailure = 0, aborted = false;
for (let i = 0; i < voices.length && !aborted; i++) {
const voice = voices[i]; const voice = voices[i];
const row = document.querySelector(`.vl-row[data-id="${CSS.escape(voice.id)}"]`); const row = document.querySelector(`.vl-row[data-id="${CSS.escape(voice.id)}"]`);
if (row) { if (row) {
@ -971,6 +974,14 @@ async function runVoiceBenchmarkBatch() {
} else { } else {
stats.errors++; stats.errors++;
stats.last = `${voice.id}: failed${b && b.error ? ' · ' + b.error : ''}`; stats.last = `${voice.id}: failed${b && b.error ? ' · ' + b.error : ''}`;
// The same unreachable service failing repeatedly means the rest will fail too.
const reason = b && b.error ? String(b.error) : '';
sameFailure = reason && reason === lastFailure ? sameFailure + 1 : 1;
lastFailure = reason;
if (/unreachable/i.test(reason) && sameFailure >= 3) {
toast(reason, 'error');
aborted = true;
}
} }
} }
} catch(e) { } catch(e) {

View File

@ -0,0 +1,34 @@
"""A designed voice's benchmark must explain an unreachable Voice Design service."""
import os
import tempfile
import unittest
from unittest import mock
os.environ.setdefault("CONFIG_DIR", tempfile.mkdtemp(prefix="ttvc-test-config-"))
import requests
from core import tts_helpers
class VoiceDesignUnreachableTest(unittest.TestCase):
def test_connection_error_becomes_actionable_message(self):
with mock.patch.object(tts_helpers, "_post_tts_with_fallback",
side_effect=requests.ConnectionError("Max retries exceeded")):
with self.assertRaises(RuntimeError) as ctx:
tts_helpers._voice_design_voice_request_audio(
"DE_F_Test", "Hallo", {"voice_design_url": "http://127.0.0.1:9"})
self.assertIn("Voice Design service unreachable", str(ctx.exception))
self.assertIn("tts-faster-qwen3-tts-voicedesign", str(ctx.exception))
def test_http_errors_are_not_masked(self):
resp = mock.Mock(status_code=500)
resp.raise_for_status.side_effect = requests.HTTPError("500")
with mock.patch.object(tts_helpers, "_post_tts_with_fallback", return_value=resp):
with self.assertRaises(requests.HTTPError):
tts_helpers._voice_design_voice_request_audio(
"DE_F_Test", "Hallo", {"voice_design_url": "http://127.0.0.1:9"})
if __name__ == "__main__":
unittest.main()