# Speech API

Speak text, transcribe recordings and add, change or remove your own voices from your own program
or from an extension. The gateway runs the speech engines.

The examples use the [Python SDK](python-sdk.md). To see the same steps as [HTTP API](http-api.md)
requests, select **HTTP** at the top of the page.

## When to use

- **A script must turn text into audio.** [Speak text](#speak-text).
- **A program must add a voice from a recording.** [Add a voice](#add-a-voice), then [list the
  voices](#list-the-voices).
- **A voice has a wrong name, language or transcript.** [Change a voice](#change-a-voice).
- **A voice is no longer necessary.** [Remove a voice](#remove-a-voice).
- **A program must check what an audio file says.** [Transcribe audio](#transcribe-audio).
- **An extension tool must speak or manage voices.** [Use speech in an
  extension](#use-speech-in-an-extension).

To learn what each engine does and how to record a good voice, read [Speech and voices](speech.md).

## Before you start

Connect a `GatewayClient` as in [Connect to a gateway and run a
task](python-sdk.md#connect-to-a-gateway-and-run-a-task). The examples use `client` and its
`speech` property. Its methods wait for gateway jobs and return typed records.

The first use of an engine downloads its model. Until the model is ready, a request gets status 425.

## Operations

Typed calls wait for jobs and return records. Generated methods send one request and return its
answer.

| Task | Typed call | Generated method |
|---|---|---|
| List the voices | `client.speech.voices(engine)` | `get_speech_voices(query=...)` |
| Add a voice | `client.speech.add_voice(recording, name, ...)` | `post_speech_voices(content=..., query=...)` |
| Change a voice | `client.speech.update_voice(voice_id, ...)` | `patch_speech_voice(voice_id, body=...)` |
| Remove a voice | `client.speech.remove_voice(voice_id)` | `delete_speech_voice(voice_id)` |
| Speak text | `client.speech.speak(text, ...)` | `post_speech(body=...)`, `get_speech_job(job_id)`, `get_speech_job_audio(job_id)` |
| Transcribe audio | `client.speech.transcribe(audio, ...)` | `post_voice(content=...)`, `get_voice_job(job_id)` |
| Prepare an engine | `client.speech.model(engine, prepare=True)` | `post_speech_model(query=...)` |

## Speak text

```python
audio = client.speech.speak(
    "The Great Wall is not one wall.", voice="emma", engine="vibevoice-local", seed=7
)
audio.save("out/line.wav")
```

The call returns a `SpeechAudio` record with the `content` bytes and their `media_type`. The same
`seed` gives the same take again. The call raises `VisTimeout` after `timeout` seconds.

## List the voices

```python
for voice in client.speech.voices("pocket-tts-local"):
    print(voice.id, voice.label, voice.language, voice.is_imported)
```

Each `Voice` record has an `id`, a `label` and its `engine`. A voice that you added has
`is_imported` set to true.

## Add a voice

```python
voice = client.speech.add_voice(
    "recordings/anna.wav", "Anna", language="en", transcript="Hello, this is Anna."
)
print(voice.id)
```

The recording is a path or bytes. A recording that the engine cannot use raises `GatewayError`
with status 400. A voice with the same ID is replaced, with its new audio.

## Change a voice

```python
voice = client.speech.update_voice("anna", name="Anna (studio)", transcript=None)
```

An argument that you omit keeps its value. `None` or an empty string removes the value. The ID
stays the same. A voice that you did not add raises `GatewayError` with status 404.

## Remove a voice

```python
removed = client.speech.remove_voice("anna")
```

The call returns false when the engine has no voice with this ID.

## Transcribe audio

```python
transcript = client.speech.transcribe("out/line.wav", engine="vibevoice-asr-local")
print(transcript.text)
```

The audio must be a 16-bit PCM WAV file. The `Transcript` record has the `text` and its `segments`.

## Use speech in an extension

```python
from blockether.vis import extension as vis


class Narrator:
    def say(self, text: str, path: str) -> str:
        """Speak `text` and save the audio at `path`."""
        return str(vis.speech.speak(text, voice="emma").save(path))
```

`vis.speech` has the same methods as `client.speech`. Add, change or remove voices only while a tool
call runs. Background work of an extension can list voices, speak and transcribe.

## See also

- [Speech and voices](speech.md) — engines, good recordings and voice IDs.
- [Extension API](extension-api.md) — declarations and host operations for extension tools.
- [Python SDK](python-sdk.md) — install the SDK, connect a client and handle gateway errors.
- [HTTP API](http-api.md) — authentication, the OpenAPI document and gateway errors.
