Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
642c0f8
docs: update README to highlight new API and migration guide
twangodev Nov 12, 2025
810386e
docs: update README with new badge for Python version and improved bu…
twangodev Nov 12, 2025
bf2f5db
docs: update README badge to reflect Python SDK branding
twangodev Nov 12, 2025
cc72716
docs: update README to remove redundant badge and simplify description
twangodev Nov 12, 2025
62b955c
docs: update README to reflect new API features and improve installat…
twangodev Nov 13, 2025
cc8416b
docs: update README to provide direct links to Python SDK Guide and A…
twangodev Nov 13, 2025
3cc4904
docs: update title in copy_docs.py from "Python SDK" to "Overview"
twangodev Nov 13, 2025
27a9909
chore: update development status to Production/Stable in pyproject.toml
twangodev Nov 13, 2025
6228352
chore: rename environment variable from FISH_AUDIO_API_KEY to FISH_AP…
twangodev Nov 13, 2025
b5b5258
feat: add streaming support for text-to-speech with AudioStream and A…
twangodev Nov 13, 2025
8034987
docs: enhance docstrings for models with detailed attribute descriptions
twangodev Nov 14, 2025
1ea4829
docs: update legacy SDK documentation links in README.md
twangodev Nov 14, 2025
c68c180
docs: update README.md to reflect new API features and usage examples
twangodev Nov 14, 2025
38a7fbd
feat: update text-to-speech implementation to use streaming method
twangodev Nov 14, 2025
22b3733
fix: correct async streaming method calls in text-to-speech implement…
twangodev Nov 14, 2025
30846d4
chore: update Python version to 3.9 and add future annotations for ty…
twangodev Nov 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1 +1 @@
FISH_AUDIO_API_KEY=
FISH_API_KEY=
4 changes: 2 additions & 2 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
python-version: "3.9"

- name: Install uv
uses: astral-sh/setup-uv@v4
Expand All @@ -83,7 +83,7 @@ jobs:
- name: Run integration tests
run: uv run pytest tests/integration/ -v
env:
FISH_AUDIO_API_KEY: ${{ secrets.FISH_AUDIO_API_KEY }}
FISH_API_KEY: ${{ secrets.FISH_API_KEY }}

- name: Upload Test Artifacts
uses: actions/upload-artifact@v4
Expand Down
279 changes: 162 additions & 117 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,205 +1,250 @@
# Fish Audio Python SDK

To provide convenient Python program integration for https://docs.fish.audio.
[![PyPI version](https://img.shields.io/pypi/v/fish-audio-sdk.svg)](https://badge.fury.io/py/fish-audio-sdk)
[![Python Version](https://img.shields.io/badge/python-3.9+-blue)](https://pypi.org/project/fish-audio-sdk/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/fish-audio-sdk)](https://pypi.org/project/fish-audio-sdk/)
[![codecov](https://img.shields.io/codecov/c/github/fishaudio/fish-audio-python)](https://codecov.io/gh/fishaudio/fish-audio-python)
[![License](https://img.shields.io/github/license/fishaudio/fish-audio-python)](https://github.com/fishaudio/fish-audio-python/blob/main/LICENSE)

## Install
The official Python library for the Fish Audio API

```bash
pip install fish-audio-sdk
```
> [!NOTE]
> The new release has not officially been released yet - please see legacy SDK documentation for now.
**Documentation:** [Python SDK Guide](https://docs.fish.audio/developer-guide/sdk-guide/python/) | [API Reference](https://docs.fish.audio/api-reference/sdk/python/)

## Usage
> **Note:** If you're using the legacy `fish_audio_sdk` API, see the [migration guide](https://docs.fish.audio/archive/python-sdk-legacy/migration-guide) to upgrade.

### New SDK (Recommended)
## Installation

The new SDK uses the `fishaudio` module:

```python
from fishaudio import FishAudio
```bash
pip install fish-audio-sdk

client = FishAudio(api_key="your_api_key")
# With audio playback utilities
pip install fish-audio-sdk[utils]
```

You can customize the base URL:
## Authentication

```python
from fishaudio import FishAudio
Get your API key from [fish.audio/app/api-keys](https://fish.audio/app/api-keys):

client = FishAudio(api_key="your_api_key", base_url="https://your-proxy-domain")
```bash
export FISH_API_KEY=your_api_key_here
```

### Legacy SDK

The legacy SDK uses the `fish_audio_sdk` module. Initialize a `Session` to use APIs. All APIs have synchronous and asynchronous versions. If you want to use the asynchronous version of the API, you only need to rewrite the original `session.api_call(...)` to `session.api_call.awaitable(...)`.
Or provide directly:

```python
from fish_audio_sdk import Session
from fishaudio import FishAudio

session = Session("your_api_key")
client = FishAudio(api_key="your_api_key")
```

Sometimes, you may need to change our endpoint to another address. You can use
## Quick Start

```python
from fish_audio_sdk import Session

session = Session("your_api_key", base_url="https://your-proxy-domain")
```

### Text to speech
**Synchronous:**

```python
from fish_audio_sdk import Session, TTSRequest
from fishaudio import FishAudio
from fishaudio.utils import play, save

client = FishAudio()

session = Session("your_api_key")
# Generate audio
audio = client.tts.convert(text="Hello, world!")

with open("r.mp3", "wb") as f:
for chunk in session.tts(TTSRequest(text="Hello, world!")):
f.write(chunk)
# Play or save
play(audio)
save(audio, "output.mp3")
```

Or use async version:
**Asynchronous:**

```python
import asyncio
import aiofiles

from fish_audio_sdk import Session, TTSRequest

session = Session("your_api_key")

from fishaudio import AsyncFishAudio
from fishaudio.utils import play, save

async def main():
async with aiofiles.open("r.mp3", "wb") as f:
async for chunk in session.tts.awaitable(
TTSRequest(text="Hello, world!"),
):
await f.write(chunk)

client = AsyncFishAudio()
audio = await client.tts.convert(text="Hello, world!")
play(audio)
save(audio, "output.mp3")

asyncio.run(main())
```

#### Reference Audio
## Core Features

```python
from fish_audio_sdk import TTSRequest
### Text-to-Speech

**With custom voice:**

TTSRequest(
text="Hello, world!",
reference_id="your_model_id",
```python
# Use a specific voice by ID
audio = client.tts.convert(
text="Custom voice",
reference_id="802e3bc2b27e49c2995d23ef70e6ac89"
)
```

Or just use `ReferenceAudio` in `TTSRequest`:
**With speed control:**

```python
from fish_audio_sdk import TTSRequest, ReferenceAudio

TTSRequest(
text="Hello, world!",
references=[
ReferenceAudio(
audio=audio_file.read(),
text="reference audio text",
)
],
audio = client.tts.convert(
text="Speaking faster!",
speed=1.5 # 1.5x speed
)
```

### List models
**Reusable configuration:**

```python
models = session.list_models()
print(models)
from fishaudio.types import TTSConfig, Prosody

config = TTSConfig(
prosody=Prosody(speed=1.2, volume=-5),
reference_id="933563129e564b19a115bedd57b7406a",
format="wav",
latency="balanced"
)

# Reuse across generations
audio1 = client.tts.convert(text="First message", config=config)
audio2 = client.tts.convert(text="Second message", config=config)
```

Or use async version:
**Chunk-by-chunk processing:**

```python
import asyncio
# Stream and process chunks as they arrive
for chunk in client.tts.stream(text="Long content..."):
send_to_websocket(chunk)

# Or collect all chunks
audio = client.tts.stream(text="Hello!").collect()
```

async def main():
models = await session.list_models.awaitable()
print(models)
[Learn more](https://docs.fish.audio/developer-guide/sdk-guide/python/text-to-speech)

### Speech-to-Text

asyncio.run(main())
```python
# Transcribe audio
with open("audio.wav", "rb") as f:
result = client.asr.transcribe(audio=f.read(), language="en")

print(result.text)

# Access timestamped segments
for segment in result.segments:
print(f"[{segment.start:.2f}s - {segment.end:.2f}s] {segment.text}")
```

[Learn more](https://docs.fish.audio/developer-guide/sdk-guide/python/speech-to-text)

### Real-time Streaming

Stream dynamically generated text for conversational AI and live applications:

### Get a model info by id
**Synchronous:**

```python
model = session.get_model("your_model_id")
print(model)
def text_chunks():
yield "Hello, "
yield "this is "
yield "streaming!"

audio_stream = client.tts.stream_websocket(text_chunks(), latency="balanced")
play(audio_stream)
```

Or use async version:
**Asynchronous:**

```python
import asyncio
async def text_chunks():
yield "Hello, "
yield "this is "
yield "streaming!"

audio_stream = await client.tts.stream_websocket(text_chunks(), latency="balanced")
play(audio_stream)
```

async def main():
model = await session.get_model.awaitable("your_model_id")
print(model)

[Learn more](https://docs.fish.audio/developer-guide/sdk-guide/python/websocket)

asyncio.run(main())
```
### Voice Cloning

### Create a model
**Instant cloning:**

```python
model = session.create_model(
title="test",
description="test",
voices=[voice_file.read(), other_voice_file.read()],
cover_image=image_file.read(),
)
print(model)
from fishaudio.types import ReferenceAudio

# Clone voice on-the-fly
with open("reference.wav", "rb") as f:
audio = client.tts.convert(
text="Cloned voice speaking",
references=[ReferenceAudio(
audio=f.read(),
text="Text spoken in reference"
)]
)
```

Or use async version:
**Persistent voice models:**

```python
import asyncio


async def main():
model = await session.create_model.awaitable(
title="test",
description="test",
voices=[voice_file.read(), other_voice_file.read()],
cover_image=image_file.read(),
# Create voice model for reuse
with open("voice_sample.wav", "rb") as f:
voice = client.voices.create(
title="My Voice",
voices=[f.read()],
description="Custom voice clone"
)
print(model)


asyncio.run(main())
# Use the created model
audio = client.tts.convert(
text="Using my saved voice",
reference_id=voice.id
)
```

[Learn more](https://docs.fish.audio/developer-guide/sdk-guide/python/voice-cloning)

### Delete a model
## Resource Clients

```python
session.delete_model("your_model_id")
```
| Resource | Description | Key Methods |
|----------|-------------|-------------|
| `client.tts` | Text-to-speech | `convert()`, `stream()`, `stream_websocket()` |
| `client.asr` | Speech recognition | `transcribe()` |
| `client.voices` | Voice management | `list()`, `get()`, `create()`, `update()`, `delete()` |
| `client.account` | Account info | `get_credits()`, `get_package()` |

Or use async version:
## Error Handling

```python
import asyncio
from fishaudio.exceptions import (
AuthenticationError,
RateLimitError,
ValidationError,
FishAudioError
)

try:
audio = client.tts.convert(text="Hello!")
except AuthenticationError:
print("Invalid API key")
except RateLimitError:
print("Rate limit exceeded")
except ValidationError as e:
print(f"Invalid request: {e}")
except FishAudioError as e:
print(f"API error: {e}")
```

## Resources

async def main():
await session.delete_model.awaitable("your_model_id")
- **Documentation:** [SDK Guide](https://docs.fish.audio/developer-guide/sdk-guide/python/) | [API Reference](https://docs.fish.audio/api-reference/sdk/python/)
- **Package:** [PyPI](https://pypi.org/project/fish-audio-sdk/) | [GitHub](https://github.com/fishaudio/fish-audio-python)
- **Legacy SDK:** [Documentation](https://docs.fish.audio/archive/python-sdk-legacy) | [Migration Guide](https://docs.fish.audio/archive/python-sdk-legacy/migration-guide)

## License

asyncio.run(main())
```
This project is licensed under the Apache-2.0 License - see the [LICENSE](LICENSE) file for details.
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ Example scripts demonstrating how to use the Fish Audio Python SDK.
```bash
# Install and setup
pip install fishaudio
export FISH_AUDIO_API_KEY="your_api_key"
export FISH_API_KEY="your_api_key"
```
Loading