tts-voice-creator-clone-and.../CHANGELOG.md
mARTin-B78 e999a8f38b fix(voices): keep per-model speaker embeddings with their voice package
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 05:40:45 +02:00

604 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

Fixed

  • Per-model speaker embeddings stay with their voice: the TTS engine's <name>.1.7b.pt / <name>.0.6b.pt files that sit beside the WAV are now part of the voice package, so hiding, unhiding, trashing, deleting and renaming a voice moves, removes or renames them too instead of leaving orphans in active_voices.
  • Custom TTS engines no longer count as available just because the app's own voice library is non-empty: availability follows the engine's health, so a stopped VoiceDesign/CustomVoice container is no longer listed.

1.61.0 — 2026-10-11

Added

  • My Voices list header: a sort header (select-all/none checkbox with a partial-selection state, then sortable Name / Lang / Gender / Speed / Rating with the active column highlighted) sits directly above the voice list, and the filter row starts with a filter icon. The full table header still takes over in table view.
  • QA: Python tests in a throwaway container (npm run qa -- --only pyunit --container): runs the unit tests on production's Python 3.11 in a no-network, read-only container built from the app image on a writable throwaway copy of the read-only mounted repo (docker/Dockerfile.test adds only httpx2 and nodejs); opt-in, SKIPPED when docker or the image is missing.
  • Sonar triage --drain: works through open agent issues one at a time, fires the multica webhook, waits for the agent's answer on GitHub and resends once if nothing arrives; stops on a stalled issue.
  • Sonar triage can fire a multica webhook autopilot after each GitHub issue (URL from ~/.config/multica-webhook or MULTICA_WEBHOOK_URL, passed to curl on stdin only); the multica CLI stays as fallback.

Changed

  • My Voices bottom bar simplified: Refresh, Sync and Import bundle stay as icon buttons (labels as tooltips / for screen readers) so the whole bar fits one row; every selection-based action now lives in one Actions (N) menu grouped Edit / Audio / Share / Danger (disabled until voices are checked; closes on pick, outside click or Esc).
  • My Voices preview text / synth source ("Synth uses" Preview text / Reference transcript + sample sentence) moved from its own always-visible card into a drawer behind an icon in the list's top bar (state remembered; the icon tooltip shows the active mode). In the stacked/mobile layout the voice list now has a bounded height and the page scrolls, so opening both drawers no longer squeezes the rows or clips the bottom toolbar.
  • My Voices filter area redesigned: one search row with a Filters button (badge = number of active filters) plus group-by-tag / table-view buttons. Language, gender, type, tag, rating, group, sort field/order, "Show disabled", "Clear filters" and the Groups accordion moved into a labelled, collapsible panel (remembered per browser); the sort header above the list stays on one line.
  • My Voices list: the Groups chip list is a collapsed accordion (summary shows the group count and the active group, with "Show all"); the bulk actions (Set tag, Set source, Hide, Unhide, Rate, Delete) moved from the selection bar into the bottom toolbar; every selection-based button (Export selected, Copy selected, Precompute, Calc dB, Benchmark, Batch seeds, Verify, Set tag, Set source, Hide, Unhide, Rate, Delete) is disabled, with a tooltip, until voices are checked; only Refresh, Sync and Import bundle are always enabled; the redundant All / None buttons are gone (use the header checkbox).
  • QA: the Sonar triage script groups medium/large findings per rule instead of per (rule, file): one GitHub issue per rule with a file/line table (max 40 rows, 'and N more'; split into 'part n/m' beyond 40 locations); dedupe marker still carries all Sonar keys.
  • Conversation Playground: the round microphone button is red (pressed, "Stop microphone") while the mic is hot (live or recording) and blue ("Start microphone") when off; it returns to blue on stop, auto-stop and mic errors. The pulse ring only animates when reduced motion is not requested.
  • Conversation Playground: the metrics (Token Usage and STT / LLM first token / TTS first audio TTFB charts) now sit in the right-hand Latency column below Turn history instead of a second "Metrics" tab; the Conversation | Metrics tablist is removed. Charts gain axis titles ("Time (ms)", "Time"), a legend and three y-ticks.

Fixed

  • Status-bar TTS picker did not drive My Voices "Play": choosing an engine in the status bar updated the preview/reader/rehearser selectors but not the My Voices (library) one, so Play kept using the previously selected engine (e.g. a stopped VoiceClone) and failed with a 500. It now also sets the library and Speech-to-Text-test selectors, and the toast names the engine.
  • Unhidden voices fell back to the default voice: unhiding moved the files but never registered the voice in the TTS engine's voices.json (only saving a transcript or importing did), so the engine logged "Voice … not configured; falling back to default voice" and spoke with another voice. Unhiding now publishes a missing entry from the voice's reference transcript; existing entries (and their cached speaker embeddings) are left alone.
  • "No TTS backend" although engines show Connected: the TTS health probe appended /health to the card URL (…:4000/v1/health = 404), so every custom TTS engine behind a LiteLLM gateway was judged unreachable. Card URLs ending in /v1 are now probed from the server root, and for engines behind a LiteLLM gateway the probe asks the gateway (/model/info, cached 30 s) for the selected model's upstream and checks that engine itself, so a stopped VoiceClone container is shown as unavailable even though the gateway is up (the gateway's unauthenticated /health/liveliness is only the fallback); and "Use as TTS / STT" refreshes the status bar immediately.
  • Copy buttons did nothing (My Voices "Copy selected", Connect "Copy active voices", engine/voice-inspector/voice-source copy): copyText was no longer exported from utils.js after the module scoping, so the click threw ReferenceError. It is exported again, now reports whether the copy succeeded, and "Copy selected" shows a copy icon, a "N voice names copied to clipboard" message, and a dialog with the pre-selected list when the async clipboard API is unavailable or refused (plain-HTTP LAN origin, embedded webviews), since the legacy fallback cannot be verified.
  • Sonar: removed 43 small Sonar findings in 20 files: unused variables and dead helper functions (frontend), unused locals and a misread comment (backend); behaviour unchanged. 7 further small findings were deliberately left (used via globals, or entangled with other dead code).
  • Conversation Playground: Turn history rows (#n, time, error rows) were in the DOM but squeezed to zero height by the flex column once charts filled the Latency column; the list no longer shrinks.
  • Conversation Playground: the metrics charts render at the column width (re-rendered on resize) so axis, tick and legend text is 11px instead of ~8px; a single point is centred with a larger dot.

Added

  • QA: scripts/sonar_triage.mjs (npm run sonar:triage), a local SonarQube triage loop (dry-run default): classifies findings small/medium/large/skipped via allow/skip lists, files one GitHub issue per rule (file/line table, max 40 locations, larger rules split into 'part n/m') with a dedupe marker, hands them to the multica sonar-fixer agent, and can auto-fix small ones as a local commit. See architecture/sonar-triage.md.
  • Conversation Playground: Metrics view (Conversation | Metrics tabs) with Pipecat-style TTFB line charts for STT, LLM first token and TTS, plus session Token Usage (prompt/completion/total, "n/a" when the backend reports none). The stream now requests stream_options.include_usage (dropped and retried on 400/422) and forwards usage in the final stats event; the TTS value is now the time to the first audio chunk instead of including the remaining LLM generation time.
  • 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).

  • Silently swallowed errors now surface: failed saves of the audiobook prompt default, generated character sheets and character colours show an error toast, and ~20 other hidden failures (rehearsal/cast write-back, reader progress and audio fetches, seed-finder DB writes, settings/model fetches, export server save) are logged with logErr instead of being dropped.

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: