Skip to content

Commit 35d9870

Browse files
committed
feat: implement Fish Audio SDK v1.0.0 with dual module support
Major changes: - Introduce new 'fishaudio' module with modern Python SDK architecture - Restore legacy 'fish_audio_sdk' module for backwards compatibility - Rename package to 'fish-audio-sdk' (from 'fish-audio') - Add comprehensive test suite (unit and integration tests) - Implement real-time WebSocket streaming for TTS - Add utility functions for audio playback and file operations - Support Python 3.9-3.14 - Include examples and documentation New Features: - Sync/async client support (FishAudio/AsyncFishAudio) - TTS with streaming and real-time WebSocket support - Voice management (list, get, create, delete) - ASR (automatic speech recognition) support - Account/credits management - Event-based streaming (TextEvent, FlushEvent) - Audio utilities (play, save, stream) Breaking Changes: - New SDK uses 'fishaudio' module instead of 'fish_audio_sdk' - Legacy 'fish_audio_sdk' module maintained for compatibility Signed-off-by: James Ding <jamesding365@gmail.com>
1 parent 2dc4384 commit 35d9870

58 files changed

Lines changed: 6096 additions & 639 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FISH_AUDIO_API_KEY=

.github/workflows/ci.yml

Lines changed: 0 additions & 54 deletions
This file was deleted.

.github/workflows/python.yml

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
name: Python
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
lint:
7+
name: Lint & Format
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v4
11+
12+
- name: Install uv
13+
uses: astral-sh/setup-uv@v4
14+
15+
- name: Check formatting
16+
run: uv run ruff format --check .
17+
18+
- name: Lint code
19+
run: uv run ruff check .
20+
21+
type-check:
22+
name: Type Check
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v4
26+
- name: Set up Python
27+
uses: actions/setup-python@v5
28+
with:
29+
python-version: "3.x"
30+
- name: Install uv
31+
uses: astral-sh/setup-uv@v4
32+
- name: Install dependencies
33+
run: uv sync
34+
- name: Run mypy
35+
run: uv run mypy src/fishaudio --ignore-missing-imports
36+
37+
test:
38+
name: Test Python ${{ matrix.python-version }}
39+
runs-on: ubuntu-latest
40+
strategy:
41+
matrix:
42+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.x"]
43+
steps:
44+
- uses: actions/checkout@v4
45+
- name: Set up Python
46+
uses: actions/setup-python@v5
47+
with:
48+
python-version: ${{ matrix.python-version }}
49+
- name: Install uv
50+
uses: astral-sh/setup-uv@v4
51+
- name: Install dependencies
52+
run: uv sync
53+
- name: Run tests with coverage
54+
run: uv run pytest tests/unit/ -v --cov=src/fishaudio --cov-report=xml --cov-report=term
55+
- name: Upload coverage to Codecov
56+
uses: codecov/codecov-action@v4
57+
if: matrix.python-version == '3.x'
58+
with:
59+
files: ./coverage.xml
60+
fail_ci_if_error: false
61+
62+
integration:
63+
name: Integration Tests
64+
runs-on: ubuntu-latest
65+
needs: [test]
66+
steps:
67+
- uses: actions/checkout@v4
68+
69+
- name: Set up Python
70+
uses: actions/setup-python@v5
71+
with:
72+
python-version: "3.x"
73+
74+
- name: Install uv
75+
uses: astral-sh/setup-uv@v4
76+
77+
- name: Install dependencies
78+
run: uv sync
79+
80+
- name: Run integration tests
81+
run: uv run pytest tests/integration/ -v
82+
env:
83+
FISH_AUDIO_API_KEY: ${{ secrets.FISH_AUDIO_API_KEY }}
84+
85+
build:
86+
name: Build Package
87+
runs-on: ubuntu-latest
88+
needs: [lint, type-check, test, integration]
89+
steps:
90+
- uses: actions/checkout@v4
91+
- name: Install uv
92+
uses: astral-sh/setup-uv@v4
93+
- name: Build package
94+
run: uv build
95+
- name: Check package
96+
run: uv run python -c "import fishaudio; print(f'fishaudio v{fishaudio.__version__}')"
97+
- name: Upload build artifacts
98+
uses: actions/upload-artifact@v4
99+
with:
100+
name: dist
101+
path: dist/
102+
103+
publish:
104+
name: Publish to PyPI
105+
runs-on: ubuntu-latest
106+
needs: [lint, type-check, test, integration, build]
107+
if: startsWith(github.ref, 'refs/tags/')
108+
environment: pypi
109+
permissions:
110+
id-token: write
111+
steps:
112+
- uses: actions/checkout@v4
113+
- name: Install uv
114+
uses: astral-sh/setup-uv@v4
115+
- name: Build package
116+
run: uv build
117+
- name: Publish to PyPI
118+
uses: pypa/gh-action-pypi-publish@release/v1

.mcp.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"mcpServers": {
3+
"fish-audio": {
4+
"type": "http",
5+
"url": "https://docs.fish.audio/mcp"
6+
}
7+
}
8+
}

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,27 @@ pip install fish-audio-sdk
1010

1111
## Usage
1212

13-
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(...)`.
13+
### New SDK (Recommended)
14+
15+
The new SDK uses the `fishaudio` module:
16+
17+
```python
18+
from fishaudio import FishAudio
19+
20+
client = FishAudio(api_key="your_api_key")
21+
```
22+
23+
You can customize the base URL:
24+
25+
```python
26+
from fishaudio import FishAudio
27+
28+
client = FishAudio(api_key="your_api_key", base_url="https://your-proxy-domain")
29+
```
30+
31+
### Legacy SDK
32+
33+
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(...)`.
1434

1535
```python
1636
from fish_audio_sdk import Session

examples/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Fish Audio SDK Examples
2+
3+
Example scripts demonstrating how to use the Fish Audio Python SDK.
4+
5+
```bash
6+
# Install and setup
7+
pip install fishaudio
8+
export FISH_AUDIO_API_KEY="your_api_key"
9+
```
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Simple Text-to-Speech Example
3+
4+
This example demonstrates the most basic usage of the Fish Audio SDK:
5+
- Initialize the client
6+
- Convert text to speech
7+
- Save the audio to an MP3 file
8+
9+
Requirements:
10+
pip install fishaudio
11+
12+
Environment Setup:
13+
export FISH_AUDIO_API_KEY="your_api_key_here"
14+
# Or pass api_key directly to the client
15+
16+
Expected Output:
17+
- Creates "output.mp3" in the current directory
18+
- Audio file contains the spoken text
19+
"""
20+
21+
import os
22+
from fishaudio import FishAudio
23+
from fishaudio.utils import save
24+
25+
26+
def main():
27+
# Initialize the client with your API key
28+
# Option 1: Use environment variable FISH_AUDIO_API_KEY
29+
# Option 2: Pass api_key directly: FishAudio(api_key="your_key")
30+
client = FishAudio()
31+
32+
# The text you want to convert to speech
33+
text = "Hello! This is a simple text-to-speech example using the Fish Audio SDK."
34+
35+
print(f"Converting text to speech: '{text}'")
36+
37+
# Convert text to speech
38+
# This returns an iterator of audio bytes
39+
audio = client.tts.convert(text=text)
40+
41+
# Save the audio to a file
42+
output_file = "output.mp3"
43+
save(audio, output_file)
44+
45+
print(f"✓ Audio saved to {output_file}")
46+
print(f" File size: {os.path.getsize(output_file) / 1024:.2f} KB")
47+
48+
49+
if __name__ == "__main__":
50+
try:
51+
main()
52+
except Exception as e:
53+
print(f"Error: {e}")
54+
print("\nMake sure you have set your API key:")
55+
print(" export FISH_AUDIO_API_KEY='your_api_key'")
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""
2+
Play Audio Example
3+
4+
This example demonstrates how to play generated audio immediately:
5+
- Generate text-to-speech audio
6+
- Play audio using the play() utility
7+
- Different playback methods (ffmpeg, sounddevice)
8+
9+
Requirements:
10+
pip install fishaudio
11+
12+
# For audio playback (choose one):
13+
# Option 1 (recommended): Install ffmpeg
14+
# macOS: brew install ffmpeg
15+
# Ubuntu: sudo apt install ffmpeg
16+
# Windows: Download from ffmpeg.org
17+
18+
# Option 2: Use sounddevice (Python-only)
19+
# pip install sounddevice soundfile
20+
21+
Environment Setup:
22+
export FISH_AUDIO_API_KEY="your_api_key_here"
23+
24+
Expected Output:
25+
- Plays the generated audio through your speakers
26+
- No file is saved (unless you uncomment the save section)
27+
"""
28+
29+
from fishaudio import FishAudio
30+
from fishaudio.utils import play
31+
32+
33+
def main():
34+
# Initialize the client
35+
client = FishAudio()
36+
37+
# Text to convert to speech
38+
text = "Welcome to Fish Audio! This audio will play immediately after generation."
39+
40+
print(f"Generating speech: '{text}'")
41+
print("Please ensure your speakers are on...\n")
42+
43+
# Generate the audio
44+
audio = client.tts.convert(text=text)
45+
46+
# Play the audio immediately
47+
# By default, this uses ffplay (from ffmpeg) if available,
48+
# otherwise falls back to sounddevice
49+
print("▶ Playing audio...")
50+
play(audio)
51+
print("✓ Playback complete!")
52+
53+
# Optional: Save the audio to a file as well
54+
# Uncomment the following lines if you want to save it:
55+
# print("\nSaving audio to file...")
56+
# audio = client.tts.convert(text=text) # Regenerate since audio was consumed
57+
# save(audio, "playback_example.mp3")
58+
# print("✓ Saved to playback_example.mp3")
59+
60+
61+
def demo_playback_methods():
62+
"""
63+
Demonstrate different playback methods.
64+
65+
Note: The play() function automatically chooses the best available method,
66+
but you can force specific methods by modifying the code.
67+
"""
68+
client = FishAudio()
69+
text = "This is a demonstration of different playback methods."
70+
71+
# Method 1: Default (uses ffmpeg if available)
72+
print("Method 1: Default playback")
73+
audio = client.tts.convert(text=text)
74+
play(audio, use_ffmpeg=True)
75+
76+
# Method 2: Using sounddevice (requires pip install sounddevice soundfile)
77+
print("\nMethod 2: Sounddevice playback")
78+
audio = client.tts.convert(text=text)
79+
play(audio, use_ffmpeg=False)
80+
81+
# Method 3: Jupyter notebook mode (for .ipynb files)
82+
# This returns an IPython.display.Audio object
83+
# Uncomment if running in a notebook:
84+
# audio = client.tts.convert(text=text)
85+
# play(audio, notebook=True)
86+
87+
88+
if __name__ == "__main__":
89+
try:
90+
main()
91+
92+
# Uncomment to try different playback methods:
93+
# print("\n" + "="*50)
94+
# print("Testing different playback methods...")
95+
# print("="*50 + "\n")
96+
# demo_playback_methods()
97+
98+
except Exception as e:
99+
print(f"Error: {e}")
100+
print("\nTroubleshooting:")
101+
print("1. Make sure your API key is set: export FISH_AUDIO_API_KEY='your_key'")
102+
print("2. Install ffmpeg for audio playback:")
103+
print(" - macOS: brew install ffmpeg")
104+
print(" - Ubuntu: sudo apt install ffmpeg")
105+
print("3. Or install sounddevice: pip install sounddevice soundfile")

0 commit comments

Comments
 (0)