diff --git a/api-reference/sdk/python/client.mdx b/api-reference/sdk/python/client.mdx
index ea7ac07..1819c9c 100644
--- a/api-reference/sdk/python/client.mdx
+++ b/api-reference/sdk/python/client.mdx
@@ -4,8 +4,6 @@
Main Fish Audio client classes.
-Install the Python SDK first: `pip install fish-audio-sdk`.
-
## FishAudio Objects
diff --git a/api-reference/sdk/python/core.mdx b/api-reference/sdk/python/core.mdx
index d44a7ae..dd5a279 100644
--- a/api-reference/sdk/python/core.mdx
+++ b/api-reference/sdk/python/core.mdx
@@ -1,11 +1,48 @@
+
+
+# fishaudio.core.omit
+
+OMIT sentinel for distinguishing None from not-provided parameters.
+
+
+
+# fishaudio.core.request\_options
+
+Request-level options for API calls.
+
+
+
+## RequestOptions Objects
+
+```python
+class RequestOptions()
+```
+
+Options that can be provided on a per-request basis to override client defaults.
+
+**Attributes**:
+
+- `timeout` - Override the client's default timeout (in seconds)
+- `max_retries` - Override the client's default max retries
+- `additional_headers` - Additional headers to include in the request
+- `additional_query_params` - Additional query parameters to include
+
+
+
+#### get\_timeout
+
+```python
+def get_timeout() -> Optional[httpx.Timeout]
+```
+
+Convert timeout to httpx.Timeout if set.
+
# fishaudio.core.client\_wrapper
HTTP client wrapper for managing requests and authentication.
-Install the Python SDK first: `pip install fish-audio-sdk`.
-
## BaseClientWrapper Objects
@@ -151,38 +188,48 @@ async def close() -> None
Close the HTTP client.
-
+
-# fishaudio.core.request\_options
+# fishaudio.core.\_ws\_utils
-Request-level options for API calls.
+Internal helpers for synchronous httpx-ws sessions.
-
+
-## RequestOptions Objects
+#### drain\_reader
```python
-class RequestOptions()
+def drain_reader(ws: Any) -> None
```
-Options that can be provided on a per-request basis to override client defaults.
-
-**Attributes**:
-
-- `timeout` - Override the client's default timeout (in seconds)
-- `max_retries` - Override the client's default max retries
-- `additional_headers` - Additional headers to include in the request
-- `additional_query_params` - Additional query parameters to include
-
-
-
-#### get\_timeout
-
-```python
-def get_timeout() -> Optional[httpx.Timeout]
-```
-
-Convert timeout to httpx.Timeout if set.
+Send the close frame, shut the socket down, and wait for httpx-ws's reader
+thread to exit before the session's own ``close()`` frees the fd.
+
+Why this is needed (Linux only in practice):
+
+httpx-ws's sync ``WebSocketSession`` reads on a background thread and closes the
+socket from the calling thread. A read that has *not started yet* is harmless:
+CPython's ``SSLSocket._real_close`` sets ``_sslobj = None`` before closing the
+fd, so a later read falls through to ``socket.recv`` on fd ``-1`` and fails with
+``EBADF``. The problem is the read that is *already in flight*. On Linux,
+``close()`` does not interrupt a thread blocked in ``recv()``. OpenSSL (with
+read_ahead off) fetches a TLS record header and body with separate ``read(fd)``
+calls on the fd number cached in its BIO, so when the server's close-frame reply
+arrives the second ``read()`` can land on a brand-new connection that has since
+reused the same fd number, and that new session fails with
+``[SSL: WRONG_VERSION_NUMBER]`` or a similar TLS error. macOS wakes the blocked
+reader with ``EBADF`` on ``close()``, so it is unaffected.
+
+The fix is to end the in-flight read *before* the fd is freed: send the close
+frame (so the server still sees a clean 1000 close rather than 1006), then
+``shutdown(SHUT_RDWR)`` the socket, which wakes the blocked reader with EOF, and
+join the reader thread. The later ``close()`` sees ``LOCAL_CLOSING`` and skips
+sending a second frame.
+
+Residual risk: if httpx-ws has already closed the socket on one of its own error
+paths (keepalive ping timeout, or a sender ``WriteError`` triggering ``close()``),
+the fd is already freed, ``shutdown()`` fails with ``EBADF``, and the in-flight
+read can still race. That can only be fixed inside httpx-ws's ``close()``.
@@ -396,9 +443,3 @@ def to_httpx_ws_kwargs() -> dict[str, Any]
Convert to kwargs dict for httpx_ws aconnect_ws/connect_ws.
-
-
-# fishaudio.core.omit
-
-OMIT sentinel for distinguishing None from not-provided parameters.
-
diff --git a/api-reference/sdk/python/overview.mdx b/api-reference/sdk/python/overview.mdx
index 2be95a1..97cb7ac 100644
--- a/api-reference/sdk/python/overview.mdx
+++ b/api-reference/sdk/python/overview.mdx
@@ -39,7 +39,7 @@ pip install fish-audio-sdk[utils]
## Authentication
-Get your API key from [Get Started](https://fish.audio/app/developers/get-started/?utm_source=docs&utm_medium=python-sdk&utm_content=get-api-key):
+Get your API key from [fish.audio/app/api-keys](https://fish.audio/app/api-keys/?utm_source=github&utm_medium=sdk-readme&utm_content=python):
```bash
export FISH_API_KEY=your_api_key_here
@@ -55,8 +55,6 @@ client = FishAudio(api_key="your_api_key")
## Quick Start
-Most examples on this page pass `model="s2.1-pro-free"`, the free model, so they work before you add API credit. Without `model=`, the SDK uses a paid model.
-
**Synchronous:**
```python
@@ -66,7 +64,7 @@ from fishaudio.utils import play, save
client = FishAudio()
# Generate audio
-audio = client.tts.convert(text="Hello, world!", model="s2.1-pro-free")
+audio = client.tts.convert(text="Hello, world!")
# Play or save
play(audio)
@@ -82,7 +80,7 @@ from fishaudio.utils import play, save
async def main():
client = AsyncFishAudio()
- audio = await client.tts.convert(text="Hello, world!", model="s2.1-pro-free")
+ audio = await client.tts.convert(text="Hello, world!")
play(audio)
save(audio, "output.mp3")
@@ -93,14 +91,35 @@ asyncio.run(main())
### Text-to-Speech
+**Selecting a model:**
+
+```python
+# Recommended for production
+production_audio = client.tts.convert(
+ text="Production speech",
+ model="s2.1-pro",
+)
+
+# Free to use, including on a new account with no API credit
+free_audio = client.tts.convert(
+ text="Free speech",
+ model="s2.1-pro-free",
+)
+
+# Drama 3: our most expressive model (uses API credit)
+drama_audio = client.tts.convert(
+ text="Expressive speech",
+ model="drama-3",
+)
+```
+
**With custom voice:**
```python
# Use a specific voice by ID
audio = client.tts.convert(
text="Custom voice",
- reference_id="9a9cf47702da476aa4629e2506d4a857",
- model="s2.1-pro-free",
+ reference_id="802e3bc2b27e49c2995d23ef70e6ac89"
)
```
@@ -109,8 +128,7 @@ audio = client.tts.convert(
```python
audio = client.tts.convert(
text="Speaking faster!",
- speed=1.5, # 1.5x speed
- model="s2.1-pro-free",
+ speed=1.5 # 1.5x speed
)
```
@@ -127,47 +145,51 @@ config = TTSConfig(
)
# Reuse across generations
-audio1 = client.tts.convert(text="First message", config=config, model="s2.1-pro-free")
-audio2 = client.tts.convert(text="Second message", config=config, model="s2.1-pro-free")
+audio1 = client.tts.convert(text="First message", config=config)
+audio2 = client.tts.convert(text="Second message", config=config)
```
**Chunk-by-chunk processing:**
```python
# Stream and process chunks as they arrive
-for chunk in client.tts.stream(text="Long content...", model="s2.1-pro-free"):
+for chunk in client.tts.stream(text="Long content..."):
send_to_websocket(chunk)
# Or collect all chunks
-audio = client.tts.stream(text="Hello!", model="s2.1-pro-free").collect()
+audio = client.tts.stream(text="Hello!").collect()
```
-[Learn more](https://docs.fish.audio/features/text-to-speech)
+[Learn more](https://docs.fish.audio/developer-guide/sdk-guide/python/text-to-speech)
### Speech-to-Text
```python
from fishaudio.core import RequestOptions
-# Transcribe audio with transcribe-1-pro (recommended).
-# Without the model header, the request runs on transcribe-1.
-with open("audio.wav", "rb") as f:
- result = client.asr.transcribe(
- audio=f.read(),
- language="en",
- request_options=RequestOptions(
- additional_headers={"model": "transcribe-1-pro"}
- ),
- )
+# transcribe-1-pro is the recommended model. Select it with the model header;
+# without it, the request runs on transcribe-1.
+pro = RequestOptions(additional_headers={"model": "transcribe-1-pro"})
+
+with open("meeting.wav", "rb") as f:
+ result = client.asr.transcribe(audio=f.read(), request_options=pro)
+# On transcribe-1-pro, text keeps <|speaker:N|> markers; speaker_turns has clean text per speaker.
print(result.text)
-# Access timestamped segments
+# Word-level timestamps
for segment in result.segments:
print(f"[{segment.start:.2f}s - {segment.end:.2f}s] {segment.text}")
+
+# Speaker turns (transcribe-1-pro, returned with timestamps)
+for turn in result.speaker_turns or []:
+ print(f"[{turn.start:.2f}s - {turn.end:.2f}s] {turn.speaker}: {turn.text}")
+
+# Language of the transcript, when the API returns it
+print(result.language_code) # e.g. "en"
```
-[Learn more](https://docs.fish.audio/features/speech-to-text)
+[Learn more](https://docs.fish.audio/developer-guide/sdk-guide/python/speech-to-text)
### Real-time Streaming
@@ -181,33 +203,23 @@ def text_chunks():
yield "this is "
yield "streaming!"
-audio_stream = client.tts.stream_websocket(text_chunks(), latency="balanced", model="s2.1-pro-free")
+audio_stream = client.tts.stream_websocket(text_chunks(), latency="balanced")
play(audio_stream)
```
**Asynchronous:**
```python
-import asyncio
-from fishaudio import AsyncFishAudio
-
async def text_chunks():
yield "Hello, "
yield "this is "
yield "streaming!"
-async def main():
- async with AsyncFishAudio() as client:
- # stream_websocket is an async generator: iterate it, don't await the call
- audio_stream = client.tts.stream_websocket(text_chunks(), latency="balanced", model="s2.1-pro-free")
- with open("out.mp3", "wb") as f:
- async for chunk in audio_stream:
- f.write(chunk)
-
-asyncio.run(main())
+audio_stream = await client.tts.stream_websocket(text_chunks(), latency="balanced")
+play(audio_stream)
```
-[Learn more](https://docs.fish.audio/features/realtime-streaming)
+[Learn more](https://docs.fish.audio/developer-guide/sdk-guide/python/websocket)
### Voice Cloning
@@ -223,8 +235,7 @@ with open("reference.wav", "rb") as f:
references=[ReferenceAudio(
audio=f.read(),
text="Text spoken in reference"
- )],
- model="s2.1-pro-free",
+ )]
)
```
@@ -246,7 +257,7 @@ audio = client.tts.convert(
)
```
-[Learn more](https://docs.fish.audio/features/voice-cloning)
+[Learn more](https://docs.fish.audio/developer-guide/sdk-guide/python/voice-cloning)
## Resource Clients
@@ -263,23 +274,20 @@ audio = client.tts.convert(
from fishaudio.exceptions import (
AuthenticationError,
RateLimitError,
- NotFoundError,
- APIError,
- FishAudioError,
+ ValidationError,
+ FishAudioError
)
try:
- audio = client.tts.convert(text="Hello!", model="s2.1-pro-free")
+ audio = client.tts.convert(text="Hello!")
except AuthenticationError:
print("Invalid API key")
except RateLimitError:
print("Rate limit exceeded")
-except NotFoundError:
- print("Voice model not found")
-except APIError as e:
- print(f"API error {e.status}: {e.message}") # any other HTTP error, including 422 validation
+except ValidationError as e:
+ print(f"Invalid request: {e}")
except FishAudioError as e:
- print(f"SDK error: {e}")
+ print(f"API error: {e}")
```
## Resources
diff --git a/api-reference/sdk/python/resources.mdx b/api-reference/sdk/python/resources.mdx
index c616f00..018a229 100644
--- a/api-reference/sdk/python/resources.mdx
+++ b/api-reference/sdk/python/resources.mdx
@@ -1,511 +1,390 @@
-
-
-# fishaudio.resources.voices
+
-Voice management namespace client.
+# fishaudio.resources.asr
-Install the Python SDK first: `pip install fish-audio-sdk`.
+ASR (Automatic Speech Recognition) namespace client.
-
+
-## VoicesClient Objects
+## ASRClient Objects
```python
-class VoicesClient()
+class ASRClient()
```
-Synchronous voice management operations.
+Synchronous ASR operations.
-
+
-#### list
+#### transcribe
```python
-def list(
- *,
- page_size: int = 10,
- page_number: int = 1,
- title: Optional[str] = OMIT,
- tags: Optional[Union[list[str], str]] = OMIT,
- self_only: bool = False,
- author_id: Optional[str] = OMIT,
- language: Optional[Union[list[str], str]] = OMIT,
- title_language: Optional[Union[list[str], str]] = OMIT,
- sort_by: str = "task_count",
- request_options: Optional[RequestOptions] = None
-) -> PaginatedResponse[Voice]
+def transcribe(
+ *,
+ audio: bytes,
+ language: Optional[str] = OMIT,
+ include_timestamps: bool = True,
+ request_options: Optional[RequestOptions] = None) -> ASRResponse
```
-List available voices/models.
+Transcribe audio to text.
**Arguments**:
-- `page_size` - Number of results per page
-- `page_number` - Page number (1-indexed)
-- `title` - Filter by title
-- `tags` - Filter by tags (single tag or list)
-- `self_only` - Only return user's own voices
-- `author_id` - Filter by author ID
-- `language` - Filter by language(s)
-- `title_language` - Filter by title language(s)
-- `sort_by` - Sort field ("task_count" or "created_at")
+- `audio` - Audio file bytes
+- `language` - Language code (e.g., "en", "zh"). Auto-detected if not provided.
+- `include_timestamps` - Whether to include timestamp information for segments
+ (and speaker turns, on models that return them)
- `request_options` - Request-level overrides
**Returns**:
- Paginated response with total count and voice items
+ ASRResponse with transcription text, duration (in seconds), and segments,
+ plus language, request ID and speaker turns when the API returns them
**Example**:
```python
+ from fishaudio.core import RequestOptions
+
client = FishAudio(api_key="...")
- # List all voices
- voices = client.voices.list(page_size=20)
- print(f"Total: {voices.total}")
- for voice in voices.items:
- print(f"{voice.title}: {voice.id}")
+ with open("audio.mp3", "rb") as f:
+ audio_bytes = f.read()
- # Filter by tags
- tagged = client.voices.list(tags=["male", "english"])
+ result = client.asr.transcribe(
+ audio=audio_bytes,
+ request_options=RequestOptions(
+ additional_headers={"model": "transcribe-1-pro"}
+ ),
+ )
+ print(result.text)
+ for segment in result.segments:
+ print(f"{segment.start}-{segment.end}: {segment.text}")
```
-
+
-#### get
+## AsyncASRClient Objects
```python
-def get(voice_id: str,
+class AsyncASRClient()
+```
+
+Asynchronous ASR operations.
+
+
+
+#### transcribe
+
+```python
+async def transcribe(
*,
- request_options: Optional[RequestOptions] = None) -> Voice
+ audio: bytes,
+ language: Optional[str] = OMIT,
+ include_timestamps: bool = True,
+ request_options: Optional[RequestOptions] = None) -> ASRResponse
```
-Get voice by ID.
+Transcribe audio to text (async).
**Arguments**:
-- `voice_id` - Voice model ID
+- `audio` - Audio file bytes
+- `language` - Language code (e.g., "en", "zh"). Auto-detected if not provided.
+- `include_timestamps` - Whether to include timestamp information for segments
+ (and speaker turns, on models that return them)
- `request_options` - Request-level overrides
**Returns**:
- Voice model details
+ ASRResponse with transcription text, duration (in seconds), and segments,
+ plus language, request ID and speaker turns when the API returns them
**Example**:
```python
- client = FishAudio(api_key="...")
- voice = client.voices.get("voice_id_here")
- print(voice.title, voice.description)
- ```
-
-
+ from fishaudio.core import RequestOptions
-#### create
+ client = AsyncFishAudio(api_key="...")
-```python
-def create(*,
- title: str,
- voices: builtins.list[bytes],
- description: Optional[str] = OMIT,
- texts: Optional[builtins.list[str]] = OMIT,
- tags: Optional[builtins.list[str]] = OMIT,
- cover_image: Optional[bytes] = OMIT,
- visibility: Visibility = "private",
- train_mode: str = "fast",
- enhance_audio_quality: bool = True,
- request_options: Optional[RequestOptions] = None) -> Voice
-```
+ async with aiofiles.open("audio.mp3", "rb") as f:
+ audio_bytes = await f.read()
-Create/clone a new voice.
+ result = await client.asr.transcribe(
+ audio=audio_bytes,
+ request_options=RequestOptions(
+ additional_headers={"model": "transcribe-1-pro"}
+ ),
+ )
+ print(result.text)
+ for segment in result.segments:
+ print(f"{segment.start}-{segment.end}: {segment.text}")
+ ```
-**Arguments**:
+
-- `title` - Voice model name
-- `voices` - List of audio file bytes for training
-- `description` - Voice description
-- `texts` - Transcripts for voice samples
-- `tags` - Tags for categorization
-- `cover_image` - Cover image bytes
-- `visibility` - Visibility setting (public, unlist, private)
-- `train_mode` - Training mode (currently only "fast" supported)
-- `enhance_audio_quality` - Whether to enhance audio quality
-- `request_options` - Request-level overrides
-
+# fishaudio.resources.tts
-**Returns**:
+TTS (Text-to-Speech) namespace client.
- Created voice model
-
+
-**Example**:
+## TTSClient Objects
- ```python
- client = FishAudio(api_key="...")
+```python
+class TTSClient()
+```
- with open("voice1.wav", "rb") as f1, open("voice2.wav", "rb") as f2:
- voice = client.voices.create(
- title="My Voice",
- voices=[f1.read(), f2.read()],
- description="Custom voice clone",
- tags=["custom", "english"]
- )
- print(f"Created: {voice.id}")
- ```
+Synchronous TTS operations.
-
+
-#### update
+#### stream
```python
-def update(voice_id: str,
- *,
- title: Optional[str] = OMIT,
- description: Optional[str] = OMIT,
- cover_image: Optional[bytes] = OMIT,
- visibility: Optional[Visibility] = OMIT,
- tags: Optional[builtins.list[str]] = OMIT,
- request_options: Optional[RequestOptions] = None) -> None
+def stream(*,
+ text: str,
+ reference_id: Optional[str] = None,
+ references: Optional[list[ReferenceAudio]] = None,
+ format: Optional[AudioFormat] = None,
+ latency: Optional[LatencyMode] = None,
+ speed: Optional[float] = None,
+ config: TTSConfig = TTSConfig(),
+ model: Union[Model, str] = "s2.1-pro",
+ request_options: Optional[RequestOptions] = None) -> AudioStream
```
-Update voice metadata.
+Stream text-to-speech audio chunks.
**Arguments**:
-- `voice_id` - Voice model ID
-- `title` - New title
-- `description` - New description
-- `cover_image` - New cover image bytes
-- `visibility` - New visibility setting
-- `tags` - New tags
+- `text` - Text to synthesize
+- `reference_id` - Voice reference ID (overrides config.reference_id if provided)
+- `references` - Reference audio samples (overrides config.references if provided)
+- `format` - Audio format - "mp3", "wav", "pcm", or "opus" (overrides config.format if provided)
+- `latency` - Latency mode - "normal" or "balanced" (overrides config.latency if provided)
+- `speed` - Speech speed multiplier, e.g. 1.5 for 1.5x speed (overrides config.prosody.speed if provided)
+- `config` - TTS configuration (audio settings, voice, model parameters)
+- `model` - TTS model to use
- `request_options` - Request-level overrides
+**Returns**:
+
+ AudioStream object that can be iterated for audio chunks
+
+
**Example**:
```python
+ from fishaudio import FishAudio
+
client = FishAudio(api_key="...")
- client.voices.update(
- "voice_id_here",
- title="Updated Title",
- visibility="public"
- )
+
+ # Stream and process chunks
+ for chunk in client.tts.stream(text="Hello world"):
+ process_audio_chunk(chunk)
+
+ # Or collect all at once
+ audio = client.tts.stream(text="Hello world").collect()
```
-
+
-#### delete
+#### convert
```python
-def delete(voice_id: str,
- *,
- request_options: Optional[RequestOptions] = None) -> None
+def convert(*,
+ text: str,
+ reference_id: Optional[str] = None,
+ references: Optional[list[ReferenceAudio]] = None,
+ format: Optional[AudioFormat] = None,
+ latency: Optional[LatencyMode] = None,
+ speed: Optional[float] = None,
+ config: TTSConfig = TTSConfig(),
+ model: Union[Model, str] = "s2.1-pro",
+ request_options: Optional[RequestOptions] = None) -> bytes
```
-Delete a voice.
+Convert text to speech and return complete audio as bytes.
+
+This is a convenience method that streams all audio chunks and combines them.
+For chunk-by-chunk processing, use stream() instead.
**Arguments**:
-- `voice_id` - Voice model ID
+- `text` - Text to synthesize
+- `reference_id` - Voice reference ID (overrides config.reference_id if provided)
+- `references` - Reference audio samples (overrides config.references if provided)
+- `format` - Audio format - "mp3", "wav", "pcm", or "opus" (overrides config.format if provided)
+- `latency` - Latency mode - "normal" or "balanced" (overrides config.latency if provided)
+- `speed` - Speech speed multiplier, e.g. 1.5 for 1.5x speed (overrides config.prosody.speed if provided)
+- `config` - TTS configuration (audio settings, voice, model parameters)
+- `model` - TTS model to use
- `request_options` - Request-level overrides
+**Returns**:
+
+ Complete audio as bytes
+
+
**Example**:
```python
+ from fishaudio import FishAudio
+ from fishaudio.utils import play, save
+
client = FishAudio(api_key="...")
- client.voices.delete("voice_id_here")
- ```
-
-
-## AsyncVoicesClient Objects
-
-```python
-class AsyncVoicesClient()
-```
-
-Asynchronous voice management operations.
-
-
-
-#### list
-
-```python
-async def list(
- *,
- page_size: int = 10,
- page_number: int = 1,
- title: Optional[str] = OMIT,
- tags: Optional[Union[list[str], str]] = OMIT,
- self_only: bool = False,
- author_id: Optional[str] = OMIT,
- language: Optional[Union[list[str], str]] = OMIT,
- title_language: Optional[Union[list[str], str]] = OMIT,
- sort_by: str = "task_count",
- request_options: Optional[RequestOptions] = None
-) -> PaginatedResponse[Voice]
-```
-
-List available voices/models (async). See sync version for details.
-
-
-
-#### get
-
-```python
-async def get(voice_id: str,
- *,
- request_options: Optional[RequestOptions] = None) -> Voice
-```
-
-Get voice by ID (async). See sync version for details.
-
-
-
-#### create
-
-```python
-async def create(*,
- title: str,
- voices: builtins.list[bytes],
- description: Optional[str] = OMIT,
- texts: Optional[builtins.list[str]] = OMIT,
- tags: Optional[builtins.list[str]] = OMIT,
- cover_image: Optional[bytes] = OMIT,
- visibility: Visibility = "private",
- train_mode: str = "fast",
- enhance_audio_quality: bool = True,
- request_options: Optional[RequestOptions] = None) -> Voice
-```
-
-Create/clone a new voice (async). See sync version for details.
-
-
-
-#### update
-
-```python
-async def update(voice_id: str,
- *,
- title: Optional[str] = OMIT,
- description: Optional[str] = OMIT,
- cover_image: Optional[bytes] = OMIT,
- visibility: Optional[Visibility] = OMIT,
- tags: Optional[builtins.list[str]] = OMIT,
- request_options: Optional[RequestOptions] = None) -> None
-```
-
-Update voice metadata (async). See sync version for details.
-
-
-
-#### delete
-
-```python
-async def delete(voice_id: str,
- *,
- request_options: Optional[RequestOptions] = None) -> None
-```
-
-Delete a voice (async). See sync version for details.
-
-
-
-# fishaudio.resources.account
-
-Account namespace client for billing and credits.
-
-
-
-## AccountClient Objects
-
-```python
-class AccountClient()
-```
-
-Synchronous account operations.
-
-
-
-#### get\_credits
-
-```python
-def get_credits(*,
- check_free_credit: Optional[bool] = OMIT,
- request_options: Optional[RequestOptions] = None) -> Credits
-```
-
-Get API credit balance.
-
-**Arguments**:
-
-- `check_free_credit` - Whether to check free credit availability
-- `request_options` - Request-level overrides
-
-
-**Returns**:
-
- Credits information
-
-
-**Example**:
-
- ```python
- client = FishAudio(api_key="...")
- credits = client.account.get_credits()
- print(f"Available credits: {float(credits.credit)}")
-
- # Check free credit availability
- credits = client.account.get_credits(check_free_credit=True)
- if credits.has_free_credit:
- print("Free credits available!")
- ```
-
-
-
-#### get\_package
-
-```python
-def get_package(*,
- request_options: Optional[RequestOptions] = None) -> Package
-```
-
-Get package information.
-
-**Arguments**:
-
-- `request_options` - Request-level overrides
-
-
-**Returns**:
-
- Package information
-
+ # Get complete audio
+ audio = client.tts.convert(text="Hello world")
-**Example**:
+ # Play it
+ play(audio)
- ```python
- client = FishAudio(api_key="...")
- package = client.account.get_package()
- print(f"Balance: {package.balance}/{package.total}")
+ # Or save it
+ save(audio, "output.mp3")
```
-
-
-## AsyncAccountClient Objects
-
-```python
-class AsyncAccountClient()
-```
-
-Asynchronous account operations.
-
-
+
-#### get\_credits
+#### stream\_websocket
```python
-async def get_credits(
+def stream_websocket(
+ text_stream: Iterable[Union[str, TextEvent, FlushEvent]],
*,
- check_free_credit: Optional[bool] = OMIT,
- request_options: Optional[RequestOptions] = None) -> Credits
+ reference_id: Optional[str] = None,
+ references: Optional[list[ReferenceAudio]] = None,
+ format: Optional[AudioFormat] = None,
+ latency: Optional[LatencyMode] = None,
+ speed: Optional[float] = None,
+ config: TTSConfig = TTSConfig(),
+ model: Union[Model, str] = "s2.1-pro",
+ max_workers: int = 10,
+ ws_options: Optional[WebSocketOptions] = None) -> Iterator[bytes]
```
-Get API credit balance (async).
+Stream text and receive audio in real-time via WebSocket.
+
+Perfect for conversational AI, live captioning, and streaming applications.
**Arguments**:
-- `check_free_credit` - Whether to check free credit availability
-- `request_options` - Request-level overrides
+- `text_stream` - Iterator of text chunks to stream
+- `reference_id` - Voice reference ID (overrides config.reference_id if provided)
+- `references` - Reference audio samples (overrides config.references if provided)
+- `format` - Audio format - "mp3", "wav", "pcm", or "opus" (overrides config.format if provided)
+- `latency` - Latency mode - "normal" or "balanced" (overrides config.latency if provided)
+- `speed` - Speech speed multiplier, e.g. 1.5 for 1.5x speed (overrides config.prosody.speed if provided)
+- `config` - TTS configuration (audio settings, voice, model parameters)
+- `model` - TTS model to use
+- `max_workers` - ThreadPoolExecutor workers for concurrent sender
+- `ws_options` - WebSocket connection options for configuring timeouts, message size limits, etc.
+ Useful for long-running generations that may exceed default timeout values.
+ See WebSocketOptions class for available parameters.
**Returns**:
- Credits information
+ Iterator of audio bytes
**Example**:
```python
- client = AsyncFishAudio(api_key="...")
- credits = await client.account.get_credits()
- print(f"Available credits: {float(credits.credit)}")
-
- # Check free credit availability
- credits = await client.account.get_credits(check_free_credit=True)
- if credits.has_free_credit:
- print("Free credits available!")
- ```
-
-
-
-#### get\_package
+ from fishaudio import FishAudio, TTSConfig, ReferenceAudio, WebSocketOptions
-```python
-async def get_package(*,
- request_options: Optional[RequestOptions] = None
- ) -> Package
-```
+ client = FishAudio(api_key="...")
-Get package information (async).
+ def text_generator():
+ yield "Hello, "
+ yield "this is "
+ yield "streaming text!"
-**Arguments**:
+ # Simple usage with defaults
+ with open("output.mp3", "wb") as f:
+ for audio_chunk in client.tts.stream_websocket(text_generator()):
+ f.write(audio_chunk)
-- `request_options` - Request-level overrides
-
+ # With format and speed parameters
+ with open("output.wav", "wb") as f:
+ for audio_chunk in client.tts.stream_websocket(
+ text_generator(),
+ format="wav",
+ speed=1.3
+ ):
+ f.write(audio_chunk)
-**Returns**:
+ # With reference_id parameter
+ with open("output.mp3", "wb") as f:
+ for audio_chunk in client.tts.stream_websocket(text_generator(), reference_id="your_model_id"):
+ f.write(audio_chunk)
- Package information
-
+ # With references parameter
+ with open("output.mp3", "wb") as f:
+ for audio_chunk in client.tts.stream_websocket(
+ text_generator(),
+ references=[ReferenceAudio(audio=audio_bytes, text="sample")]
+ ):
+ f.write(audio_chunk)
-**Example**:
+ # With WebSocket options for long-running generations
+ # Useful if you're generating very long responses that may take >20 seconds
+ ws_options = WebSocketOptions(keepalive_ping_timeout_seconds=60.0)
+ with open("output.mp3", "wb") as f:
+ for audio_chunk in client.tts.stream_websocket(
+ text_generator(),
+ ws_options=ws_options
+ ):
+ f.write(audio_chunk)
- ```python
- client = AsyncFishAudio(api_key="...")
- package = await client.account.get_package()
- print(f"Balance: {package.balance}/{package.total}")
+ # Parameters override config values
+ config = TTSConfig(format="mp3", latency="balanced")
+ with open("output.wav", "wb") as f:
+ for audio_chunk in client.tts.stream_websocket(
+ text_generator(),
+ format="wav", # Parameter wins
+ config=config
+ ):
+ f.write(audio_chunk)
```
-
-
-# fishaudio.resources.tts
-
-TTS (Text-to-Speech) namespace client.
-
-
+
-## TTSClient Objects
+## AsyncTTSClient Objects
```python
-class TTSClient()
+class AsyncTTSClient()
```
-Synchronous TTS operations.
+Asynchronous TTS operations.
-
+
#### stream
```python
-def stream(*,
- text: str,
- reference_id: Optional[str] = None,
- references: Optional[list[ReferenceAudio]] = None,
- format: Optional[AudioFormat] = None,
- latency: Optional[LatencyMode] = None,
- speed: Optional[float] = None,
- config: TTSConfig = TTSConfig(),
- model: Model = "s2-pro",
- request_options: Optional[RequestOptions] = None) -> AudioStream
+async def stream(
+ *,
+ text: str,
+ reference_id: Optional[str] = None,
+ references: Optional[list[ReferenceAudio]] = None,
+ format: Optional[AudioFormat] = None,
+ latency: Optional[LatencyMode] = None,
+ speed: Optional[float] = None,
+ config: TTSConfig = TTSConfig(),
+ model: Union[Model, str] = "s2.1-pro",
+ request_options: Optional[RequestOptions] = None) -> AsyncAudioStream
```
-Stream text-to-speech audio chunks.
+Stream text-to-speech audio chunks (async).
**Arguments**:
@@ -522,42 +401,43 @@ Stream text-to-speech audio chunks.
**Returns**:
- AudioStream object that can be iterated for audio chunks
+ AsyncAudioStream object that can be iterated for audio chunks
**Example**:
```python
- from fishaudio import FishAudio
+ from fishaudio import AsyncFishAudio
- client = FishAudio(api_key="...")
+ client = AsyncFishAudio(api_key="...")
# Stream and process chunks
- for chunk in client.tts.stream(text="Hello world"):
- process_audio_chunk(chunk)
+ async for chunk in await client.tts.stream(text="Hello world"):
+ await process_audio_chunk(chunk)
# Or collect all at once
- audio = client.tts.stream(text="Hello world").collect()
+ stream = await client.tts.stream(text="Hello world")
+ audio = await stream.collect()
```
-
+
#### convert
```python
-def convert(*,
- text: str,
- reference_id: Optional[str] = None,
- references: Optional[list[ReferenceAudio]] = None,
- format: Optional[AudioFormat] = None,
- latency: Optional[LatencyMode] = None,
- speed: Optional[float] = None,
- config: TTSConfig = TTSConfig(),
- model: Model = "s2-pro",
- request_options: Optional[RequestOptions] = None) -> bytes
+async def convert(*,
+ text: str,
+ reference_id: Optional[str] = None,
+ references: Optional[list[ReferenceAudio]] = None,
+ format: Optional[AudioFormat] = None,
+ latency: Optional[LatencyMode] = None,
+ speed: Optional[float] = None,
+ config: TTSConfig = TTSConfig(),
+ model: Union[Model, str] = "s2.1-pro",
+ request_options: Optional[RequestOptions] = None) -> bytes
```
-Convert text to speech and return complete audio as bytes.
+Convert text to speech and return complete audio as bytes (async).
This is a convenience method that streams all audio chunks and combines them.
For chunk-by-chunk processing, use stream() instead.
@@ -583,13 +463,13 @@ For chunk-by-chunk processing, use stream() instead.
**Example**:
```python
- from fishaudio import FishAudio
+ from fishaudio import AsyncFishAudio
from fishaudio.utils import play, save
- client = FishAudio(api_key="...")
+ client = AsyncFishAudio(api_key="...")
# Get complete audio
- audio = client.tts.convert(text="Hello world")
+ audio = await client.tts.convert(text="Hello world")
# Play it
play(audio)
@@ -598,32 +478,31 @@ For chunk-by-chunk processing, use stream() instead.
save(audio, "output.mp3")
```
-
+
#### stream\_websocket
```python
-def stream_websocket(
- text_stream: Iterable[Union[str, TextEvent, FlushEvent]],
- *,
- reference_id: Optional[str] = None,
- references: Optional[list[ReferenceAudio]] = None,
- format: Optional[AudioFormat] = None,
- latency: Optional[LatencyMode] = None,
- speed: Optional[float] = None,
- config: TTSConfig = TTSConfig(),
- model: Model = "s2-pro",
- max_workers: int = 10,
- ws_options: Optional[WebSocketOptions] = None) -> Iterator[bytes]
+async def stream_websocket(text_stream: AsyncIterable[Union[str, TextEvent,
+ FlushEvent]],
+ *,
+ reference_id: Optional[str] = None,
+ references: Optional[list[ReferenceAudio]] = None,
+ format: Optional[AudioFormat] = None,
+ latency: Optional[LatencyMode] = None,
+ speed: Optional[float] = None,
+ config: TTSConfig = TTSConfig(),
+ model: Union[Model, str] = "s2.1-pro",
+ ws_options: Optional[WebSocketOptions] = None)
```
-Stream text and receive audio in real-time via WebSocket.
+Stream text and receive audio in real-time via WebSocket (async).
Perfect for conversational AI, live captioning, and streaming applications.
**Arguments**:
-- `text_stream` - Iterator of text chunks to stream
+- `text_stream` - Async iterator of text chunks to stream
- `reference_id` - Voice reference ID (overrides config.reference_id if provided)
- `references` - Reference audio samples (overrides config.references if provided)
- `format` - Audio format - "mp3", "wav", "pcm", or "opus" (overrides config.format if provided)
@@ -631,7 +510,6 @@ Perfect for conversational AI, live captioning, and streaming applications.
- `speed` - Speech speed multiplier, e.g. 1.5 for 1.5x speed (overrides config.prosody.speed if provided)
- `config` - TTS configuration (audio settings, voice, model parameters)
- `model` - TTS model to use
-- `max_workers` - ThreadPoolExecutor workers for concurrent sender
- `ws_options` - WebSocket connection options for configuring timeouts, message size limits, etc.
Useful for long-running generations that may exceed default timeout values.
See WebSocketOptions class for available parameters.
@@ -639,460 +517,598 @@ Perfect for conversational AI, live captioning, and streaming applications.
**Returns**:
- Iterator of audio bytes
+ Async iterator of audio bytes
**Example**:
```python
- from fishaudio import FishAudio, TTSConfig, ReferenceAudio, WebSocketOptions
+ from fishaudio import AsyncFishAudio, TTSConfig, ReferenceAudio, WebSocketOptions
- client = FishAudio(api_key="...")
+ client = AsyncFishAudio(api_key="...")
- def text_generator():
+ async def text_generator():
yield "Hello, "
yield "this is "
- yield "streaming text!"
+ yield "async streaming!"
# Simple usage with defaults
- with open("output.mp3", "wb") as f:
- for audio_chunk in client.tts.stream_websocket(text_generator()):
- f.write(audio_chunk)
+ async with aiofiles.open("output.mp3", "wb") as f:
+ async for audio_chunk in client.tts.stream_websocket(text_generator()):
+ await f.write(audio_chunk)
# With format and speed parameters
- with open("output.wav", "wb") as f:
- for audio_chunk in client.tts.stream_websocket(
+ async with aiofiles.open("output.wav", "wb") as f:
+ async for audio_chunk in client.tts.stream_websocket(
text_generator(),
format="wav",
speed=1.3
):
- f.write(audio_chunk)
+ await f.write(audio_chunk)
# With reference_id parameter
- with open("output.mp3", "wb") as f:
- for audio_chunk in client.tts.stream_websocket(text_generator(), reference_id="your_model_id"):
- f.write(audio_chunk)
+ async with aiofiles.open("output.mp3", "wb") as f:
+ async for audio_chunk in client.tts.stream_websocket(text_generator(), reference_id="your_model_id"):
+ await f.write(audio_chunk)
# With references parameter
- with open("output.mp3", "wb") as f:
- for audio_chunk in client.tts.stream_websocket(
+ async with aiofiles.open("output.mp3", "wb") as f:
+ async for audio_chunk in client.tts.stream_websocket(
text_generator(),
references=[ReferenceAudio(audio=audio_bytes, text="sample")]
):
- f.write(audio_chunk)
+ await f.write(audio_chunk)
+
+ # With WebSocket options for long-running generations
+ # Useful if you're generating very long responses that may take >20 seconds
+ ws_options = WebSocketOptions(keepalive_ping_timeout_seconds=60.0)
+ async with aiofiles.open("output.mp3", "wb") as f:
+ async for audio_chunk in client.tts.stream_websocket(
+ text_generator(),
+ ws_options=ws_options
+ ):
+ await f.write(audio_chunk)
+
+ # Parameters override config values
+ config = TTSConfig(format="mp3", latency="balanced")
+ async with aiofiles.open("output.wav", "wb") as f:
+ async for audio_chunk in client.tts.stream_websocket(
+ text_generator(),
+ format="wav", # Parameter wins
+ config=config
+ ):
+ await f.write(audio_chunk)
+ ```
+
+
+
+# fishaudio.resources.realtime
+
+Real-time WebSocket streaming helpers.
+
+
+
+#### iter\_websocket\_audio
+
+```python
+def iter_websocket_audio(ws) -> Iterator[bytes]
+```
+
+Process WebSocket audio messages (sync).
+
+Receives messages from WebSocket, yields audio chunks, handles errors.
+Unknown events are ignored and iteration continues.
+
+**Arguments**:
+
+- `ws` - WebSocket connection from httpx_ws.connect_ws
+
+
+**Yields**:
+
+ Audio bytes
+
+
+**Raises**:
+
+- `WebSocketError` - On disconnect or error finish event
+
+
+
+#### aiter\_websocket\_audio
+
+```python
+async def aiter_websocket_audio(ws) -> AsyncIterator[bytes]
+```
+
+Process WebSocket audio messages (async).
+
+Receives messages from WebSocket, yields audio chunks, handles errors.
+Unknown events are ignored and iteration continues.
+
+**Arguments**:
+
+- `ws` - WebSocket connection from httpx_ws.aconnect_ws
+
+
+**Yields**:
+
+ Audio bytes
+
+
+**Raises**:
+
+- `WebSocketError` - On disconnect or error finish event
+
+
+
+# fishaudio.resources.account
+
+Account namespace client for billing and credits.
+
+
+
+## AccountClient Objects
+
+```python
+class AccountClient()
+```
+
+Synchronous account operations.
+
+
+
+#### get\_credits
+
+```python
+def get_credits(*,
+ check_free_credit: Optional[bool] = OMIT,
+ request_options: Optional[RequestOptions] = None) -> Credits
+```
+
+Get API credit balance.
+
+**Arguments**:
+
+- `check_free_credit` - Whether to check free credit availability
+- `request_options` - Request-level overrides
+
+
+**Returns**:
+
+ Credits information
+
+
+**Example**:
+
+ ```python
+ client = FishAudio(api_key="...")
+ credits = client.account.get_credits()
+ print(f"Available credits: {float(credits.credit)}")
+
+ # Check free credit availability
+ credits = client.account.get_credits(check_free_credit=True)
+ if credits.has_free_credit:
+ print("Free credits available!")
+ ```
+
+
+
+#### get\_package
+
+```python
+def get_package(*,
+ request_options: Optional[RequestOptions] = None) -> Package
+```
+
+Get package information.
+
+**Arguments**:
+
+- `request_options` - Request-level overrides
+
- # With WebSocket options for long-running generations
- # Useful if you're generating very long responses that may take >20 seconds
- ws_options = WebSocketOptions(keepalive_ping_timeout_seconds=60.0)
- with open("output.mp3", "wb") as f:
- for audio_chunk in client.tts.stream_websocket(
- text_generator(),
- ws_options=ws_options
- ):
- f.write(audio_chunk)
+**Returns**:
- # Parameters override config values
- config = TTSConfig(format="mp3", latency="balanced")
- with open("output.wav", "wb") as f:
- for audio_chunk in client.tts.stream_websocket(
- text_generator(),
- format="wav", # Parameter wins
- config=config
- ):
- f.write(audio_chunk)
+ Package information
+
+
+**Example**:
+
+ ```python
+ client = FishAudio(api_key="...")
+ package = client.account.get_package()
+ print(f"Balance: {package.balance}/{package.total}")
```
-
+
-## AsyncTTSClient Objects
+## AsyncAccountClient Objects
```python
-class AsyncTTSClient()
+class AsyncAccountClient()
```
-Asynchronous TTS operations.
+Asynchronous account operations.
-
+
-#### stream
+#### get\_credits
```python
-async def stream(
+async def get_credits(
*,
- text: str,
- reference_id: Optional[str] = None,
- references: Optional[list[ReferenceAudio]] = None,
- format: Optional[AudioFormat] = None,
- latency: Optional[LatencyMode] = None,
- speed: Optional[float] = None,
- config: TTSConfig = TTSConfig(),
- model: Model = "s2-pro",
- request_options: Optional[RequestOptions] = None) -> AsyncAudioStream
+ check_free_credit: Optional[bool] = OMIT,
+ request_options: Optional[RequestOptions] = None) -> Credits
```
-Stream text-to-speech audio chunks (async).
+Get API credit balance (async).
**Arguments**:
-- `text` - Text to synthesize
-- `reference_id` - Voice reference ID (overrides config.reference_id if provided)
-- `references` - Reference audio samples (overrides config.references if provided)
-- `format` - Audio format - "mp3", "wav", "pcm", or "opus" (overrides config.format if provided)
-- `latency` - Latency mode - "normal" or "balanced" (overrides config.latency if provided)
-- `speed` - Speech speed multiplier, e.g. 1.5 for 1.5x speed (overrides config.prosody.speed if provided)
-- `config` - TTS configuration (audio settings, voice, model parameters)
-- `model` - TTS model to use
+- `check_free_credit` - Whether to check free credit availability
- `request_options` - Request-level overrides
**Returns**:
- AsyncAudioStream object that can be iterated for audio chunks
+ Credits information
**Example**:
```python
- from fishaudio import AsyncFishAudio
-
client = AsyncFishAudio(api_key="...")
+ credits = await client.account.get_credits()
+ print(f"Available credits: {float(credits.credit)}")
- # Stream and process chunks
- async for chunk in await client.tts.stream(text="Hello world"):
- await process_audio_chunk(chunk)
-
- # Or collect all at once
- stream = await client.tts.stream(text="Hello world")
- audio = await stream.collect()
+ # Check free credit availability
+ credits = await client.account.get_credits(check_free_credit=True)
+ if credits.has_free_credit:
+ print("Free credits available!")
```
-
+
-#### convert
+#### get\_package
```python
-async def convert(*,
- text: str,
- reference_id: Optional[str] = None,
- references: Optional[list[ReferenceAudio]] = None,
- format: Optional[AudioFormat] = None,
- latency: Optional[LatencyMode] = None,
- speed: Optional[float] = None,
- config: TTSConfig = TTSConfig(),
- model: Model = "s2-pro",
- request_options: Optional[RequestOptions] = None) -> bytes
+async def get_package(*,
+ request_options: Optional[RequestOptions] = None
+ ) -> Package
```
-Convert text to speech and return complete audio as bytes (async).
-
-This is a convenience method that streams all audio chunks and combines them.
-For chunk-by-chunk processing, use stream() instead.
+Get package information (async).
**Arguments**:
-- `text` - Text to synthesize
-- `reference_id` - Voice reference ID (overrides config.reference_id if provided)
-- `references` - Reference audio samples (overrides config.references if provided)
-- `format` - Audio format - "mp3", "wav", "pcm", or "opus" (overrides config.format if provided)
-- `latency` - Latency mode - "normal" or "balanced" (overrides config.latency if provided)
-- `speed` - Speech speed multiplier, e.g. 1.5 for 1.5x speed (overrides config.prosody.speed if provided)
-- `config` - TTS configuration (audio settings, voice, model parameters)
-- `model` - TTS model to use
- `request_options` - Request-level overrides
**Returns**:
- Complete audio as bytes
+ Package information
**Example**:
```python
- from fishaudio import AsyncFishAudio
- from fishaudio.utils import play, save
-
client = AsyncFishAudio(api_key="...")
+ package = await client.account.get_package()
+ print(f"Balance: {package.balance}/{package.total}")
+ ```
- # Get complete audio
- audio = await client.tts.convert(text="Hello world")
+
- # Play it
- play(audio)
+# fishaudio.resources.voices
- # Or save it
- save(audio, "output.mp3")
- ```
+Voice management namespace client.
-
+
-#### stream\_websocket
+## VoicesClient Objects
```python
-async def stream_websocket(text_stream: AsyncIterable[Union[str, TextEvent,
- FlushEvent]],
- *,
- reference_id: Optional[str] = None,
- references: Optional[list[ReferenceAudio]] = None,
- format: Optional[AudioFormat] = None,
- latency: Optional[LatencyMode] = None,
- speed: Optional[float] = None,
- config: TTSConfig = TTSConfig(),
- model: Model = "s2-pro",
- ws_options: Optional[WebSocketOptions] = None)
+class VoicesClient()
```
-Stream text and receive audio in real-time via WebSocket (async).
+Synchronous voice management operations.
-Perfect for conversational AI, live captioning, and streaming applications.
+
+
+#### list
+
+```python
+def list(
+ *,
+ page_size: int = 10,
+ page_number: int = 1,
+ title: Optional[str] = OMIT,
+ tags: Optional[Union[list[str], str]] = OMIT,
+ self_only: bool = False,
+ author_id: Optional[str] = OMIT,
+ language: Optional[Union[list[str], str]] = OMIT,
+ title_language: Optional[Union[list[str], str]] = OMIT,
+ sort_by: str = "task_count",
+ request_options: Optional[RequestOptions] = None
+) -> PaginatedResponse[Voice]
+```
+
+List available voices/models.
**Arguments**:
-- `text_stream` - Async iterator of text chunks to stream
-- `reference_id` - Voice reference ID (overrides config.reference_id if provided)
-- `references` - Reference audio samples (overrides config.references if provided)
-- `format` - Audio format - "mp3", "wav", "pcm", or "opus" (overrides config.format if provided)
-- `latency` - Latency mode - "normal" or "balanced" (overrides config.latency if provided)
-- `speed` - Speech speed multiplier, e.g. 1.5 for 1.5x speed (overrides config.prosody.speed if provided)
-- `config` - TTS configuration (audio settings, voice, model parameters)
-- `model` - TTS model to use
-- `ws_options` - WebSocket connection options for configuring timeouts, message size limits, etc.
- Useful for long-running generations that may exceed default timeout values.
- See WebSocketOptions class for available parameters.
+- `page_size` - Number of results per page
+- `page_number` - Page number (1-indexed)
+- `title` - Filter by title
+- `tags` - Filter by tags (single tag or list)
+- `self_only` - Only return user's own voices
+- `author_id` - Filter by author ID
+- `language` - Filter by language(s)
+- `title_language` - Filter by title language(s)
+- `sort_by` - Sort field ("task_count" or "created_at")
+- `request_options` - Request-level overrides
**Returns**:
- Async iterator of audio bytes
+ Paginated response with total count and voice items
**Example**:
```python
- from fishaudio import AsyncFishAudio, TTSConfig, ReferenceAudio, WebSocketOptions
-
- client = AsyncFishAudio(api_key="...")
-
- async def text_generator():
- yield "Hello, "
- yield "this is "
- yield "async streaming!"
-
- # Simple usage with defaults
- async with aiofiles.open("output.mp3", "wb") as f:
- async for audio_chunk in client.tts.stream_websocket(text_generator()):
- await f.write(audio_chunk)
-
- # With format and speed parameters
- async with aiofiles.open("output.wav", "wb") as f:
- async for audio_chunk in client.tts.stream_websocket(
- text_generator(),
- format="wav",
- speed=1.3
- ):
- await f.write(audio_chunk)
-
- # With reference_id parameter
- async with aiofiles.open("output.mp3", "wb") as f:
- async for audio_chunk in client.tts.stream_websocket(text_generator(), reference_id="your_model_id"):
- await f.write(audio_chunk)
-
- # With references parameter
- async with aiofiles.open("output.mp3", "wb") as f:
- async for audio_chunk in client.tts.stream_websocket(
- text_generator(),
- references=[ReferenceAudio(audio=audio_bytes, text="sample")]
- ):
- await f.write(audio_chunk)
-
- # With WebSocket options for long-running generations
- # Useful if you're generating very long responses that may take >20 seconds
- ws_options = WebSocketOptions(keepalive_ping_timeout_seconds=60.0)
- async with aiofiles.open("output.mp3", "wb") as f:
- async for audio_chunk in client.tts.stream_websocket(
- text_generator(),
- ws_options=ws_options
- ):
- await f.write(audio_chunk)
-
- # Parameters override config values
- config = TTSConfig(format="mp3", latency="balanced")
- async with aiofiles.open("output.wav", "wb") as f:
- async for audio_chunk in client.tts.stream_websocket(
- text_generator(),
- format="wav", # Parameter wins
- config=config
- ):
- await f.write(audio_chunk)
- ```
-
-
+ client = FishAudio(api_key="...")
-# fishaudio.resources.realtime
+ # List all voices
+ voices = client.voices.list(page_size=20)
+ print(f"Total: {voices.total}")
+ for voice in voices.items:
+ print(f"{voice.title}: {voice.id}")
-Real-time WebSocket streaming helpers.
+ # Filter by tags
+ tagged = client.voices.list(tags=["male", "english"])
+ ```
-
+
-#### iter\_websocket\_audio
+#### get
```python
-def iter_websocket_audio(ws) -> Iterator[bytes]
+def get(voice_id: str,
+ *,
+ request_options: Optional[RequestOptions] = None) -> Voice
```
-Process WebSocket audio messages (sync).
-
-Receives messages from WebSocket, yields audio chunks, handles errors.
-Unknown events are ignored and iteration continues.
+Get voice by ID.
**Arguments**:
-- `ws` - WebSocket connection from httpx_ws.connect_ws
+- `voice_id` - Voice model ID
+- `request_options` - Request-level overrides
-**Yields**:
+**Returns**:
- Audio bytes
+ Voice model details
-**Raises**:
+**Example**:
-- `WebSocketError` - On disconnect or error finish event
+ ```python
+ client = FishAudio(api_key="...")
+ voice = client.voices.get("voice_id_here")
+ print(voice.title, voice.description)
+ ```
-
+
-#### aiter\_websocket\_audio
+#### create
```python
-async def aiter_websocket_audio(ws) -> AsyncIterator[bytes]
+def create(*,
+ title: str,
+ voices: builtins.list[bytes],
+ description: Optional[str] = OMIT,
+ texts: Optional[builtins.list[str]] = OMIT,
+ tags: Optional[builtins.list[str]] = OMIT,
+ cover_image: Optional[bytes] = OMIT,
+ visibility: Visibility = "private",
+ train_mode: str = "fast",
+ enhance_audio_quality: bool = True,
+ request_options: Optional[RequestOptions] = None) -> Voice
```
-Process WebSocket audio messages (async).
-
-Receives messages from WebSocket, yields audio chunks, handles errors.
-Unknown events are ignored and iteration continues.
+Create/clone a new voice.
**Arguments**:
-- `ws` - WebSocket connection from httpx_ws.aconnect_ws
+- `title` - Voice model name
+- `voices` - List of audio file bytes for training
+- `description` - Voice description
+- `texts` - Transcripts for voice samples
+- `tags` - Tags for categorization
+- `cover_image` - Cover image bytes
+- `visibility` - Visibility setting (public, unlist, private)
+- `train_mode` - Training mode (currently only "fast" supported)
+- `enhance_audio_quality` - Whether to enhance audio quality
+- `request_options` - Request-level overrides
-**Yields**:
+**Returns**:
- Audio bytes
+ Created voice model
-**Raises**:
-
-- `WebSocketError` - On disconnect or error finish event
-
-
+**Example**:
-# fishaudio.resources.asr
+ ```python
+ client = FishAudio(api_key="...")
-ASR (Automatic Speech Recognition) namespace client.
+ with open("voice1.wav", "rb") as f1, open("voice2.wav", "rb") as f2:
+ voice = client.voices.create(
+ title="My Voice",
+ voices=[f1.read(), f2.read()],
+ description="Custom voice clone",
+ tags=["custom", "english"]
+ )
+ print(f"Created: {voice.id}")
+ ```
-
+
-## ASRClient Objects
+#### update
```python
-class ASRClient()
+def update(voice_id: str,
+ *,
+ title: Optional[str] = OMIT,
+ description: Optional[str] = OMIT,
+ cover_image: Optional[bytes] = OMIT,
+ visibility: Optional[Visibility] = OMIT,
+ tags: Optional[builtins.list[str]] = OMIT,
+ request_options: Optional[RequestOptions] = None) -> None
```
-Synchronous ASR operations.
+Update voice metadata.
-
+**Arguments**:
-#### transcribe
+- `voice_id` - Voice model ID
+- `title` - New title
+- `description` - New description
+- `cover_image` - New cover image bytes
+- `visibility` - New visibility setting
+- `tags` - New tags
+- `request_options` - Request-level overrides
+
+
+**Example**:
+
+ ```python
+ client = FishAudio(api_key="...")
+ client.voices.update(
+ "voice_id_here",
+ title="Updated Title",
+ visibility="public"
+ )
+ ```
+
+
+
+#### delete
```python
-def transcribe(
- *,
- audio: bytes,
- language: Optional[str] = OMIT,
- include_timestamps: bool = True,
- request_options: Optional[RequestOptions] = None) -> ASRResponse
+def delete(voice_id: str,
+ *,
+ request_options: Optional[RequestOptions] = None) -> None
```
-Transcribe audio to text.
+Delete a voice.
**Arguments**:
-- `audio` - Audio file bytes
-- `language` - Language code (e.g., "en", "zh"). Auto-detected if not provided.
-- `include_timestamps` - Whether to include timestamp information for segments
+- `voice_id` - Voice model ID
- `request_options` - Request-level overrides
-**Returns**:
-
- ASRResponse with transcription text, duration, and segments
-
-
**Example**:
```python
client = FishAudio(api_key="...")
+ client.voices.delete("voice_id_here")
+ ```
- with open("audio.mp3", "rb") as f:
- audio_bytes = f.read()
+
- result = client.asr.transcribe(audio=audio_bytes, language="en")
- print(result.text)
- for segment in result.segments:
- print(f"{segment.start}-{segment.end}: {segment.text}")
- ```
+## AsyncVoicesClient Objects
-
+```python
+class AsyncVoicesClient()
+```
-## AsyncASRClient Objects
+Asynchronous voice management operations.
+
+
+
+#### list
```python
-class AsyncASRClient()
+async def list(
+ *,
+ page_size: int = 10,
+ page_number: int = 1,
+ title: Optional[str] = OMIT,
+ tags: Optional[Union[list[str], str]] = OMIT,
+ self_only: bool = False,
+ author_id: Optional[str] = OMIT,
+ language: Optional[Union[list[str], str]] = OMIT,
+ title_language: Optional[Union[list[str], str]] = OMIT,
+ sort_by: str = "task_count",
+ request_options: Optional[RequestOptions] = None
+) -> PaginatedResponse[Voice]
```
-Asynchronous ASR operations.
+List available voices/models (async). See sync version for details.
-
+
-#### transcribe
+#### get
```python
-async def transcribe(
- *,
- audio: bytes,
- language: Optional[str] = OMIT,
- include_timestamps: bool = True,
- request_options: Optional[RequestOptions] = None) -> ASRResponse
+async def get(voice_id: str,
+ *,
+ request_options: Optional[RequestOptions] = None) -> Voice
```
-Transcribe audio to text (async).
+Get voice by ID (async). See sync version for details.
-**Arguments**:
+
-- `audio` - Audio file bytes
-- `language` - Language code (e.g., "en", "zh"). Auto-detected if not provided.
-- `include_timestamps` - Whether to include timestamp information for segments
-- `request_options` - Request-level overrides
-
+#### create
-**Returns**:
+```python
+async def create(*,
+ title: str,
+ voices: builtins.list[bytes],
+ description: Optional[str] = OMIT,
+ texts: Optional[builtins.list[str]] = OMIT,
+ tags: Optional[builtins.list[str]] = OMIT,
+ cover_image: Optional[bytes] = OMIT,
+ visibility: Visibility = "private",
+ train_mode: str = "fast",
+ enhance_audio_quality: bool = True,
+ request_options: Optional[RequestOptions] = None) -> Voice
+```
- ASRResponse with transcription text, duration, and segments
-
+Create/clone a new voice (async). See sync version for details.
-**Example**:
+
- ```python
- client = AsyncFishAudio(api_key="...")
+#### update
- async with aiofiles.open("audio.mp3", "rb") as f:
- audio_bytes = await f.read()
+```python
+async def update(voice_id: str,
+ *,
+ title: Optional[str] = OMIT,
+ description: Optional[str] = OMIT,
+ cover_image: Optional[bytes] = OMIT,
+ visibility: Optional[Visibility] = OMIT,
+ tags: Optional[builtins.list[str]] = OMIT,
+ request_options: Optional[RequestOptions] = None) -> None
+```
- result = await client.asr.transcribe(audio=audio_bytes, language="en")
- print(result.text)
- for segment in result.segments:
- print(f"{segment.start}-{segment.end}: {segment.text}")
- ```
+Update voice metadata (async). See sync version for details.
+
+
+
+#### delete
+
+```python
+async def delete(voice_id: str,
+ *,
+ request_options: Optional[RequestOptions] = None) -> None
+```
+
+Delete a voice (async). See sync version for details.
diff --git a/api-reference/sdk/python/types.mdx b/api-reference/sdk/python/types.mdx
index 3873865..64c46fc 100644
--- a/api-reference/sdk/python/types.mdx
+++ b/api-reference/sdk/python/types.mdx
@@ -1,124 +1,67 @@
-
-
-# fishaudio.types.voices
-
-Voice and model management types.
-
-
-
-## Sample Objects
-
-```python
-class Sample(BaseModel)
-```
-
-A sample audio for a voice model.
+
-**Attributes**:
+# fishaudio.types.asr
-- `title` - Title/name of the audio sample
-- `text` - Transcription of the spoken content in the sample
-- `task_id` - Unique identifier for the sample task
-- `audio` - URL or path to the audio file
+ASR (Automatic Speech Recognition) related types.
-
+
-## Author Objects
+## ASRSegment Objects
```python
-class Author(BaseModel)
+class ASRSegment(BaseModel)
```
-Voice model author information.
+A timestamped segment of transcribed text.
**Attributes**:
-- `id` - Unique author identifier
-- `nickname` - Author's display name
-- `avatar` - URL to author's avatar image
+- `text` - The transcribed text for this segment
+- `start` - Segment start time in seconds
+- `end` - Segment end time in seconds
-
+
-## Voice Objects
+## SpeakerTurn Objects
```python
-class Voice(BaseModel)
+class SpeakerTurn(BaseModel)
```
-A voice model.
-
-Represents a TTS voice that can be used for synthesis.
+A stretch of the transcript spoken by one speaker.
**Attributes**:
-- `id` - Unique voice model identifier (use as reference_id in TTS)
-- `type` - Model type. Options: "svc" (singing voice conversion), "tts" (text-to-speech)
-- `title` - Voice model title/name
-- `description` - Detailed description of the voice model
-- `cover_image` - URL to the voice model's cover image
-- `train_mode` - Training mode used. Options: "fast"
-- `state` - Current model state: "created", "training", "trained", or "failed"
-- `tags` - List of tags for categorization (e.g., ["male", "english", "young"])
-- `samples` - List of audio samples demonstrating the voice
-- `created_at` - Timestamp when the model was created
-- `updated_at` - Timestamp when the model was last updated
-- `languages` - List of supported language codes (e.g., ["en", "zh"])
-- `visibility` - Model visibility. Options: "public", "private", "unlist"
-- `lock_visibility` - Whether visibility setting is locked
-- `like_count` - Number of likes the model has received
-- `mark_count` - Number of bookmarks/favorites
-- `shared_count` - Number of times the model has been shared
-- `task_count` - Number of times the model has been used for generation
-- `liked` - Whether the current user has liked this model. Default: False
-- `marked` - Whether the current user has bookmarked this model. Default: False
-- `author` - Information about the voice model's creator
-
-
-
-# fishaudio.types.account
-
-Account-related types (credits, packages, etc.).
+- `speaker` - Speaker label (e.g., "speaker:0"). Labels identify speakers within one response only
+- `text` - What the speaker said in this turn, without inline speaker markers
+- `start` - Turn start time in seconds
+- `end` - Turn end time in seconds
-
+
-## Credits Objects
+## ASRResponse Objects
```python
-class Credits(BaseModel)
+class ASRResponse(BaseModel)
```
-User's API credit balance.
+Response from speech-to-text transcription.
**Attributes**:
-- `id` - Unique credits record identifier
-- `user_id` - User identifier
-- `credit` - Current credit balance (decimal for precise accounting)
-- `created_at` - Timestamp when the credits record was created
-- `updated_at` - Timestamp when the credits were last updated
-- `has_phone_sha256` - Whether the user has a verified phone number. Optional
-- `has_free_credit` - Whether the user has received free credits. Optional
-
-
-
-## Package Objects
-
-```python
-class Package(BaseModel)
-```
+- `text` - Complete transcription of the entire audio
+- `duration` - Total audio duration in seconds
+- `segments` - List of timestamped text segments. Empty if include_timestamps=False
+- `language` - Display name of the transcript's language (e.g., "English"). None if not returned
+- `language_code` - Language code of the transcript (e.g., "en"): the detected language, or the requested language if none was detected. None if not returned
+- `request_id` - Identifier of this request; quote it when reporting a problem with the request. None if not returned
+- `speaker_turns` - Speaker turns in order, with times in seconds. Returned only by models with speaker diarization, and only if include_timestamps=True; empty if no speech was found. None otherwise
-User's prepaid package information.
+
-**Attributes**:
+#### duration
-- `id` - Unique package identifier
-- `user_id` - User identifier
-- `type` - Package type identifier
-- `total` - Total units in the package
-- `balance` - Remaining units in the package
-- `created_at` - Timestamp when the package was purchased
-- `updated_at` - Timestamp when the package was last updated
-- `finished_at` - Timestamp when the package was fully consumed. None if still active
+Duration in seconds
@@ -205,7 +148,7 @@ All parameters have sensible defaults.
- `chunk_length` - Characters per generation chunk. Range: 100-300. Default: 200.
Lower values = faster initial response, higher values = better quality
- `latency` - Generation mode. Options: "normal" (higher quality), "balanced" (faster). Default: "balanced"
-- `reference_id` - Voice model ID from fish.audio (e.g., "9a9cf47702da476aa4629e2506d4a857").
+- `reference_id` - Voice model ID from fish.audio (e.g., "802e3bc2b27e49c2995d23ef70e6ac89").
Find IDs in voice URLs or via voices.list()
- `references` - List of reference audio samples for instant voice cloning. Default: []
- `prosody` - Speech speed and volume settings. Default: None (uses natural prosody)
@@ -312,78 +255,156 @@ WebSocket event to end the streaming session.
- `event` - Event type identifier, always "stop"
-
+
-# fishaudio.types.shared
+# fishaudio.types.account
-Shared types used across the SDK.
+Account-related types (credits, packages, etc.).
-
+
-## PaginatedResponse Objects
+## Credits Objects
```python
-class PaginatedResponse(BaseModel, Generic[T])
+class Credits(BaseModel)
```
-Generic paginated response.
+User's API credit balance.
**Attributes**:
-- `total` - Total number of items across all pages
-- `items` - List of items on the current page
+- `id` - Unique credits record identifier
+- `user_id` - User identifier
+- `credit` - Current credit balance (decimal for precise accounting)
+- `created_at` - Timestamp when the credits record was created
+- `updated_at` - Timestamp when the credits were last updated
+- `has_phone_sha256` - Whether the user has a verified phone number. Optional
+- `has_free_credit` - Whether the user has received free credits. Optional
-
+
-#### warn\_if\_deprecated\_model
+## Package Objects
```python
-def warn_if_deprecated_model(model: str) -> None
+class Package(BaseModel)
```
-Emit a deprecation warning if a legacy model is used.
+User's prepaid package information.
-
+**Attributes**:
-# fishaudio.types.asr
+- `id` - Unique package identifier
+- `user_id` - User identifier
+- `type` - Package type identifier
+- `total` - Total units in the package
+- `balance` - Remaining units in the package
+- `created_at` - Timestamp when the package was purchased
+- `updated_at` - Timestamp when the package was last updated
+- `finished_at` - Timestamp when the package was fully consumed. None if still active
-ASR (Automatic Speech Recognition) related types.
+
-
+# fishaudio.types.voices
-## ASRSegment Objects
+Voice and model management types.
+
+
+
+## Sample Objects
```python
-class ASRSegment(BaseModel)
+class Sample(BaseModel)
```
-A timestamped segment of transcribed text.
+A sample audio for a voice model.
**Attributes**:
-- `text` - The transcribed text for this segment
-- `start` - Segment start time in seconds
-- `end` - Segment end time in seconds
+- `title` - Title/name of the audio sample
+- `text` - Transcription of the spoken content in the sample
+- `task_id` - Unique identifier for the sample task
+- `audio` - URL or path to the audio file
-
+
-## ASRResponse Objects
+## Author Objects
```python
-class ASRResponse(BaseModel)
+class Author(BaseModel)
```
-Response from speech-to-text transcription.
+Voice model author information.
**Attributes**:
-- `text` - Complete transcription of the entire audio
-- `duration` - Total audio duration in seconds
-- `segments` - List of timestamped text segments. Empty if include_timestamps=False
+- `id` - Unique author identifier
+- `nickname` - Author's display name
+- `avatar` - URL to author's avatar image
-
+
-#### duration
+## Voice Objects
-Duration in seconds
+```python
+class Voice(BaseModel)
+```
+
+A voice model.
+
+Represents a TTS voice that can be used for synthesis.
+
+**Attributes**:
+
+- `id` - Unique voice model identifier (use as reference_id in TTS)
+- `type` - Model type. Options: "svc" (singing voice conversion), "tts" (text-to-speech)
+- `title` - Voice model title/name
+- `description` - Detailed description of the voice model
+- `cover_image` - URL to the voice model's cover image
+- `train_mode` - Training mode used. Options: "fast"
+- `state` - Current model state (e.g., "ready", "training", "failed")
+- `tags` - List of tags for categorization (e.g., ["male", "english", "young"])
+- `samples` - List of audio samples demonstrating the voice
+- `created_at` - Timestamp when the model was created
+- `updated_at` - Timestamp when the model was last updated
+- `languages` - List of supported language codes (e.g., ["en", "zh"])
+- `visibility` - Model visibility. Options: "public", "private", "unlist"
+- `lock_visibility` - Whether visibility setting is locked
+- `like_count` - Number of likes the model has received
+- `mark_count` - Number of bookmarks/favorites
+- `shared_count` - Number of times the model has been shared
+- `task_count` - Number of times the model has been used for generation
+- `liked` - Whether the current user has liked this model. Default: False
+- `marked` - Whether the current user has bookmarked this model. Default: False
+- `author` - Information about the voice model's creator
+
+
+
+# fishaudio.types.shared
+
+Shared types used across the SDK.
+
+
+
+## PaginatedResponse Objects
+
+```python
+class PaginatedResponse(BaseModel, Generic[T])
+```
+
+Generic paginated response.
+
+**Attributes**:
+
+- `total` - Total number of items across all pages
+- `items` - List of items on the current page
+
+
+
+#### warn\_if\_deprecated\_model
+
+```python
+def warn_if_deprecated_model(model: str) -> None
+```
+
+Emit a deprecation warning if a legacy model is used.
diff --git a/api-reference/sdk/python/utils.mdx b/api-reference/sdk/python/utils.mdx
index 93a81c5..100aa97 100644
--- a/api-reference/sdk/python/utils.mdx
+++ b/api-reference/sdk/python/utils.mdx
@@ -1,11 +1,47 @@
+
+
+# fishaudio.utils.save
+
+Audio saving utility.
+
+
+
+#### save
+
+```python
+def save(audio: Union[bytes, Iterable[bytes]], filename: str) -> None
+```
+
+Save audio to a file.
+
+**Arguments**:
+
+- `audio` - Audio bytes or iterable of bytes
+- `filename` - Path to save the audio file
+
+
+**Examples**:
+
+ ```python
+ from fishaudio import FishAudio, save
+
+ client = FishAudio(api_key="...")
+ audio = client.tts.convert(text="Hello world")
+
+ # Save to file
+ save(audio, "output.mp3")
+
+ # Works with iterators too
+ audio_stream = client.tts.convert(text="Another example")
+ save(audio_stream, "another.mp3")
+ ```
+
# fishaudio.utils.play
Audio playback utility.
-Install the Python SDK first: `pip install fish-audio-sdk`.
-
#### play
@@ -49,44 +85,6 @@ Play audio using various playback methods.
play(audio, use_ffmpeg=False)
```
-
-
-# fishaudio.utils.save
-
-Audio saving utility.
-
-
-
-#### save
-
-```python
-def save(audio: Union[bytes, Iterable[bytes]], filename: str) -> None
-```
-
-Save audio to a file.
-
-**Arguments**:
-
-- `audio` - Audio bytes or iterable of bytes
-- `filename` - Path to save the audio file
-
-
-**Examples**:
-
- ```python
- from fishaudio import FishAudio, save
-
- client = FishAudio(api_key="...")
- audio = client.tts.convert(text="Hello world")
-
- # Save to file
- save(audio, "output.mp3")
-
- # Works with iterators too
- audio_stream = client.tts.convert(text="Another example")
- save(audio_stream, "another.mp3")
- ```
-
# fishaudio.utils.stream