diff --git a/README.md b/README.md index 95f87bd..1572c98 100644 --- a/README.md +++ b/README.md @@ -48,33 +48,38 @@ uvx pocket-tts generate pocket-tts generate ``` Modify the voice with `--voice` and the text with `--text`. We provide a small catalog of voices. -Choose a pretrained language model with `--language` when running `generate`, `export-voice`, or `serve` (default: `english`). +Choose a pretrained language model with `--language` when running `generate`, `export-voice`, or `serve` (default: `english`). Non-english languages have also biggers 24 layers variants that are higher quality but slower. You can select them by using for example `--language italian_24l`. The `--config` option accepts only a local YAML path for custom weights. You can take a look at [this page](https://huggingface.co/kyutai/tts-voices) which details the licenses for each voice. -* [alba](https://huggingface.co/kyutai/tts-voices/blob/main/alba-mackenna/casual.wav) -* [anna](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p228_023_enhanced.wav) -* [azelma](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p303_023_enhanced.wav) -* [bill_boerst](https://huggingface.co/kyutai/tts-voices/blob/main/voice-zero/bill_boerst.wav) -* [caro_davy](https://huggingface.co/kyutai/tts-voices/blob/main/voice-zero/caro_davy.wav) -* [charles](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p254_023_enhanced.wav) -* [cosette](https://huggingface.co/kyutai/tts-voices/blob/main/expresso/ex04-ex02_confused_001_channel1_499s.wav) -* [eponine](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p262_023_enhanced.wav) -* [eve](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p361_023_enhanced.wav) -* [fantine](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p244_023_enhanced.wav) -* [george](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p315_023_enhanced.wav) -* [jane](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p339_023_enhanced.wav) -* [jean](https://huggingface.co/kyutai/tts-voices/blob/main/ears/p010/freeform_speech_01_enhanced.wav) -* [javert](https://huggingface.co/kyutai/tts-voices/blob/main/voice-donations/Butter.wav) -* [marius](https://huggingface.co/kyutai/tts-voices/blob/main/voice-donations/Selfie.wav) -* [mary](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p333_023_enhanced.wav) -* [michael](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p360_023_enhanced.wav) -* [paul](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p259_023_enhanced.wav) -* [peter_yearsley](https://huggingface.co/kyutai/tts-voices/blob/main/voice-zero/peter_yearsley.wav) -* [stuart_bell](https://huggingface.co/kyutai/tts-voices/blob/main/voice-zero/stuart_bell.wav) -* [vera](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p229_023_enhanced.wav) +* [alba](https://huggingface.co/kyutai/tts-voices/blob/main/alba-mackenna/casual.wav) (en) +* [giovanni](https://huggingface.co/kyutai/pocket-tts/blob/add_lang_not_documented/common_voice_it_36520747-enhanced-v2.mp3) (it) +* [lola](https://huggingface.co/kyutai/pocket-tts/blob/add_lang_not_documented/common_voice_es_19762977-enhanced-v2.mp3) (es) +* [juergen](https://huggingface.co/kyutai/pocket-tts/blob/add_lang_not_documented/de-DE-juergen.mp3) (de) +* [rafael](https://huggingface.co/kyutai/pocket-tts/blob/add_lang_not_documented/g-Vi8PgmSY0-enhanced-v2.wav) (pt) +* [estelle](https://huggingface.co/kyutai/tts-voices/blob/main/unmute-prod-website/developpeuse-3.wav) (fr) +* [anna](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p228_023_enhanced.wav) (en) +* [azelma](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p303_023_enhanced.wav) (en) +* [bill_boerst](https://huggingface.co/kyutai/tts-voices/blob/main/voice-zero/bill_boerst.wav) (en) +* [caro_davy](https://huggingface.co/kyutai/tts-voices/blob/main/voice-zero/caro_davy.wav) (en) +* [charles](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p254_023_enhanced.wav) (en) +* [cosette](https://huggingface.co/kyutai/tts-voices/blob/main/expresso/ex04-ex02_confused_001_channel1_499s.wav) (en) +* [eponine](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p262_023_enhanced.wav) (en) +* [eve](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p361_023_enhanced.wav) (en) +* [fantine](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p244_023_enhanced.wav) (en) +* [george](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p315_023_enhanced.wav) (en) +* [jane](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p339_023_enhanced.wav) (en) +* [jean](https://huggingface.co/kyutai/tts-voices/blob/main/ears/p010/freeform_speech_01_enhanced.wav) (en) +* [javert](https://huggingface.co/kyutai/tts-voices/blob/main/voice-donations/Butter.wav) (en) +* [marius](https://huggingface.co/kyutai/tts-voices/blob/main/voice-donations/Selfie.wav) (en) +* [mary](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p333_023_enhanced.wav) (en) +* [michael](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p360_023_enhanced.wav) (en) +* [paul](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p259_023_enhanced.wav) (en) +* [peter_yearsley](https://huggingface.co/kyutai/tts-voices/blob/main/voice-zero/peter_yearsley.wav) (en) +* [stuart_bell](https://huggingface.co/kyutai/tts-voices/blob/main/voice-zero/stuart_bell.wav) (en) +* [vera](https://huggingface.co/kyutai/tts-voices/blob/main/vctk/p229_023_enhanced.wav) (en) The `--voice` argument can also take a plain wav file as input for voice cloning. You can use your own or check out our [voice repository](https://huggingface.co/kyutai/tts-voices). diff --git a/docs/CLI Commands/generate.md b/docs/CLI Commands/generate.md index c60d155..a708eac 100644 --- a/docs/CLI Commands/generate.md +++ b/docs/CLI Commands/generate.md @@ -17,7 +17,7 @@ This will generate a WAV file `./tts_output.wav` with the default text and voice ### Core Options - `--text TEXT`: Text to generate (default: "Hello world! I am Kyutai Pocket TTS. I'm fast enough to run on small CPUs. I hope you'll like me.") -- `--voice VOICE`: Path to audio conditioning file (voice to clone) (default: "hf://kyutai/tts-voices/alba-mackenna/casual.wav"). Urls and local paths are supported. +- `--voice VOICE`: Path to audio conditioning file (voice to clone). Defaults to a built-in voice chosen from the language: `giovanni` (it) for italian, `lola` (es) for spanish, `alba` (en) otherwise. Urls and local paths are supported. - `--output-path OUTPUT_PATH`: Output path for generated audio (default: "./tts_output.wav") - `--language LANGUAGE`: Language for the TTS model, one of `'english_2026-01'`, `'english_2026-04'`, `'english'`, `'french_24l'`, `'german_24l'`, `'portuguese_24l'`, `'italian_24l'`, `'spanish_24l'` (default: `english`, which is the same model as `'english_2026-04'`). Incompatible with `--config`. The "24l" variants are bigger models, not distilled yet and here only as preview. diff --git a/pocket_tts/default_parameters.py b/pocket_tts/default_parameters.py index 5613f2a..bffa304 100644 --- a/pocket_tts/default_parameters.py +++ b/pocket_tts/default_parameters.py @@ -1,4 +1,3 @@ -DEFAULT_AUDIO_PROMPT = "alba" DEFAULT_LANGUAGE = "english" DEFAULT_TEMPERATURE = 0.7 DEFAULT_LSD_DECODE_STEPS = 1 @@ -41,9 +40,25 @@ DEFAULT_TEXT_FOR_LANGUAGE = { ), } +DEFAULT_VOICE_FOR_LANGUAGE = { + "italian": "giovanni", + "spanish": "lola", + "german": "juergen", + "portuguese": "rafael", + "french": "estelle", +} +DEFAULT_VOICE_FALLBACK = "alba" + def get_default_text_for_language(language: str | None) -> str: for key, text in DEFAULT_TEXT_FOR_LANGUAGE.items(): if language is not None and key in language: return text return DEFAULT_TEXT_FOR_LANGUAGE[DEFAULT_LANGUAGE] + + +def get_default_voice_for_language(language: str | None) -> str: + for key, voice in DEFAULT_VOICE_FOR_LANGUAGE.items(): + if language is not None and key in language: + return voice + return DEFAULT_VOICE_FALLBACK diff --git a/pocket_tts/main.py b/pocket_tts/main.py index c040425..4a172fc 100644 --- a/pocket_tts/main.py +++ b/pocket_tts/main.py @@ -16,7 +16,6 @@ from typing_extensions import Annotated from pocket_tts.data.audio import stream_audio_chunks from pocket_tts.default_parameters import ( - DEFAULT_AUDIO_PROMPT, DEFAULT_EOS_THRESHOLD, DEFAULT_FRAMES_AFTER_EOS, DEFAULT_LSD_DECODE_STEPS, @@ -24,6 +23,7 @@ from pocket_tts.default_parameters import ( DEFAULT_TEMPERATURE, MAX_TOKEN_PER_CHUNK, get_default_text_for_language, + get_default_voice_for_language, ) from pocket_tts.models.tts_model import TTSModel, export_model_state from pocket_tts.utils.logging_utils import enable_logging @@ -136,7 +136,7 @@ def text_to_speech( raise HTTPException(status_code=400, detail="Text cannot be empty") if voice_url is None and voice_wav is None: - voice_url = DEFAULT_AUDIO_PROMPT + voice_url = get_default_voice_for_language(str(tts_model.origin)) if voice_url is not None and voice_wav is not None: raise HTTPException(status_code=400, detail="Cannot provide both voice_url and voice_wav") @@ -223,8 +223,17 @@ def serve( def generate( text: Annotated[str, typer.Option(help="Text to generate")] = None, voice: Annotated[ - str, typer.Option(help="Path to audio conditioning file (voice to clone)") - ] = DEFAULT_AUDIO_PROMPT, + str | None, + typer.Option( + help=( + "Path to audio conditioning file (voice to clone). " + "Defaults to a built-in voice chosen from the language: " + "'giovanni' for italian, 'lola' for spanish, 'juergen' for german, " + "'rafael' for portuguese, 'estelle' for french, 'alba' otherwise." + ), + show_default=False, + ), + ] = None, quiet: Annotated[bool, typer.Option("-q", "--quiet", help="Disable logging output")] = False, language: Annotated[ str | None, @@ -293,6 +302,8 @@ def generate( ) tts_model.to(device) + if voice is None: + voice = get_default_voice_for_language(language) model_state_for_voice = tts_model.get_state_for_audio_prompt(voice) # Stream audio generation directly to file or stdout audio_chunks = tts_model.generate_audio_stream( diff --git a/pocket_tts/models/tts_model.py b/pocket_tts/models/tts_model.py index 04718d0..592a832 100644 --- a/pocket_tts/models/tts_model.py +++ b/pocket_tts/models/tts_model.py @@ -295,7 +295,7 @@ class TTSModel(nn.Module): if language is not None: if language == "french": raise ValueError( - "The french model is not ready yet, please use 'french_24l' instead." + "For technical reasons, only a larger 24-layer model is available for French. Please use the 'french_24l' language instead." ) config = CONFIGS_DIR / f"{language}.yaml" config = Path(config) diff --git a/pocket_tts/static/index.html b/pocket_tts/static/index.html index efcf3e1..b3fedbe 100644 --- a/pocket_tts/static/index.html +++ b/pocket_tts/static/index.html @@ -40,13 +40,15 @@ id="voice-url-input" class="w-full border rounded p-2" placeholder="hf://kyutai/tts-voices/alba-mackenna/casual.wav" - value="alba" />

Supports: http://, https://, or hf:// URLs.
- You can also use predefined voices:
- "alba", "anna", "azelma", "bill_boerst", "caro_davy", "charles", "eponine", "eve", - "fantine", "george", "jane", "jean", "mary", "michael", "paul", "peter_yearsley", "stuart_bell", "vera". + You can also use predefined voices (language code in parentheses):
+ "alba" (en), "anna" (en), "azelma" (en), "bill_boerst" (en), "caro_davy" (en), + "charles" (en), "cosette" (en), "eponine" (en), "eve" (en), "fantine" (en), + "george" (en), "jane" (en), "javert" (en), "jean" (en), + "marius" (en), "mary" (en), "michael" (en), "paul" (en), + "peter_yearsley" (en), "stuart_bell" (en), "vera" (en), "giovanni" (it), "lola" (es), "juergen" (de), "rafael" (pt), "estelle" (fr).
You can find more voices in our str: - return f"hf://kyutai/pocket-tts-without-voice-cloning/languages/{language}/embeddings/{name}.safetensors@d29db7978e464fb90cb3359ee0c69a273b9142cc" + return f"hf://kyutai/pocket-tts-without-voice-cloning/languages/{language}/embeddings/{name}.safetensors@e041936c75475d350b405bc870bcf7c22da4e9e6" def make_cache_directory() -> Path: diff --git a/scripts/generate_default_voices.py b/scripts/generate_default_voices.py index b145c67..636b179 100644 --- a/scripts/generate_default_voices.py +++ b/scripts/generate_default_voices.py @@ -1,26 +1,29 @@ -import sys - -import scipy.io.wavfile +from pathlib import Path from pocket_tts import TTSModel, export_model_state from pocket_tts.utils.utils import _ORIGINS_OF_PREDEFINED_VOICES -model = TTSModel.load_model(language=sys.argv[1]) +languages = [x.stem for x in Path("./pocket_tts/config").glob("*.yaml")] -for voice_name, voice_origin in _ORIGINS_OF_PREDEFINED_VOICES.items(): - print(f"Processing voice: {voice_name} from origin: {voice_origin}") - # Export a voice state for fast loading later - model_state = model.get_state_for_audio_prompt(voice_origin) - export_model_state(model_state, f"./built-in-voices/{voice_name}.safetensors") - model_state_copy = model.get_state_for_audio_prompt( - f"./built-in-voices/{voice_name}.safetensors" - ) +for language in languages: + model = TTSModel.load_model(language=language) - audio = model.generate_audio( - model_state_copy, - "An Sommernachmittagen spielten die Kinder fröhlich auf dem Platz der kleinen Stadt.", - ) - scipy.io.wavfile.write( - f"./built-in-voices-generated/{voice_name}.wav", model.sample_rate, audio.numpy() - ) + for voice_name, voice_origin in _ORIGINS_OF_PREDEFINED_VOICES.items(): + print( + f"Processing voice: {voice_name} from origin: {voice_origin} for language: {language}" + ) + # Export a voice state for fast loading later + model_state = model.get_state_for_audio_prompt(voice_origin) + dest = f"/projects/huggingface/pocket-tts/languages/{language}/embeddings/{voice_name}.safetensors" + export_model_state(model_state, dest) + + model_state_copy = model.get_state_for_audio_prompt(dest) + + # audio = model.generate_audio( + # model_state_copy, + # "An Sommernachmittagen spielten die Kinder fröhlich auf dem Platz der kleinen Stadt.", + # ) + # scipy.io.wavfile.write( + # f"./built-in-voices-generated/{voice_name}.wav", model.sample_rate, audio.numpy() + # )