Skip to content

Commit ba4ab45

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 <[email protected]>
1 parent 2dc4384 commit ba4ab45

Some content is hidden

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

58 files changed

+6099
-639
lines changed

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