diff --git a/docker-compose.yml b/docker-compose.yml index cec5ba1..e91e2e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: - ${VOICE_HOST_DIR:-/home/sparky/Media/_Sounds/TTS_Voices}:/voices:rw - ./config:/home/app/.config/tts-voice-creator:rw # settings, presets, routes - ./logs:/logs:rw # application logs + - /home/sparky/Docker/tts-dgx-spark-faster-qwen3-tts/config:/tts-config:rw - /var/run/docker.sock:/var/run/docker.sock:ro - ./static:/app/static:ro - ./server.py:/app/server.py:ro @@ -31,7 +32,8 @@ services: - VOICES_DIR=/voices - OUTPUT_DIR=/voices/active_voices # Containers that load WAV files and need a restart after voice changes - - TTS_CONTAINER_NAMES=faster-qwen3-tts-voiceclone,faster-qwen3-tts-streaming + - TTS_CONTAINER_NAME=tts-faster-qwen3-tts-voiceclone + - TTS_CONTAINER_NAMES=tts-faster-qwen3-tts-voiceclone,tts-faster-qwen3-tts-streaming - VOICE_DESIGN_MODEL=Qwen3-TTS-12Hz-1.7B-VoiceDesign extra_hosts: diff --git a/docs/seed-sample-text.txt b/docs/seed-sample-text.txt new file mode 100644 index 0000000..27c4c6a --- /dev/null +++ b/docs/seed-sample-text.txt @@ -0,0 +1 @@ +Die 3.567 neuen High-End Geräte für das Server-Update benötigen eine außergewöhnlich starke Kühlung und regelmäßige Maßnahmen, um die Performance bei großer Last zu gewährleisten. - The system administrator successfully configured the customized Docker stacks and benchmarked the inference engines at exactly 8:45 AM. - Notiere dir an Midsummer 21.06. um 14 Uhr - Es ist reine Zeitverschwendung, etwas Mittelmäßiges zu tun! Schöne Grüße! — Madonna - Träume beginnt mit einem positiven Mindset. diff --git a/portainer-stack.yml b/portainer-stack.yml index 748c324..c228499 100644 --- a/portainer-stack.yml +++ b/portainer-stack.yml @@ -33,6 +33,7 @@ services: - /home/sparky/Media/_Sounds/TTS_Voices:/voices:rw - /home/sparky/Docker/tts-voice-creator-clone-and-design-2/config:/home/app/.config/tts-voice-creator:rw - /home/sparky/Docker/tts-voice-creator-clone-and-design-2/logs:/logs:rw + - /home/sparky/Docker/tts-dgx-spark-faster-qwen3-tts/config:/tts-config:rw - /var/run/docker.sock:/var/run/docker.sock:ro # ── Hot-reload mounts (changes apply on container recreate, not restart) ── - /home/sparky/Docker/tts-voice-creator-clone-and-design-2/server.py:/app/server.py:ro @@ -47,8 +48,8 @@ services: - VOICES_DIR=/voices - OUTPUT_DIR=/voices/active_voices - TTS_STREAM_URL=http://host.docker.internal:8023 - - TTS_CONTAINER_NAME=faster-qwen3-tts-voiceclone - - TTS_CONTAINER_NAMES=faster-qwen3-tts-voiceclone,faster-qwen3-tts-streaming + - TTS_CONTAINER_NAME=tts-faster-qwen3-tts-voiceclone + - TTS_CONTAINER_NAMES=tts-faster-qwen3-tts-voiceclone,tts-faster-qwen3-tts-streaming - VOICE_DESIGN_MODEL=Qwen3-TTS-12Hz-1.7B-VoiceDesign extra_hosts: diff --git a/routes/docker.py b/routes/docker.py index d089568..9d971bf 100644 --- a/routes/docker.py +++ b/routes/docker.py @@ -19,10 +19,10 @@ _LOCAL_CONTAINER_DEFS: list[dict] = [ {"name": "ollama", "label": "Ollama", "role": "llm", "port": 11434, "stack": "dgx-spark_lite-llm_llama-swap_vllm_llama-cpp_ollama", "image": "ollama/ollama", "repo": "https://ollama.com", "description": "Local LLM runtime. The app uses its OpenAI-compatible /v1 endpoint."}, {"name": "llama-swap", "label": "llama-swap", "role": "llm", "port": 28080, "stack": "dgx-spark_lite-llm_llama-swap_vllm_llama-cpp_ollama", "image": "ghcr.io/mostlygeek/llama-swap:cuda", "repo": "https://github.com/mostlygeek/llama-swap", "description": "Model swapping proxy for local LLMs. Useful as the default Rehearser LLM endpoint."}, {"name": "llama-qwen35-4b", "label": "Qwen3.5 4B LLM", "role": "llm", "port": 19001, "stack": "dgx-spark_lite-llm_llama-swap_vllm_llama-cpp_ollama", "image": "local", "repo": "", "description": "Dedicated Qwen LLM container. Use if it exposes an OpenAI-compatible /v1 API."}, - {"name": "faster-qwen3-tts-voiceclone", "label": "Qwen3 TTS · Voice Clone", "role": "tts", "port": 8020, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:v4", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "WAV voice cloning. Scans active_voices at startup; restart after adding or editing voices."}, - {"name": "faster-qwen3-tts-voicedesign", "label": "Qwen3 TTS · Voice Design", "role": "tts", "port": 8021, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:v4", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "Instruction-based voice design. Best Qwen path for tone, style, and character prompts."}, - {"name": "faster-qwen3-tts-customvoice", "label": "Qwen3 TTS · Custom Voice", "role": "tts", "port": 8022, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:v4", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "CustomVoice speakers with per-request instruct overrides for tone/style."}, - {"name": "faster-qwen3-tts-streaming", "label": "Qwen3 TTS · Streaming", "role": "tts", "port": 8023, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:streaming", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "Low-latency progressive WAV streaming for voice clone voices."}, + {"name": "tts-faster-qwen3-tts-voiceclone", "label": "Qwen3 TTS · Voice Clone", "role": "tts", "port": 8020, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:v4", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "WAV voice cloning. Scans active_voices at startup; restart after adding or editing voices."}, + {"name": "tts-faster-qwen3-tts-voicedesign", "label": "Qwen3 TTS · Voice Design", "role": "tts", "port": 8021, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:v4", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "Instruction-based voice design. Best Qwen path for tone, style, and character prompts."}, + {"name": "tts-faster-qwen3-tts-customvoice", "label": "Qwen3 TTS · Custom Voice", "role": "tts", "port": 8022, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:v4", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "CustomVoice speakers with per-request instruct overrides for tone/style."}, + {"name": "tts-faster-qwen3-tts-streaming", "label": "Qwen3 TTS · Streaming", "role": "tts", "port": 8023, "stack": "faster-qwen3-tts-dgx-spark", "image": "martinb78/faster-qwen3-tts-dgx-spark:streaming", "repo": "https://github.com/mARTin-B78/dgx-spark-faster-qwen3-tts", "description": "Low-latency progressive WAV streaming for voice clone voices."}, {"name": "faster-whisper", "label": "faster-whisper", "role": "stt", "port": 8010, "stack": "faster-whisper-dgx-spark", "image": "local", "repo": "https://github.com/SYSTRAN/faster-whisper", "description": "Fast local Whisper transcription endpoint."}, {"name": "fish-speech-api", "label": "Fish-Speech API", "role": "tts", "port": 38080, "stack": "fish-speech-dgx-spark", "image": "local", "repo": "https://github.com/fishaudio/fish-speech", "description": "Tone-aware cloning backend. Recommended for Rehearser character voices when you need both identity and emotion."}, {"name": "nemotron-asr-streaming", "label": "Nemotron ASR Streaming", "role": "stt", "port": 8006, "stack": "nemotron-asr-streaming", "image": "local", "repo": "https://github.com/pipecat-ai/nemotron-january-2026", "description": "Streaming ASR service. Use as a custom STT endpoint if it exposes OpenAI transcription or /transcribe."}, diff --git a/routes/library.py b/routes/library.py index 8ca18da..a3f3752 100644 --- a/routes/library.py +++ b/routes/library.py @@ -641,9 +641,23 @@ async def rename_voice(request: Request): if backup and backup.exists(): if backup.resolve() != new_backup.resolve(): backup.rename(new_backup) - new_meta = _load_meta(new_audio) + + # Also rename TTS config assets if mounted + tts_config = Path("/tts-config") + if tts_config.exists(): + old_pt = tts_config / "speakers" / f"{old_id}.pt" + if old_pt.exists(): + old_pt.rename(tts_config / "speakers" / f"{new_id}.pt") + + old_seeds = tts_config / "seed_samples" / old_id + if old_seeds.exists() and old_seeds.is_dir(): + old_seeds.rename(tts_config / "seed_samples" / new_id) + + new_meta = _load_meta(new_audio) + if backup and backup.exists(): new_meta["original_backup"] = str(new_backup) - _save_meta(new_audio, new_meta) + new_meta["needs_tts_restart"] = True + _save_meta(new_audio, new_meta) return {"new_id": new_id, "path": str(new_audio), "file_type": new_audio.suffix.lower().lstrip(".")} diff --git a/static/js/engines.js b/static/js/engines.js index 8d6fffb..8a623b8 100644 --- a/static/js/engines.js +++ b/static/js/engines.js @@ -201,10 +201,10 @@ function saveCustomEngineCards(cards) { // Settings key per docker container name; role fallback for custom cards const DC_USE_MAP = { - 'faster-qwen3-tts-voiceclone': { settingKey: 'tts_url', label: 'Use as TTS' }, - 'faster-qwen3-tts-voicedesign': { settingKey: 'voice_design_url', label: 'Use as Voice Design' }, - 'faster-qwen3-tts-customvoice': { settingKey: 'customvoice_url', label: 'Use as CustomVoice' }, - 'faster-qwen3-tts-streaming': { settingKey: 'tts_stream_url', label: 'Use as Streaming TTS' }, + 'tts-faster-qwen3-tts-voiceclone': { settingKey: 'tts_url', label: 'Use as TTS' }, + 'tts-faster-qwen3-tts-voicedesign': { settingKey: 'voice_design_url', label: 'Use as Voice Design' }, + 'tts-faster-qwen3-tts-customvoice': { settingKey: 'customvoice_url', label: 'Use as CustomVoice' }, + 'tts-faster-qwen3-tts-streaming': { settingKey: 'tts_stream_url', label: 'Use as Streaming TTS' }, 'faster-whisper': { settingKey: 'faster_whisper_url', label: 'Use as STT' }, 'fish-speech-api': { settingKey: 'fishspeech_url', label: 'Use as Fish-Speech' }, 'nemotron-asr-streaming': { settingKey: 'faster_whisper_url', label: 'Use as STT' }, @@ -333,10 +333,10 @@ function renderLocalContainers(containers) { const ROLE_LABEL = { tts: 'TTS', stt: 'STT', 'stt+tts': 'STT · TTS', llm: 'LLM' }; const DC_ICONS = { - 'faster-qwen3-tts-voiceclone': '', - 'faster-qwen3-tts-voicedesign': '', - 'faster-qwen3-tts-customvoice': '🎭', - 'faster-qwen3-tts-streaming': '', + 'tts-faster-qwen3-tts-voiceclone': '', + 'tts-faster-qwen3-tts-voicedesign': '', + 'tts-faster-qwen3-tts-customvoice': '🎭', + 'tts-faster-qwen3-tts-streaming': '', 'parakeet-asr': '🦜', 'magpie-tts': '🐦', 'parakeet-rnnt-nim': '🦜', @@ -357,10 +357,10 @@ function renderLocalContainers(containers) { const _q = (v) => ``; const _m = (v) => ``; const DC_METRICS = { - 'faster-qwen3-tts-voiceclone': [[_t(),'~0.3× GPU'],[_l(),'1–3 s'],[_q(),'Premium clone'],[_m(),'6–8 GB VRAM']], - 'faster-qwen3-tts-voicedesign': [[_t(),'~0.4× GPU'],[_l(),'1–3 s'],[_q(),'Premium'], [_m(),'6–8 GB VRAM']], - 'faster-qwen3-tts-customvoice': [[_t(),'~0.3× GPU'],[_l(),'1–3 s'],[_q(),'Premium'], [_m(),'6–8 GB VRAM']], - 'faster-qwen3-tts-streaming': [[_t(),'~0.1× GPU'],[_l(),'0.5–1 s'],[_q(),'Premium'], [_m(),'6–8 GB VRAM']], + 'tts-faster-qwen3-tts-voiceclone': [[_t(),'~0.3× GPU'],[_l(),'1–3 s'],[_q(),'Premium clone'],[_m(),'6–8 GB VRAM']], + 'tts-faster-qwen3-tts-voicedesign': [[_t(),'~0.4× GPU'],[_l(),'1–3 s'],[_q(),'Premium'], [_m(),'6–8 GB VRAM']], + 'tts-faster-qwen3-tts-customvoice': [[_t(),'~0.3× GPU'],[_l(),'1–3 s'],[_q(),'Premium'], [_m(),'6–8 GB VRAM']], + 'tts-faster-qwen3-tts-streaming': [[_t(),'~0.1× GPU'],[_l(),'0.5–1 s'],[_q(),'Premium'], [_m(),'6–8 GB VRAM']], 'magpie-tts': [[_t(),'~0.05× GPU'],[_l(),'0.3–0.8 s'],[_q(),'High'], [_m(),'4–6 GB VRAM']], 'parakeet-asr': [[_t(),'~200× RT GPU'],[_l(),'<0.3 s'],[_q(),'Parakeet-TDT'],[_m(),'2 GB VRAM']], 'parakeet-rnnt-nim': [[_t(),'~200× RT GPU'],[_l(),'<0.3 s'],[_q(),'Parakeet-1B'], [_m(),'2 GB VRAM']], diff --git a/static/js/seed-finder.js b/static/js/seed-finder.js index d09445b..bb12697 100644 --- a/static/js/seed-finder.js +++ b/static/js/seed-finder.js @@ -12,6 +12,9 @@ const SEED_FINDER_TEXT_EN = SEED_FINDER_TEXT_DE; const SEED_FINDER_TEXT_MIXED = SEED_FINDER_TEXT_DE; function _seedFinderDefaultText(voiceId) { + if (window._appSettings && window._appSettings.seed_finder_text) { + return window._appSettings.seed_finder_text; + } const lc = (voiceId || '').toLowerCase(); if (lc.startsWith('de_')) return SEED_FINDER_TEXT_MIXED; if (lc.startsWith('en_') || lc.startsWith('gb_')) return SEED_FINDER_TEXT_EN; diff --git a/static/js/settings.js b/static/js/settings.js index b435f5a..7282cc2 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -342,7 +342,8 @@ const SETTINGS_FIELD_HELP = { 's-output-dir': ['Active voices directory', 'Container path where new cloned or exported voices are saved. Usually /voices/active_voices.'], 's-tts-key': ['TTS API key', 'Optional. For local OpenAI-compatible servers, sk-local or empty usually works.'], 's-vd-key': ['Voice Design API key', 'Optional. Use only if your Voice Design backend requires Authorization.'], - 's-whisper-key': ['Whisper API key', 'Optional. Needed for cloud STT, usually empty for local Whisper-compatible containers.'] + 's-whisper-key': ['Whisper API key', 'Optional. Needed for cloud STT, usually empty for local Whisper-compatible containers.'], + 's-seed-finder-text': ['Seed Finder text', 'Custom test sentence used when opening the Seed Finder. Leave empty to use language defaults.'] }; @@ -459,6 +460,8 @@ async function loadSettings() { $('s-vd-key').value = s.voice_design_api_key || ''; $('s-voices-scan-dir').value = s.voices_scan_dir || ''; $('s-output-dir').value = s.output_dir || ''; + const seedTextEl = $('s-seed-finder-text'); + if (seedTextEl) seedTextEl.value = s.seed_finder_text || ''; const themeEl = $('s-theme-select'); if (themeEl) themeEl.value = document.documentElement.dataset.theme || 'dark'; // Captures settings @@ -566,6 +569,7 @@ document.addEventListener('click', async e => { if (!e.target.closest('.s-save-b refine_corrections: $('s-refine-corrections')?.checked ?? true, refine_punctuation: $('s-refine-punctuation')?.checked ?? true, captures_default_voice: $('s-captures-default-voice')?.value || '', + seed_finder_text: $('s-seed-finder-text')?.value || '', }) }); _appSettings.tts_stream_url = $('s-tts-stream-url').value; _appSettings.customvoice_url = $('s-customvoice-url').value; @@ -579,6 +583,7 @@ document.addEventListener('click', async e => { if (!e.target.closest('.s-save-b _appSettings.vibevoice_url = $('s-vibevoice-url').value; _appSettings.xtts_url = $('s-xtts-url')?.value || ''; _appSettings.tts_stream_mode = $('s-tts-stream-mode').value; + _appSettings.seed_finder_text = $('s-seed-finder-text')?.value || ''; _ttsStreamHealth = null; await refreshTtsBackendAvailability($('tts-backend-select')?.value || ''); markSettingsSeen(); diff --git a/static/sections/s-settings.html b/static/sections/s-settings.html index 70505ac..e9ac4c6 100644 --- a/static/sections/s-settings.html +++ b/static/sections/s-settings.html @@ -24,6 +24,11 @@ Switch between light and dark interface. Saved instantly. +
+ + + Custom test sentence used when opening the Seed Finder. +