tts-voice-creator-clone-and.../CHANGELOG.md
mARTin-B78 2479617964 fix: run blocking requests/sleep in async routes via to_thread
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 00:40:34 +02:00

596 KiB
Raw Blame History

Changelog

All notable changes to TTS Voice Creator — Clone and Design are documented here.
Follows Keep a Changelog · versioned with Semantic Versioning.


Unreleased

Added

  • Conversation Playground: a Thinking on/off switch next to the Language Model (default off, setting conversation_thinking). Off sends chat_template_kwargs.enable_thinking=false (plus think:false for Ollama) with each reply request and never touches the system prompt; the empty-reply error now says to turn Thinking off.

Fixed

  • Async routes no longer block the event loop on synchronous HTTP: seed proxy/samples, text refine/persona rewrite, voice matching (its retry back-off is now asyncio.sleep), character deep analysis, character image-from-URL, LLM/engine model listing, source audio import/proxy and ElevenLabs shared voices now run requests through asyncio.to_thread (same timeouts and responses).

Changed

  • Conversation Playground: the Language Model base URL is now picked from a dropdown of the language models configured under Engines (Docker LLM containers incl. saved URL overrides, and custom LLM cards; running/stopped marked), with a "Custom…" option that reveals the free-text URL. Selecting an entry saves conv_llm_url and fetches the models.
  • Playwright specs now import test from tests/_guard.js, whose auto fixture aborts and fails any unmocked non-GET request to /api/** so a spec can no longer modify live settings. See architecture/qa.md.

1.60.0 — 2026-10-04

Added

  • npm run qa: central QA command (scripts/qa.mjs) with timed stages (lint, python/node unit tests, Playwright, security scans, bundle freshness) and an opt-in SonarQube stage (--sonar, token from the environment only). See architecture/qa.md.

Changed

  • npm run qa: pyunit gets a fresh temp HOME/CONFIG_DIR/TMPDIR per run (cleaned afterwards); --sonar stage also produces Python coverage.xml when coverage is installed (else SKIPPED).
  • guided_installer.py: command execution extracted into a testable run_command helper (lowers cognitive complexity, python:S3776); no behaviour change.
  • Voice list rows no longer show the "Source unknown" badge (it crowded the play buttons). The badge stays in the voice inspector, next to the Source & consent panel.

Fixed

  • Magazine specs no longer touch the live settings. The export-provenance checkboxes read and saved /api/settings on the real app (a run left the default off, failing the next one); the spec now mocks it. The hostile deep-link spec waits for the lazily loaded magazine module before calling the parser.
  • Duplicate German i18n keys and dead frontend code. Removed 14 shadowed duplicate keys in i18n.js (effective translations unchanged), the unreachable body of audiobookShowPreview, and two identical-branch ternaries.
  • Security hardening (Sonar findings). The Docker installer downloads get-docker.sh into a private mkdtemp dir (not a predictable /tmp path) and removes it afterwards; the OMDb poster lookup uses HTTPS; the fish.audio cache key marks MD5 as usedforsecurity=False.
  • Background jobs could be garbage-collected mid-run. Fire-and-forget asyncio.create_task calls (magazine, conversation, tts, stt, library, sources) now go through core.tasks.spawn, which keeps a strong reference until the task ends and logs failures by exception class only.
  • Voice archetype gallery broke npm run check:frontend: its say helper collided with magazine.js; renamed to archSay.
  • Export failed with HTTP 500 when provenance tagging failed (EPUB/ZIP/article MP3 with audio ffmpeg cannot tag); it now ships the untagged file and logs a warning.
  • Archetype language filter accepted only full names. GET /api/archetypes?language=de returned 0 items; it now accepts ISO codes and names, case-insensitive (de, DE, German); unknown values return an empty list.
  • LibriVox search returned 502 "unavailable" after 20-30 s. Cause: the LibriVox API is erratic (10-40 s for any request, measured from host and container; egress/DNS/TLS fine) and the old call asked for extended=1 (all chapters of 15 books) with a 30 s timeout, mapping every failure to one generic message. Now the list is light (no extended), the first chapter is fetched per book on demand (GET /api/voice-sources/librivox/book/{id}, UI "Load preview" / on import), timeout 25 s with one retry and backoff, failures cached 30 s, distinct messages (timeout / unreachable / HTTP status / unreadable) and a privacy-safe log line (exception class and query length only). Host/redirect rules unchanged.
  • export_provenance_default missing from settings defaults. It is now in the defaults (true), so GET /api/settings exposes it.

Added

  • Voice archetype gallery (own, German-first). "Voice gallery" card in Design a Voice: 40 hand-authored named voices (mostly German, a few English) in 7 categories, each a free-text Qwen3 VoiceDesign description with facets (gender, age, pitch, accent, tone) from the design-chip taxonomy. Search, facet chips, category list, favourites (browser storage), 24 cards per page rendered only after opening, labelled Preview (aria-live status) and "Use voice" (then "Open in Design" prefills the editor). GET /api/archetypes, POST /api/archetypes/{id}/preview (cached by sha256 of instruct, language, engine and seed 42; timeouts, blank-audio guard) and POST /api/archetypes/{id}/use (saves voice archetype_<id>, origin designed, provenance basis unknown, source archetype/<id>, note "synthetic designed voice"; idempotent). Own catalog, nothing copied from other projects; no network. Optional scripts/render_archetype_previews.py pre-renders all previews. Tests: tests/test_voice_archetypes.py, tests/archetype-gallery.spec.js. See architecture/voice-archetypes.md.
  • Audiobook emotion tests. Playwright tests/audiobook-emotion.spec.js (mocked routes) covers span-cast emotion -> instruct in /api/tts-preview (dialogue only, English tag, narration empty) and the line cache key; tests/test_tts_instruct_payload.py covers the instruct passthrough.
  • LibriVox and Wikimedia Commons voice sources. New tab in Get Voices Online searches the official LibriVox feed and the Commons MediaWiki API through GET /api/voice-sources/{librivox|wikimedia}?q=&lang= (fixed hosts, query cap 80, 10 results, 30 s timeout, 10 min cache, 1 s throttle, descriptive User-Agent, result URLs host-checked). Results show licence and a preview; "Import clip" trims (start/length) and stores provenance (LibriVox: public domain + check-the-reader note; Wikimedia: licence + attribution). archive.org and upload.wikimedia.org added to the audio proxy allowlist. Tests: tests/test_voice_sources_new.py, tests/voice-sources-new.spec.js. See architecture/voice-sources.md.
  • Provenance tags on exported audio. Magazine MP3 downloads, audio.zip and the MP3s inside the EPUB get ID3 tags (synthetic_speech=true, generator, generator_version, generated_date, comment; voice name only on opt-in) via one ffmpeg stream copy of a temp copy; stored audio is untouched. Per-export checkboxes on the magazine export row ("Mark as synthetic speech", "include voice name"), default from new setting export_provenance_default (on). POST /api/export/provenance and python -m core.export_provenance file.mp3 read the tags back. Inaudible AudioSeal watermarking is researched (MIT code and weights, torch needed, weak message recovery after MP3/AAC) but only a stub watermark(path) that reports unavailable. Spoken disclosure is not implemented yet. Tests: tests/test_watermark_tags.py, tests/magazine.spec.js. See architecture/watermarking.md.
  • Inline text tags for TTS (setting inline_tags_enabled, default OFF). [pause 500ms]/[pause 1.2s] (max 10 s each, 60 s per text), [spell]USA[/spell] (German letter names for German text), [slow]..[/slow] / [fast]..[/fast] (0.85 / 1.15 via ffmpeg atempo, innermost wins, unclosed ends at paragraph). Known control tags (e.g. [laugh]) are stripped, never spoken; [1] and [sic] stay. Rendered in POST /api/tts-preview (also used by audiobook line previews), /v1/audio/speech (non-streaming), /speak and the MCP speak tool through one shared ssml_lite.render_with_tags; stripped only (never spoken) in streaming, spoken chat replies and magazine MP3 (timings stay exact). Settings -> General switch (accessible, DE/EN) and a syntax hint under the Try It Out and Read Aloud text boxes. Tests: tests/test_ssml_lite.py, tests/test_inline_tags_paths.py, tests/inline-tags.spec.js.
  • Optional vocal isolation for URL/YouTube imports (default OFF). POST /api/isolate-vocals {id} runs the Demucs CLI (htdemucs, two stems) on a registered temp audio and registers the vocals as a new id; the original stays valid. Needs VOCAL_ISOLATION=1, a demucs binary (or VOCAL_ISOLATION_CMD) and the weights already on disk; the app never downloads anything and nothing is added to the main image. Limits: 15 min input, 15 min runtime (VOCAL_ISOLATION_MAX_SECONDS/_TIMEOUT), one job at a time, cancelled on client disconnect, temp files always removed. GET /api/version reports features.vocal_isolation; the "Remove background music" button (clone page URL card and voice library add-URL) appears only then, with an aria-live status and a play original / play without music toggle. Tests: tests/test_vocal_isolation.py, tests/vocal-isolation.spec.js. See architecture/vocal-isolation.md.
  • Voice export/import bundles. GET /api/voices/{id}/export streams a <id>.voice.zip (audio, reference.txt, meta.json with seed, design prompt, rating/tags, provenance, audio_problems; optional picture and consent recording; manifest.json with format version, app version and sha256 per file; no temp files). POST /api/voices/import validates at the boundary: 150 MB upload cap, fixed flat member names only (zip-slip/path traversal rejected), per-file, total and compression-ratio caps, hashes verified, audio decoded, meta and provenance re-validated; a taken name gets a _2 suffix, an identical voice is a no-op, nothing is overwritten. Voice library toolbar: "Export selected" and "Import bundle" (44px). Tests: tests/test_voice_bundle.py, tests/voice-bundle.spec.js. See architecture/voice-bundle.md.
  • Consent/provenance field per voice. Each voice gets provenance in its .meta.json (basis: own_voice, consented, licensed, public_domain or unknown; source, licence, note, optional recorded_consent, set_at). Existing voices count as unknown; quick-import-voice prefills the source URL (basis stays unknown). New PUT /api/voices/{id}/provenance (enum, length caps, control characters stripped, http(s) URLs only, no credentials) and POST/GET /api/voices/{id}/consent-recording (audio only, 10 MB cap, stored as <voice>.consent, moves/deletes with the voice). The voice inspector has a "Source & consent" section (typed note or MediaRecorder recording) and voices without a recorded basis show a "Source unknown" badge. Generation is not blocked. Data stays local. Tests: tests/test_voice_provenance.py, tests/voice-provenance.spec.js.
  • Seed Finder: Auto-pick best. POST /api/seed-autopick {voice, text, seeds|count} (default 8, max 16) generates candidates sequentially through the existing temporary-voice path (voices.json is always restored), scores each 0-100 (ASR word accuracy, loudness/clipping, audio_problems) and returns all sub-scores plus the winner. Degrades to loudness/problems when STT is unavailable. The UI highlights the winner; saving stays a click on "Use seed". Tests: tests/test_seed_autopick.py, tests/seed-autopick.spec.js.
  • Voice Design: attribute chips. Toggle chips (gender, age, pitch, pace, accent/region, tone/timbre, emotion; German + English labels) above the design prompt add or remove only their own phrase in the free-text description. The textarea stays fully free-form. Test: tests/design-chips.spec.js.
  • Voice health: timestamped audio problems. core.audio.audio_problems flags long interior silence, clipping and sudden level jumps (problems list with kind, start_s, end_s, detail, plus one warning per kind) in the route-test voice health; existing keys unchanged. Computed on demand and cached by file mtime, not in the voice list. Shown in the routing test result and in the Try-out voice panel (new GET /api/voices/{id}/health); imports store the result in meta (audio_problems). Tests: tests/test_audio_problems.py, tests/voice-health.spec.js.

Docs

  • Audiobook emotions section in Manual.md (filling, editing, voice types that honour them, limits) and an Emotions section in architecture/audiobook-attribution.md.

Changed

  • Magazine interviews: journalist/guest voices. Interview labels such as c't: (also c’t, ct, OCR Cc't), Redaktion, Interviewer, Frage are the interviewing side and are read in the narrator voice; every other recurring label is a guest and gets the second voice. Unlabelled blocks continue the current speaker's turn; headings, decks and bylines stay narrator. Detection stays strict (label starts a block and recurs at least twice). Audio-timing records now store each segment's voice and speaker. Tests: tests/test_magazine.py (InterviewJournalistTests).

1.59.0 — 2026-10-04

Changed

  • Characters: global cast view in the sidebar, per-book characters inside Studio. Library > Characters / Cast is now titled "All characters (all books)": one collapsible band per book (heading, per-book count, aria-expanded), first book open and the rest closed and not rendered until opened (DOM budget), and a Search all books field. Studio > Voices lists only the book picked in Source, under a "Book: