From 2df34f85cc13b577720d62e1a7aca49ce85378ee Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sat, 23 May 2026 13:24:25 -0400 Subject: [PATCH] Add F5-TTS as tts-1-f5 engine (additive, alongside tts-1-qwen) --- Makefile | 12 +- docs/MODELS.md | 60 ++++++++ requirements.txt | 5 + sample.env | 7 +- speech.py | 182 ++++++++++++++++++++++++- voice_to_speaker.default.yaml | 250 ++++++++++++++++++++++++++++++++++ 6 files changed, 511 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index a85ef35..cb5b5be 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CONTAINER_NAME ?= uncloseai-speech-server-1 -.PHONY: help deploy restart logs test clean stop start voices voices-qwen voices-piper voices-xtts voices-kokoro test-kokoro voices-silero test-silero voices-chatterbox test-chatterbox push-all hydrate load-test test-qwen venv venv-run local local-cpu +.PHONY: help deploy restart logs test clean stop start voices voices-qwen voices-piper voices-xtts voices-kokoro test-kokoro voices-silero test-silero voices-chatterbox test-chatterbox push-all hydrate load-test test-qwen test-f5 venv venv-run local local-cpu help: @echo "Raccoon TTS Mission - Development Commands" @@ -24,6 +24,7 @@ help: @echo "Testing:" @echo " make test - Test TTS endpoint (Qwen3-TTS)" @echo " make test-qwen - Test Qwen3-TTS voice cloning" + @echo " make test-f5 - Test F5-TTS voice cloning" @echo " make hydrate - Test all configured voices" @echo " make load-test - Concurrent load test" @echo "" @@ -118,6 +119,15 @@ test-qwen: @echo "" @echo "Saved to /tmp/qwen_test.mp3" +test-f5: + @echo "Testing F5-TTS endpoint..." + curl -X POST http://localhost:8000/v1/audio/speech \ + -H "Content-Type: application/json" \ + -d '{"model":"tts-1-f5","voice":"aria","input":"Raccoon mission TTS test with F5 flow matching"}' \ + -o /tmp/f5_test.mp3 + @echo "" + @echo "Saved to /tmp/f5_test.mp3" + test-kokoro: @echo "Testing Kokoro fast synthesis..." curl -X POST http://localhost:8000/v1/audio/speech \ diff --git a/docs/MODELS.md b/docs/MODELS.md index a4d7714..21cf541 100644 --- a/docs/MODELS.md +++ b/docs/MODELS.md @@ -125,6 +125,66 @@ make test-qwen # Test Qwen3-TTS explicitly --- +### F5-TTS ✅ (also enabled by default) + +**Status:** INTEGRATED as tts-1-f5 (additive, alongside tts-1-qwen) +**Project:** SWivid/F5-TTS (community-maintained, gated HuggingFace checkpoint) +**License:** MIT (model + code) +**Model:** F5-TTS_v1 + +**Why Integrated:** +- Empirical benchmark (Richard, 2026-05-23): faster inference + better voice clones than Qwen3-TTS on the same reference clips +- Smaller model (~336M params vs Qwen3-TTS 1.7B) — lower VRAM, fits comfortably on modest GPUs +- Flow-matching architecture, zero-shot cloning, no fine-tuning needed +- 24kHz output, matches Qwen3-TTS sample rate (drop-in voice swap for clients) + +**Model Specs:** +- Parameters: ~336M +- Sample Rate: 24kHz +- Input: Text + Reference Audio (3+ seconds) + Reference Transcript +- Languages: English (primary); community fine-tunes available for others +- Size: ~1.5GB (F5-TTS_v1 + Vocos vocoder) + +**Model Source:** +- HuggingFace: `SWivid/F5-TTS` (**gated** — accept license at https://huggingface.co/SWivid/F5-TTS) +- Auto-downloaded on first use; requires `HF_TOKEN` env var + +**Integration:** +- Used for `tts-1-f5` model (additive default) +- Voice cloning with `ref_audio` + `ref_text` (same shape as tts-1-qwen) +- Reuses the same 40 LibriSpeech voices as tts-1-qwen + +**Example Config:** +```yaml +tts-1-f5: + aria: + ref_audio: cloned-voices/aria.wav + ref_text: "BUT THE WINDOWS ARE PATCHED WITH WOODEN PANES AND THE DOOR I THINK IS LIKE THE GATE IT IS NEVER OPENED" + language: English +``` + +**Tuning Knobs (engine-specific, not OpenAI-compatible):** +- `nfe_step` (default 32) — ODE step count; higher = better quality, slower +- `cfg_strength` (default 2.0) — classifier-free guidance strength +- `speed` (default 1.0) — pitch-preserving speed multiplier +- Does NOT support `temperature` / `top_p` / `top_k` (flow-matching, not autoregressive) + +**Makefile Targets:** +```bash +make test-f5 # Test F5-TTS voice cloning +``` + +**Hardware Requirements:** +- GPU: NVIDIA with 4GB+ VRAM (lighter than Qwen3-TTS) +- CPU: Works but many× realtime +- MPS (Apple Silicon): supported, ~1.5–2× realtime per VoiceClone benchmarks + +**Source of Inspiration:** [MonumentalSystems/VoiceClone](https://github.com/MonumentalSystems/VoiceClone) — a single-file F5-TTS web app that proved the engine on our reference workload. Our wrapper mirrors their `F5TTS.infer()` call pattern. + +**Raccoon Priority:** ⭐⭐⭐⭐⭐ (Empirically beats current default, MIT, lower VRAM) + +--- + ### 1. Piper TTS (disabled by default) ✅ > 📖 **See [detailed documentation](models/piper-tts.md)** for comprehensive technical specs and integration guide diff --git a/requirements.txt b/requirements.txt index 35bed2f..a257339 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,11 @@ loguru # Qwen3-TTS - state-of-the-art TTS with voice cloning (Apache 2.0) # 1.7B params, 10 languages, 97ms latency, 12Hz tokenizer qwen-tts>=0.0.5 +# F5-TTS - flow-matching zero-shot voice cloning (MIT, SWivid/F5-TTS) +# 336M params, 24kHz output, no fine-tuning needed +# Checked 2026-05-23: 1.1.20 is latest stable +# Note: gated checkpoint on HuggingFace; requires HF_TOKEN env var + license accept +f5-tts==1.1.20 # OHF-Voice fork doesn't have installable Python package yet # Stick with PyPI piper-tts but use absolute paths in config # piper-tts>=1.2.0 diff --git a/sample.env b/sample.env index 0003f53..a39d1ef 100644 --- a/sample.env +++ b/sample.env @@ -9,4 +9,9 @@ WORKERS=1 #PRELOAD_MODEL=xtts #PRELOAD_MODEL=xtts_v2.0.2 #EXTRA_ARGS=--log-level DEBUG --unload-timer 300 -#USE_ROCM=1 \ No newline at end of file +#USE_ROCM=1 + +# F5-TTS gated checkpoint requires a HuggingFace token + license accept. +# Visit https://huggingface.co/SWivid/F5-TTS, accept the license, then set +# this in your local speech.env (NOT here — sample.env is committed). +#HF_TOKEN= \ No newline at end of file diff --git a/speech.py b/speech.py index 09c320f..1dff98c 100755 --- a/speech.py +++ b/speech.py @@ -52,6 +52,20 @@ except ImportError: torch = None Qwen3TTSModel = None +# Try to import F5-TTS dependencies (SWivid/F5-TTS, MIT-licensed flow-matching TTS) +try: + import torch + from f5_tts.api import F5TTS + F5_TTS_AVAILABLE = True +except ImportError: + F5_TTS_AVAILABLE = False + if torch is None: + try: + import torch + except ImportError: + torch = None + F5TTS = None + @contextlib.asynccontextmanager async def lifespan(app): # Startup: Initialize voice caches in each worker process @@ -107,6 +121,10 @@ async def lifespan(app): model_info["engine"] = "qwen3-tts" model_info["description"] = "State-of-the-art TTS with voice cloning (1.7B params, 10 languages)" model_info["sample_rate"] = 24000 + elif model_id == 'tts-1-f5': + model_info["engine"] = "f5-tts" + model_info["description"] = "Flow-matching zero-shot voice cloning (336M params, MIT)" + model_info["sample_rate"] = 24000 models_data.append(model_info) @@ -145,6 +163,7 @@ kokoro_pipeline = None kokoro_lang = None qwen_model = None qwen_voice_prompts = {} # Cache for voice clone prompts +f5_model = None # Default args for worker processes (will be overridden in __main__) class DefaultArgs: @@ -180,6 +199,7 @@ voices_cache = None silero_load_semaphore = asyncio.Semaphore(1) # Only one Silero model load at a time kokoro_load_semaphore = asyncio.Semaphore(1) # Only one Kokoro model load at a time qwen_load_semaphore = asyncio.Semaphore(1) # Only one Qwen model load at a time +f5_load_semaphore = asyncio.Semaphore(1) # Only one F5-TTS model load at a time def unload_model(): import torch, gc @@ -491,6 +511,72 @@ class qwen3_wrapper(): logger.error(f"Qwen3-TTS generation failed: {e}") raise +class f5_wrapper(): + """Wrapper for F5-TTS model (SWivid/F5-TTS, MIT) + + Flow-matching zero-shot voice cloning: + - ~336M params, smaller than Qwen3-TTS (1.7B) + - 24kHz output, matches Qwen3-TTS sample rate + - Reference audio + transcript, no fine-tuning + - No temperature/top_p/top_k — uses cfg_strength + nfe_step instead + """ + def __init__(self, device='cuda'): + self.device = device + self.sample_rate = 24000 # f5_tts.infer.utils_infer.target_sample_rate + + logger.info(f"Loading F5-TTS model on device '{device}'") + + try: + from f5_tts.api import F5TTS + self.model = F5TTS(device=device) + logger.info(f"Successfully loaded F5-TTS model on {device}") + except Exception as e: + logger.error(f"Failed to load F5-TTS model: {e}") + raise + + def tts(self, text, ref_audio, ref_text, speed=1.0, nfe_step=32, cfg_strength=2.0): + """Generate speech from text using F5-TTS voice cloning. + + Args: + text: Text to synthesize + ref_audio: Path to reference audio file + ref_text: Transcript of reference audio + speed: Speed multiplier (1.0 = normal) + nfe_step: Number of ODE steps (32 = default, balance quality/speed) + cfg_strength: Classifier-free guidance strength + + Returns: + Audio data as bytes (float32 PCM at 24kHz) + """ + import numpy as np + + logger.info(f"F5-TTS generating: text length={len(text)}") + + try: + wav, sr, _spec = self.model.infer( + ref_file=ref_audio, + ref_text=ref_text, + gen_text=text, + nfe_step=nfe_step, + cfg_strength=cfg_strength, + speed=speed, + show_info=lambda *a, **k: None, + progress=None, + ) + + if hasattr(wav, 'detach'): + wav = wav.detach().to('cpu', dtype=torch.float32).numpy() + audio = np.asarray(wav, dtype=np.float32).flatten() + + self.sample_rate = sr + logger.info(f"F5-TTS generated {len(audio)} samples at {sr}Hz") + + return audio.tobytes() + + except Exception as e: + logger.error(f"F5-TTS generation failed: {e}") + raise + def default_exists(filename: str): if not os.path.exists(filename): fpath, ext = os.path.splitext(filename) @@ -613,7 +699,7 @@ def build_ffmpeg_args(response_format, input_format, sample_rate): async def list_models(): """List all available TTS models (OpenAI-compatible format)""" # Return minimal OpenAI-compatible model list (no extra fields) - # Only tts-1-qwen is enabled by default + # tts-1-qwen and tts-1-f5 enabled by default return { "object": "list", "data": [ @@ -622,6 +708,12 @@ async def list_models(): "object": "model", "created": 1700000000, "owned_by": "uncloseai" + }, + { + "id": "tts-1-f5", + "object": "model", + "created": 1700000000, + "owned_by": "uncloseai" } # Other models disabled by default: # { @@ -705,6 +797,10 @@ async def list_voices(): model_info["engine"] = "qwen3-tts" model_info["description"] = "State-of-the-art TTS with voice cloning (1.7B params, 10 languages)" model_info["sample_rate"] = 24000 + elif model_id == 'tts-1-f5': + model_info["engine"] = "f5-tts" + model_info["description"] = "Flow-matching zero-shot voice cloning (336M params, MIT)" + model_info["sample_rate"] = 24000 models_data.append(model_info) @@ -764,6 +860,8 @@ async def generate_speech(request: GenerateSpeechRequest): media_type = "audio/pcm;rate=24000" elif model == 'tts-1-qwen': # qwen3-tts media_type = "audio/pcm;rate=24000" + elif model == 'tts-1-f5': # f5-tts + media_type = "audio/pcm;rate=24000" else: raise BadRequestError(f"Invalid response_format: '{response_format}'", param='response_format') @@ -1174,9 +1272,86 @@ async def generate_speech(request: GenerateSpeechRequest): out_writer_worker = threading.Thread(target=out_writer, daemon=True) out_writer_worker.start() + return StreamingResponse(content=ffmpeg_proc.stdout, media_type=media_type) + # Use F5-TTS for tts-1-f5 + elif model == 'tts-1-f5': + global f5_model + + if not F5_TTS_AVAILABLE: + raise ServiceUnavailableError("F5-TTS is not available. Install with: pip install f5-tts") + + voice_map = map_voice_to_speaker(voice, 'tts-1-f5') + ref_audio = voice_map.get('ref_audio') + ref_text = voice_map.get('ref_text') + + if not (ref_audio and ref_text): + raise BadRequestError(f"Voice '{voice}' requires ref_audio and ref_text configuration", param='voice') + + # Load F5 model if not already loaded + if f5_model is None: + async with f5_load_semaphore: + if f5_model is None: + device = args.xtts_device if args.xtts_device != 'none' else 'cpu' + logger.info(f"Loading F5-TTS model on device '{device}'") + f5_model = await asyncio.to_thread(f5_wrapper, device=device) + + # Split text into sentences for streaming (first audio arrives faster) + sentences = simple_sentence_split(input_text, max_length=500) + logger.info(f"Split text into {len(sentences)} sentences for F5-TTS streaming") + + # F5-TTS outputs float32 PCM at 24kHz + ffmpeg_args = build_ffmpeg_args(response_format, input_format="f32le", sample_rate="24000") + + # F5-TTS accepts speed natively; only apply atempo as a fallback when ffmpeg can do it cheaper + # (we pass speed to the model below, leaving ffmpeg speed at 1.0) + + ffmpeg_args.extend(["-"]) + ffmpeg_proc = subprocess.Popen(ffmpeg_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + + in_q = queue.Queue() + + def generator(): + """Process sentences sequentially and feed to queue""" + try: + for idx, sentence in enumerate(sentences): + logger.debug(f"F5-TTS processing sentence {idx+1}/{len(sentences)}: {len(sentence)} chars") + audio_bytes = f5_model.tts( + text=sentence, + ref_audio=ref_audio, + ref_text=ref_text, + speed=speed, + ) + in_q.put(audio_bytes) + logger.debug(f"F5-TTS: queued sentence {idx+1}/{len(sentences)}") + except Exception as e: + logger.error(f"F5-TTS streaming error: {e}") + finally: + in_q.put(None) + logger.info(f"F5-TTS streaming complete: {len(sentences)} sentences processed") + + def out_writer(): + """Write audio from queue to ffmpeg stdin""" + try: + while True: + chunk = in_q.get() + if chunk is None: + break + ffmpeg_proc.stdin.write(chunk) + except Exception as e: + logger.error(f"F5-TTS ffmpeg write error: {e}") + ffmpeg_proc.kill() + finally: + ffmpeg_proc.stdin.close() + + generator_worker = threading.Thread(target=generator, daemon=True) + generator_worker.start() + + out_writer_worker = threading.Thread(target=out_writer, daemon=True) + out_writer_worker.start() + return StreamingResponse(content=ffmpeg_proc.stdout, media_type=media_type) else: - raise BadRequestError("No such model, must be tts-1-qwen (default), tts-1, tts-1-hd, tts-1-silero, or tts-1-kokoro.", param='model') + raise BadRequestError("No such model, must be tts-1-qwen (default), tts-1-f5, tts-1, tts-1-hd, tts-1-silero, or tts-1-kokoro.", param='model') if __name__ == "__main__": parser = argparse.ArgumentParser( @@ -1203,8 +1378,9 @@ if __name__ == "__main__": elif args.preload: xtts = xtts_wrapper(args.preload, device=args.xtts_device, unload_timer=args.unload_timer) - # Register only Qwen by default (other models disabled) + # Register Qwen + F5 by default (other models disabled) app.register_model('tts-1-qwen') + app.register_model('tts-1-f5') # To enable other models, uncomment below: # app.register_model('tts-1') # app.register_model('tts-1-hd') diff --git a/voice_to_speaker.default.yaml b/voice_to_speaker.default.yaml index 9ccaba4..24cdb3e 100644 --- a/voice_to_speaker.default.yaml +++ b/voice_to_speaker.default.yaml @@ -266,3 +266,253 @@ tts-1-qwen: # alloy: # model: xtts # speaker: voices/alloy.wav + +# ===== F5-TTS voice mappings ===== +# Same 40 LibriSpeech speakers as tts-1-qwen, reused for F5-TTS (flow-matching). +# F5-TTS uses ref_audio + ref_text; identical to Qwen3-TTS reference format. + +tts-1-f5: + + # female - speaker 2094 + aria: + ref_audio: cloned-voices/aria.wav + ref_text: "BUT THE WINDOWS ARE PATCHED WITH WOODEN PANES AND THE DOOR I THINK IS LIKE THE GATE IT IS NEVER OPENED" + language: English + + # female - speaker 3575 + clara: + ref_audio: cloned-voices/clara.wav + ref_text: "BUT IT IS NOT WITH A VIEW TO DISTINCTION THAT YOU SHOULD CULTIVATE THIS TALENT IF YOU CONSULT YOUR OWN HAPPINESS" + language: English + + # female - speaker 2961 + elena: + ref_audio: cloned-voices/elena.wav + ref_text: "MANY IF NOT ALL THE ELEMENTS OF THE PRE SOCRATIC PHILOSOPHY ARE INCLUDED IN THE TIMAEUS" + language: English + + # female - speaker 8463 + grace: + ref_audio: cloned-voices/grace.wav + ref_text: "AS TO HIS AGE AND ALSO THE NAME OF HIS MASTER JACOB'S STATEMENT VARIED SOMEWHAT FROM THE ADVERTISEMENT" + language: English + + # female - speaker 1995 + hazel: + ref_audio: cloned-voices/hazel.wav + ref_text: "I BELIEVE IN THE TRAINING OF PEOPLE TO THEIR HIGHEST CAPACITY THE ENGLISHMAN HERE HEARTILY SECONDED HIM" + language: English + + # female - speaker 1284 + iris: + ref_audio: cloned-voices/iris.wav + ref_text: "GOLD IS THE MOST COMMON METAL IN THE LAND OF OZ AND IS USED FOR MANY PURPOSES BECAUSE IT IS SOFT AND PLIABLE" + language: English + + # female - speaker 5142 + luna: + ref_audio: cloned-voices/luna.wav + ref_text: "THE DOOR OPENED AGAIN WHILE I WAS STILL STUDYING THE TWO BROTHERS WITHOUT I HONESTLY CONFESS BEING VERY FAVORABLY IMPRESSED BY EITHER OF THEM" + language: English + + # female - speaker 4446 + maya: + ref_audio: cloned-voices/maya.wav + ref_text: "HE HAD PRECONCEIVED IDEAS ABOUT EVERYTHING AND HIS IDEA ABOUT AMERICANS WAS THAT THEY SHOULD BE ENGINEERS OR MECHANICS" + language: English + + # female - speaker 1221 + ruby: + ref_audio: cloned-voices/ruby.wav + ref_text: "YEA HIS HONOURABLE WORSHIP IS WITHIN BUT HE HATH A GODLY MINISTER OR TWO WITH HIM AND LIKEWISE A LEECH" + language: English + + # female - speaker 4507 + sage: + ref_audio: cloned-voices/sage.wav + ref_text: "NOW WHEN HAS HORROR EVER EXCLUDED STUDY" + language: English + + # female - speaker 3729 + sofia: + ref_audio: cloned-voices/sofia.wav + ref_text: "I HAD A NAME I BELIEVE IN MY YOUNG DAYS BUT I HAVE FORGOTTEN IT SINCE I HAVE BEEN IN SERVICE" + language: English + + # female - speaker 121 + amber: + ref_audio: cloned-voices/amber.wav + ref_text: "HAY FEVER A HEART TROUBLE CAUSED BY FALLING IN LOVE WITH A GRASS WIDOW" + language: English + + # female - speaker 237 + brooke: + ref_audio: cloned-voices/brooke.wav + ref_text: "FRANK READ ENGLISH SLOWLY AND THE MORE HE READ ABOUT THIS DIVORCE CASE THE ANGRIER HE GREW" + language: English + + # female - speaker 1580 + cora: + ref_audio: cloned-voices/cora.wav + ref_text: "THE ALTERNATIVE WAS THAT SOMEONE PASSING HAD OBSERVED THE KEY IN THE DOOR HAD KNOWN THAT I WAS OUT AND HAD ENTERED TO LOOK AT THE PAPERS" + language: English + + # female - speaker 3570 + diana: + ref_audio: cloned-voices/diana.wav + ref_text: "THE WEARERS OF UNIFORMS AND LIVERIES MAY BE ROUGHLY DIVIDED INTO TWO CLASSES THE FREE AND THE SERVILE OR THE NOBLE AND THE IGNOBLE" + language: English + + # female - speaker 4970 + eden: + ref_audio: cloned-voices/eden.wav + ref_text: "RUTH SAT QUITE STILL FOR A TIME WITH FACE INTENT AND FLUSHED IT WAS OUT NOW" + language: English + + # female - speaker 4992 + faye: + ref_audio: cloned-voices/faye.wav + ref_text: "HE GIVE UP HIS POSITION AND SHUT THE FAMILY UP IN THAT TOMB OF A HOUSE SO T HE COULD STUDY HIS BOOKS" + language: English + + # female - speaker 5683 + gemma: + ref_audio: cloned-voices/gemma.wav + ref_text: "DO YOU KNOW LAKE OH I REALLY CAN'T TELL BUT HE'LL SOON TIRE OF COUNTRY LIFE" + language: English + + # female - speaker 6829 + hope: + ref_audio: cloned-voices/hope.wav + ref_text: "MISS DE GRAF SAID KENNETH NOTICING THE BOY'S FACE CRITICALLY AS HE STOOD WHERE THE LIGHT FROM THE PASSAGE FELL UPON IT" + language: English + + # female - speaker 8555 + ivy: + ref_audio: cloned-voices/ivy.wav + ref_text: "OVER THE TRACK LINED CITY STREET THE YOUNG MEN THE GRINNING MEN PASS" + language: English + + # male - speaker 6930 + atlas: + ref_audio: cloned-voices/atlas.wav + ref_text: "IT IS YOU WHO ARE MISTAKEN RAOUL I HAVE READ HIS DISTRESS IN HIS EYES IN HIS EVERY GESTURE AND ACTION THE WHOLE DAY" + language: English + + # male - speaker 1320 + caleb: + ref_audio: cloned-voices/caleb.wav + ref_text: "FOUR OR FIVE OF THE LATTER ONLY LINGERED ABOUT THE DOOR OF THE PRISON OF UNCAS WARY BUT CLOSE OBSERVERS OF THE MANNER OF THEIR CAPTIVE" + language: English + + # male - speaker 5639 + felix: + ref_audio: cloned-voices/felix.wav + ref_text: "SHE SAW THAT THE BED WAS GILDED AND SO RICH THAT IT SEEMED THAT OF A PRINCE RATHER THAN OF A PRIVATE GENTLEMAN" + language: English + + # male - speaker 260 + hugo: + ref_audio: cloned-voices/hugo.wav + ref_text: "CRIED ALICE AGAIN FOR THIS TIME THE MOUSE WAS BRISTLING ALL OVER AND SHE FELT CERTAIN IT MUST BE REALLY OFFENDED" + language: English + + # male - speaker 7729 + jasper: + ref_audio: cloned-voices/jasper.wav + ref_text: "THAT SUMMER'S EMIGRATION HOWEVER BEING MAINLY FROM THE FREE STATES GREATLY CHANGED THE RELATIVE STRENGTH OF THE TWO PARTIES" + language: English + + # male - speaker 7127 + kai: + ref_audio: cloned-voices/kai.wav + ref_text: "UPON THIS MADAME DEIGNED TO TURN HER EYES LANGUISHINGLY TOWARDS THE COMTE OBSERVING" + language: English + + # male - speaker 8230 + leo: + ref_audio: cloned-voices/leo.wav + ref_text: "THE BEHAVIOURIST WHO ATTEMPTS TO MAKE PSYCHOLOGY A RECORD OF BEHAVIOUR HAS TO TRUST HIS MEMORY IN MAKING THE RECORD" + language: English + + # male - speaker 7176 + marcus: + ref_audio: cloned-voices/marcus.wav + ref_text: "IN THE OLD BADLY MADE PLAY IT WAS FREQUENTLY NECESSARY FOR ONE OF THE CHARACTERS TO TAKE THE AUDIENCE INTO HIS CONFIDENCE" + language: English + + # male - speaker 8455 + owen: + ref_audio: cloned-voices/owen.wav + ref_text: "I DID NOT MEAN SAID CAPTAIN BATTLEAX TO TOUCH UPON PUBLIC SUBJECTS AT SUCH A MOMENT AS THIS" + language: English + + # male - speaker 2830 + theo: + ref_audio: cloned-voices/theo.wav + ref_text: "I KNEW NOTHING OF THE DOCTRINE OF FAITH BECAUSE WE WERE TAUGHT SOPHISTRY INSTEAD OF CERTAINTY AND NOBODY UNDERSTOOD SPIRITUAL BOASTING" + language: English + + # male - speaker 61 + archer: + ref_audio: cloned-voices/archer.wav + ref_text: "WHAT IS THE TUMULT AND RIOTING CRIED OUT THE SQUIRE AUTHORITATIVELY AND HE BLEW TWICE ON A SILVER WHISTLE WHICH HUNG AT HIS BELT" + language: English + + # male - speaker 672 + blake: + ref_audio: cloned-voices/blake.wav + ref_text: "IN AUTUMN THE WOOD CUTTERS ALWAYS CAME AND FELLED SOME OF THE LARGEST TREES" + language: English + + # male - speaker 908 + cole: + ref_audio: cloned-voices/cole.wav + ref_text: "LIKE THE DOVES VOICE LIKE TRANSIENT DAY LIKE MUSIC IN THE AIR AH" + language: English + + # male - speaker 1089 + dane: + ref_audio: cloned-voices/dane.wav + ref_text: "THE PRIDE OF THAT DIM IMAGE BROUGHT BACK TO HIS MIND THE DIGNITY OF THE OFFICE HE HAD REFUSED" + language: English + + # male - speaker 1188 + ezra: + ref_audio: cloned-voices/ezra.wav + ref_text: "BUT IN THIS VIGNETTE COPIED FROM TURNER YOU HAVE THE TWO PRINCIPLES BROUGHT OUT PERFECTLY" + language: English + + # male - speaker 2300 + finn: + ref_audio: cloned-voices/finn.wav + ref_text: "WHY IF WE ERECT A STATION AT THE FALLS IT IS A GREAT ECONOMY TO GET IT UP TO THE CITY" + language: English + + # male - speaker 4077 + grant: + ref_audio: cloned-voices/grant.wav + ref_text: "AT THE INCEPTION OF PLURAL MARRIAGE AMONG THE LATTER DAY SAINTS THERE WAS NO LAW NATIONAL OR STATE AGAINST ITS PRACTISE" + language: English + + # male - speaker 5105 + heath: + ref_audio: cloned-voices/heath.wav + ref_text: "AND WHAT DEMONSTRATION DO YOU OFFER ASKED SERVADAC EAGERLY THAT IT WILL NOT HAPPEN" + language: English + + # male - speaker 7021 + ivan: + ref_audio: cloned-voices/ivan.wav + ref_text: "THEN TURNING TO JANE SHE ASKED IN A SOMEWHAT ALTERED TONE HAS SHE BEEN A GOOD GIRL JANE" + language: English + + # male - speaker 8224 + jude: + ref_audio: cloned-voices/jude.wav + ref_text: "THE KING STOOD UP AND CALLED FOR THAT PSALM WHICH BEGINS WITH THESE WORDS" + language: English + +# Other TTS engines (disabled by default) +# Uncomment and configure to enable +