audiopod 1.2.0__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- audiopod-1.4.0/PKG-INFO +206 -0
- audiopod-1.4.0/README.md +173 -0
- audiopod-1.4.0/audiopod/__init__.py +29 -0
- audiopod-1.4.0/audiopod/client.py +306 -0
- audiopod-1.4.0/audiopod/config.py +17 -0
- audiopod-1.4.0/audiopod/exceptions.py +41 -0
- {audiopod-1.2.0 → audiopod-1.4.0}/audiopod/services/__init__.py +8 -6
- audiopod-1.4.0/audiopod/services/base.py +69 -0
- audiopod-1.4.0/audiopod/services/credits.py +42 -0
- audiopod-1.4.0/audiopod/services/denoiser.py +131 -0
- audiopod-1.4.0/audiopod/services/music.py +217 -0
- audiopod-1.4.0/audiopod/services/speaker.py +134 -0
- audiopod-1.4.0/audiopod/services/stem_extraction.py +168 -0
- audiopod-1.4.0/audiopod/services/transcription.py +187 -0
- audiopod-1.4.0/audiopod/services/translation.py +135 -0
- audiopod-1.4.0/audiopod/services/voice.py +187 -0
- audiopod-1.4.0/audiopod/services/wallet.py +235 -0
- audiopod-1.4.0/audiopod.egg-info/PKG-INFO +206 -0
- {audiopod-1.2.0 → audiopod-1.4.0}/audiopod.egg-info/SOURCES.txt +6 -12
- audiopod-1.4.0/audiopod.egg-info/dependency_links.txt +1 -0
- audiopod-1.4.0/audiopod.egg-info/requires.txt +8 -0
- audiopod-1.4.0/audiopod.egg-info/top_level.txt +1 -0
- audiopod-1.4.0/pyproject.toml +59 -0
- audiopod-1.2.0/CHANGELOG.md +0 -279
- audiopod-1.2.0/MANIFEST.in +0 -25
- audiopod-1.2.0/PKG-INFO +0 -454
- audiopod-1.2.0/README.md +0 -402
- audiopod-1.2.0/audiopod/__init__.py +0 -83
- audiopod-1.2.0/audiopod/cli.py +0 -285
- audiopod-1.2.0/audiopod/client.py +0 -335
- audiopod-1.2.0/audiopod/config.py +0 -63
- audiopod-1.2.0/audiopod/exceptions.py +0 -96
- audiopod-1.2.0/audiopod/models.py +0 -250
- audiopod-1.2.0/audiopod/py.typed +0 -2
- audiopod-1.2.0/audiopod/services/base.py +0 -213
- audiopod-1.2.0/audiopod/services/credits.py +0 -46
- audiopod-1.2.0/audiopod/services/denoiser.py +0 -51
- audiopod-1.2.0/audiopod/services/karaoke.py +0 -61
- audiopod-1.2.0/audiopod/services/music.py +0 -522
- audiopod-1.2.0/audiopod/services/speaker.py +0 -53
- audiopod-1.2.0/audiopod/services/stem_extraction.py +0 -180
- audiopod-1.2.0/audiopod/services/transcription.py +0 -212
- audiopod-1.2.0/audiopod/services/translation.py +0 -196
- audiopod-1.2.0/audiopod/services/voice.py +0 -470
- audiopod-1.2.0/examples/README.md +0 -81
- audiopod-1.2.0/examples/basic_usage.py +0 -536
- audiopod-1.2.0/pyproject.toml +0 -146
- audiopod-1.2.0/requirements.txt +0 -7
- audiopod-1.2.0/setup.py +0 -93
- audiopod-1.2.0/tests/test_end_to_end_integration.py +0 -617
- audiopod-1.2.0/tests/test_sdk_api_compatibility.py +0 -892
- {audiopod-1.2.0 → audiopod-1.4.0}/LICENSE +0 -0
- {audiopod-1.2.0 → audiopod-1.4.0}/setup.cfg +0 -0
audiopod-1.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: audiopod
|
|
3
|
+
Version: 1.4.0
|
|
4
|
+
Summary: AudioPod SDK for Python - Professional Audio Processing powered by AI
|
|
5
|
+
Author-email: AudioPod AI <support@audiopod.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://audiopod.ai
|
|
8
|
+
Project-URL: Documentation, https://docs.audiopod.ai
|
|
9
|
+
Project-URL: Repository, https://github.com/AudiopodAI/audiopod-python
|
|
10
|
+
Keywords: audiopod,audio,ai,voice,cloning,transcription,stem-separation,music,sdk
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: requests>=2.28.0
|
|
26
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
30
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# AudioPod Python SDK
|
|
35
|
+
|
|
36
|
+
Official Python SDK for [AudioPod AI](https://audiopod.ai) - Professional Audio Processing powered by AI.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install audiopod
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from audiopod import Client
|
|
48
|
+
|
|
49
|
+
# Initialize client
|
|
50
|
+
client = Client(api_key="ap_your_api_key")
|
|
51
|
+
# Or set AUDIOPOD_API_KEY environment variable
|
|
52
|
+
|
|
53
|
+
# Check wallet balance
|
|
54
|
+
balance = client.wallet.get_balance()
|
|
55
|
+
print(f"Balance: {balance['balance_usd']}")
|
|
56
|
+
|
|
57
|
+
# Extract stems from audio
|
|
58
|
+
job = client.stem_extraction.extract_stems(
|
|
59
|
+
audio_file="song.mp3",
|
|
60
|
+
stem_types=["vocals", "drums", "bass", "other"],
|
|
61
|
+
wait_for_completion=True
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Download stems
|
|
65
|
+
for stem, url in job["download_urls"].items():
|
|
66
|
+
print(f"{stem}: {url}")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Services
|
|
70
|
+
|
|
71
|
+
### Wallet (API Billing)
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# Get balance
|
|
75
|
+
balance = client.wallet.get_balance()
|
|
76
|
+
|
|
77
|
+
# Get pricing
|
|
78
|
+
pricing = client.wallet.get_pricing()
|
|
79
|
+
|
|
80
|
+
# Estimate cost
|
|
81
|
+
estimate = client.wallet.estimate_cost("stem_extraction", duration_seconds=300)
|
|
82
|
+
|
|
83
|
+
# Top up wallet
|
|
84
|
+
checkout = client.wallet.create_topup_checkout(amount_cents=2500) # $25
|
|
85
|
+
print(f"Pay at: {checkout['url']}")
|
|
86
|
+
|
|
87
|
+
# Usage history
|
|
88
|
+
usage = client.wallet.get_usage_history(page=1, limit=50)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Stem Extraction
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# Extract all stems
|
|
95
|
+
job = client.stem_extraction.extract_stems(
|
|
96
|
+
audio_file="song.mp3",
|
|
97
|
+
stem_types=["vocals", "drums", "bass", "other"],
|
|
98
|
+
wait_for_completion=True
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# From URL
|
|
102
|
+
job = client.stem_extraction.extract_stems(
|
|
103
|
+
url="https://example.com/song.mp3",
|
|
104
|
+
stem_types=["vocals", "other"]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Check status
|
|
108
|
+
status = client.stem_extraction.get_job(job_id=123)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Transcription
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# Transcribe audio
|
|
115
|
+
result = client.transcription.transcribe(
|
|
116
|
+
audio_file="podcast.mp3",
|
|
117
|
+
speaker_diarization=True,
|
|
118
|
+
wait_for_completion=True
|
|
119
|
+
)
|
|
120
|
+
print(result["transcript"])
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Voice Cloning & TTS
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
# List voices
|
|
127
|
+
voices = client.voice.list_voices()
|
|
128
|
+
|
|
129
|
+
# Generate speech
|
|
130
|
+
audio = client.voice.generate_speech(
|
|
131
|
+
text="Hello, world!",
|
|
132
|
+
voice_id=123,
|
|
133
|
+
wait_for_completion=True
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Music Generation
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
# Generate music
|
|
141
|
+
result = client.music.generate(
|
|
142
|
+
prompt="upbeat electronic dance music",
|
|
143
|
+
duration=30,
|
|
144
|
+
wait_for_completion=True
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Noise Reduction
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
# Denoise audio
|
|
152
|
+
result = client.denoiser.denoise(
|
|
153
|
+
audio_file="noisy.mp3",
|
|
154
|
+
mode="studio",
|
|
155
|
+
wait_for_completion=True
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Async Support
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import asyncio
|
|
163
|
+
from audiopod import AsyncClient
|
|
164
|
+
|
|
165
|
+
async def main():
|
|
166
|
+
async with AsyncClient() as client:
|
|
167
|
+
balance = await client.wallet.get_balance()
|
|
168
|
+
print(f"Balance: {balance['balance_usd']}")
|
|
169
|
+
|
|
170
|
+
asyncio.run(main())
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Error Handling
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from audiopod import Client
|
|
177
|
+
from audiopod.exceptions import (
|
|
178
|
+
AuthenticationError,
|
|
179
|
+
InsufficientBalanceError,
|
|
180
|
+
RateLimitError,
|
|
181
|
+
APIError
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
client = Client()
|
|
186
|
+
job = client.stem_extraction.extract_stems(audio_file="song.mp3")
|
|
187
|
+
except AuthenticationError:
|
|
188
|
+
print("Invalid API key")
|
|
189
|
+
except InsufficientBalanceError as e:
|
|
190
|
+
print(f"Need to top up: required {e.required_cents} cents")
|
|
191
|
+
except RateLimitError:
|
|
192
|
+
print("Rate limit exceeded, try again later")
|
|
193
|
+
except APIError as e:
|
|
194
|
+
print(f"API error ({e.status_code}): {e}")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Documentation
|
|
198
|
+
|
|
199
|
+
- [API Reference](https://docs.audiopod.ai)
|
|
200
|
+
- [API Wallet](https://docs.audiopod.ai/account/api-wallet)
|
|
201
|
+
- [Stem Separation](https://docs.audiopod.ai/api-reference/stem-splitter)
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|
|
206
|
+
|
audiopod-1.4.0/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# AudioPod Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for [AudioPod AI](https://audiopod.ai) - Professional Audio Processing powered by AI.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install audiopod
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from audiopod import Client
|
|
15
|
+
|
|
16
|
+
# Initialize client
|
|
17
|
+
client = Client(api_key="ap_your_api_key")
|
|
18
|
+
# Or set AUDIOPOD_API_KEY environment variable
|
|
19
|
+
|
|
20
|
+
# Check wallet balance
|
|
21
|
+
balance = client.wallet.get_balance()
|
|
22
|
+
print(f"Balance: {balance['balance_usd']}")
|
|
23
|
+
|
|
24
|
+
# Extract stems from audio
|
|
25
|
+
job = client.stem_extraction.extract_stems(
|
|
26
|
+
audio_file="song.mp3",
|
|
27
|
+
stem_types=["vocals", "drums", "bass", "other"],
|
|
28
|
+
wait_for_completion=True
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Download stems
|
|
32
|
+
for stem, url in job["download_urls"].items():
|
|
33
|
+
print(f"{stem}: {url}")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Services
|
|
37
|
+
|
|
38
|
+
### Wallet (API Billing)
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
# Get balance
|
|
42
|
+
balance = client.wallet.get_balance()
|
|
43
|
+
|
|
44
|
+
# Get pricing
|
|
45
|
+
pricing = client.wallet.get_pricing()
|
|
46
|
+
|
|
47
|
+
# Estimate cost
|
|
48
|
+
estimate = client.wallet.estimate_cost("stem_extraction", duration_seconds=300)
|
|
49
|
+
|
|
50
|
+
# Top up wallet
|
|
51
|
+
checkout = client.wallet.create_topup_checkout(amount_cents=2500) # $25
|
|
52
|
+
print(f"Pay at: {checkout['url']}")
|
|
53
|
+
|
|
54
|
+
# Usage history
|
|
55
|
+
usage = client.wallet.get_usage_history(page=1, limit=50)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Stem Extraction
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
# Extract all stems
|
|
62
|
+
job = client.stem_extraction.extract_stems(
|
|
63
|
+
audio_file="song.mp3",
|
|
64
|
+
stem_types=["vocals", "drums", "bass", "other"],
|
|
65
|
+
wait_for_completion=True
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# From URL
|
|
69
|
+
job = client.stem_extraction.extract_stems(
|
|
70
|
+
url="https://example.com/song.mp3",
|
|
71
|
+
stem_types=["vocals", "other"]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Check status
|
|
75
|
+
status = client.stem_extraction.get_job(job_id=123)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Transcription
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
# Transcribe audio
|
|
82
|
+
result = client.transcription.transcribe(
|
|
83
|
+
audio_file="podcast.mp3",
|
|
84
|
+
speaker_diarization=True,
|
|
85
|
+
wait_for_completion=True
|
|
86
|
+
)
|
|
87
|
+
print(result["transcript"])
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Voice Cloning & TTS
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
# List voices
|
|
94
|
+
voices = client.voice.list_voices()
|
|
95
|
+
|
|
96
|
+
# Generate speech
|
|
97
|
+
audio = client.voice.generate_speech(
|
|
98
|
+
text="Hello, world!",
|
|
99
|
+
voice_id=123,
|
|
100
|
+
wait_for_completion=True
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Music Generation
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# Generate music
|
|
108
|
+
result = client.music.generate(
|
|
109
|
+
prompt="upbeat electronic dance music",
|
|
110
|
+
duration=30,
|
|
111
|
+
wait_for_completion=True
|
|
112
|
+
)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Noise Reduction
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# Denoise audio
|
|
119
|
+
result = client.denoiser.denoise(
|
|
120
|
+
audio_file="noisy.mp3",
|
|
121
|
+
mode="studio",
|
|
122
|
+
wait_for_completion=True
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Async Support
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
import asyncio
|
|
130
|
+
from audiopod import AsyncClient
|
|
131
|
+
|
|
132
|
+
async def main():
|
|
133
|
+
async with AsyncClient() as client:
|
|
134
|
+
balance = await client.wallet.get_balance()
|
|
135
|
+
print(f"Balance: {balance['balance_usd']}")
|
|
136
|
+
|
|
137
|
+
asyncio.run(main())
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Error Handling
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from audiopod import Client
|
|
144
|
+
from audiopod.exceptions import (
|
|
145
|
+
AuthenticationError,
|
|
146
|
+
InsufficientBalanceError,
|
|
147
|
+
RateLimitError,
|
|
148
|
+
APIError
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
client = Client()
|
|
153
|
+
job = client.stem_extraction.extract_stems(audio_file="song.mp3")
|
|
154
|
+
except AuthenticationError:
|
|
155
|
+
print("Invalid API key")
|
|
156
|
+
except InsufficientBalanceError as e:
|
|
157
|
+
print(f"Need to top up: required {e.required_cents} cents")
|
|
158
|
+
except RateLimitError:
|
|
159
|
+
print("Rate limit exceeded, try again later")
|
|
160
|
+
except APIError as e:
|
|
161
|
+
print(f"API error ({e.status_code}): {e}")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Documentation
|
|
165
|
+
|
|
166
|
+
- [API Reference](https://docs.audiopod.ai)
|
|
167
|
+
- [API Wallet](https://docs.audiopod.ai/account/api-wallet)
|
|
168
|
+
- [Stem Separation](https://docs.audiopod.ai/api-reference/stem-splitter)
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
|
173
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AudioPod SDK for Python
|
|
3
|
+
Professional Audio Processing powered by AI
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "1.3.0"
|
|
7
|
+
|
|
8
|
+
from .client import Client, AsyncClient
|
|
9
|
+
from .exceptions import (
|
|
10
|
+
AudioPodError,
|
|
11
|
+
AuthenticationError,
|
|
12
|
+
APIError,
|
|
13
|
+
RateLimitError,
|
|
14
|
+
ValidationError,
|
|
15
|
+
InsufficientBalanceError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Client",
|
|
20
|
+
"AsyncClient",
|
|
21
|
+
"AudioPodError",
|
|
22
|
+
"AuthenticationError",
|
|
23
|
+
"APIError",
|
|
24
|
+
"RateLimitError",
|
|
25
|
+
"ValidationError",
|
|
26
|
+
"InsufficientBalanceError",
|
|
27
|
+
"__version__",
|
|
28
|
+
]
|
|
29
|
+
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AudioPod API Client
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Optional, Dict, Any
|
|
8
|
+
from urllib.parse import urljoin
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
import aiohttp
|
|
12
|
+
from requests.adapters import HTTPAdapter
|
|
13
|
+
from urllib3.util.retry import Retry
|
|
14
|
+
|
|
15
|
+
from .config import ClientConfig
|
|
16
|
+
from .exceptions import AuthenticationError, APIError, RateLimitError, InsufficientBalanceError
|
|
17
|
+
from .services import (
|
|
18
|
+
VoiceService,
|
|
19
|
+
MusicService,
|
|
20
|
+
TranscriptionService,
|
|
21
|
+
TranslationService,
|
|
22
|
+
SpeakerService,
|
|
23
|
+
DenoiserService,
|
|
24
|
+
CreditService,
|
|
25
|
+
StemExtractionService,
|
|
26
|
+
WalletService,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BaseClient:
|
|
33
|
+
"""Base client with common functionality"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
api_key: Optional[str] = None,
|
|
38
|
+
base_url: Optional[str] = None,
|
|
39
|
+
timeout: int = 30,
|
|
40
|
+
max_retries: int = 3,
|
|
41
|
+
verify_ssl: bool = True,
|
|
42
|
+
debug: bool = False,
|
|
43
|
+
):
|
|
44
|
+
"""
|
|
45
|
+
Initialize the AudioPod API client.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
api_key: Your AudioPod API key. If None, reads from AUDIOPOD_API_KEY env var.
|
|
49
|
+
base_url: API base URL. Defaults to https://api.audiopod.ai
|
|
50
|
+
timeout: Request timeout in seconds.
|
|
51
|
+
max_retries: Maximum retries for failed requests.
|
|
52
|
+
verify_ssl: Whether to verify SSL certificates.
|
|
53
|
+
debug: Enable debug logging.
|
|
54
|
+
"""
|
|
55
|
+
if debug:
|
|
56
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
57
|
+
|
|
58
|
+
self.api_key = api_key or os.getenv("AUDIOPOD_API_KEY")
|
|
59
|
+
if not self.api_key:
|
|
60
|
+
raise AuthenticationError(
|
|
61
|
+
"API key required. Pass api_key or set AUDIOPOD_API_KEY environment variable."
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if not self.api_key.startswith("ap_"):
|
|
65
|
+
raise AuthenticationError("Invalid API key format. Keys start with 'ap_'")
|
|
66
|
+
|
|
67
|
+
self.config = ClientConfig(
|
|
68
|
+
base_url=base_url or "https://api.audiopod.ai",
|
|
69
|
+
timeout=timeout,
|
|
70
|
+
max_retries=max_retries,
|
|
71
|
+
verify_ssl=verify_ssl,
|
|
72
|
+
debug=debug,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def _get_headers(self) -> Dict[str, str]:
|
|
76
|
+
return {
|
|
77
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
"User-Agent": f"audiopod-python/{self.config.version}",
|
|
80
|
+
"Accept": "application/json",
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
|
|
84
|
+
try:
|
|
85
|
+
response.raise_for_status()
|
|
86
|
+
if response.status_code == 204:
|
|
87
|
+
return {}
|
|
88
|
+
return response.json()
|
|
89
|
+
except requests.exceptions.HTTPError as e:
|
|
90
|
+
if response.status_code == 401:
|
|
91
|
+
raise AuthenticationError("Invalid API key")
|
|
92
|
+
elif response.status_code == 402:
|
|
93
|
+
try:
|
|
94
|
+
data = response.json()
|
|
95
|
+
raise InsufficientBalanceError(
|
|
96
|
+
data.get("message", "Insufficient balance"),
|
|
97
|
+
required_cents=data.get("required_cents"),
|
|
98
|
+
available_cents=data.get("available_cents"),
|
|
99
|
+
)
|
|
100
|
+
except (ValueError, KeyError):
|
|
101
|
+
raise InsufficientBalanceError("Insufficient wallet balance")
|
|
102
|
+
elif response.status_code == 429:
|
|
103
|
+
raise RateLimitError("Rate limit exceeded")
|
|
104
|
+
else:
|
|
105
|
+
try:
|
|
106
|
+
error_data = response.json()
|
|
107
|
+
message = error_data.get("detail", str(e))
|
|
108
|
+
except:
|
|
109
|
+
message = str(e)
|
|
110
|
+
raise APIError(f"API error: {message}", status_code=response.status_code)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Client(BaseClient):
|
|
114
|
+
"""
|
|
115
|
+
Synchronous AudioPod API Client.
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
```python
|
|
119
|
+
from audiopod import Client
|
|
120
|
+
|
|
121
|
+
client = Client(api_key="ap_your_key")
|
|
122
|
+
|
|
123
|
+
# Check wallet balance
|
|
124
|
+
balance = client.wallet.get_balance()
|
|
125
|
+
print(f"Balance: {balance['balance_usd']}")
|
|
126
|
+
|
|
127
|
+
# Extract stems
|
|
128
|
+
job = client.stem_extraction.extract_stems(
|
|
129
|
+
audio_file="song.mp3",
|
|
130
|
+
stem_types=["vocals", "drums", "bass", "other"]
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def __init__(self, **kwargs):
|
|
136
|
+
super().__init__(**kwargs)
|
|
137
|
+
|
|
138
|
+
self.session = requests.Session()
|
|
139
|
+
retry_strategy = Retry(
|
|
140
|
+
total=self.config.max_retries,
|
|
141
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
142
|
+
allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "DELETE"],
|
|
143
|
+
backoff_factor=1,
|
|
144
|
+
)
|
|
145
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
146
|
+
self.session.mount("http://", adapter)
|
|
147
|
+
self.session.mount("https://", adapter)
|
|
148
|
+
|
|
149
|
+
# Services
|
|
150
|
+
self.voice = VoiceService(self)
|
|
151
|
+
self.music = MusicService(self)
|
|
152
|
+
self.transcription = TranscriptionService(self)
|
|
153
|
+
self.translation = TranslationService(self)
|
|
154
|
+
self.speaker = SpeakerService(self)
|
|
155
|
+
self.denoiser = DenoiserService(self)
|
|
156
|
+
self.credits = CreditService(self)
|
|
157
|
+
self.stem_extraction = StemExtractionService(self)
|
|
158
|
+
self.wallet = WalletService(self)
|
|
159
|
+
|
|
160
|
+
def request(
|
|
161
|
+
self,
|
|
162
|
+
method: str,
|
|
163
|
+
endpoint: str,
|
|
164
|
+
data: Optional[Dict[str, Any]] = None,
|
|
165
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
166
|
+
files: Optional[Dict[str, Any]] = None,
|
|
167
|
+
params: Optional[Dict[str, Any]] = None,
|
|
168
|
+
) -> Dict[str, Any]:
|
|
169
|
+
"""Make API request."""
|
|
170
|
+
url = urljoin(self.config.base_url, endpoint)
|
|
171
|
+
headers = self._get_headers()
|
|
172
|
+
|
|
173
|
+
if files:
|
|
174
|
+
headers.pop("Content-Type", None)
|
|
175
|
+
|
|
176
|
+
response = self.session.request(
|
|
177
|
+
method=method,
|
|
178
|
+
url=url,
|
|
179
|
+
headers=headers,
|
|
180
|
+
data=data,
|
|
181
|
+
json=json_data,
|
|
182
|
+
files=files,
|
|
183
|
+
params=params,
|
|
184
|
+
timeout=self.config.timeout,
|
|
185
|
+
verify=self.config.verify_ssl,
|
|
186
|
+
)
|
|
187
|
+
return self._handle_response(response)
|
|
188
|
+
|
|
189
|
+
def get_user_info(self) -> Dict[str, Any]:
|
|
190
|
+
"""Get current user information."""
|
|
191
|
+
return self.request("GET", "/api/v1/auth/me")
|
|
192
|
+
|
|
193
|
+
def close(self):
|
|
194
|
+
"""Close client session."""
|
|
195
|
+
self.session.close()
|
|
196
|
+
|
|
197
|
+
def __enter__(self):
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
201
|
+
self.close()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class AsyncClient(BaseClient):
|
|
205
|
+
"""
|
|
206
|
+
Asynchronous AudioPod API Client.
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
```python
|
|
210
|
+
import asyncio
|
|
211
|
+
from audiopod import AsyncClient
|
|
212
|
+
|
|
213
|
+
async def main():
|
|
214
|
+
async with AsyncClient(api_key="ap_your_key") as client:
|
|
215
|
+
balance = await client.wallet.get_balance()
|
|
216
|
+
print(f"Balance: {balance['balance_usd']}")
|
|
217
|
+
|
|
218
|
+
asyncio.run(main())
|
|
219
|
+
```
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
def __init__(self, **kwargs):
|
|
223
|
+
super().__init__(**kwargs)
|
|
224
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
225
|
+
|
|
226
|
+
# Services
|
|
227
|
+
self.voice = VoiceService(self, async_mode=True)
|
|
228
|
+
self.music = MusicService(self, async_mode=True)
|
|
229
|
+
self.transcription = TranscriptionService(self, async_mode=True)
|
|
230
|
+
self.translation = TranslationService(self, async_mode=True)
|
|
231
|
+
self.speaker = SpeakerService(self, async_mode=True)
|
|
232
|
+
self.denoiser = DenoiserService(self, async_mode=True)
|
|
233
|
+
self.credits = CreditService(self, async_mode=True)
|
|
234
|
+
self.stem_extraction = StemExtractionService(self, async_mode=True)
|
|
235
|
+
self.wallet = WalletService(self, async_mode=True)
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def session(self) -> aiohttp.ClientSession:
|
|
239
|
+
if self._session is None or self._session.closed:
|
|
240
|
+
timeout = aiohttp.ClientTimeout(total=self.config.timeout)
|
|
241
|
+
connector = aiohttp.TCPConnector(ssl=self.config.verify_ssl)
|
|
242
|
+
self._session = aiohttp.ClientSession(
|
|
243
|
+
timeout=timeout, connector=connector, headers=self._get_headers()
|
|
244
|
+
)
|
|
245
|
+
return self._session
|
|
246
|
+
|
|
247
|
+
async def request(
|
|
248
|
+
self,
|
|
249
|
+
method: str,
|
|
250
|
+
endpoint: str,
|
|
251
|
+
data: Optional[Dict[str, Any]] = None,
|
|
252
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
253
|
+
files: Optional[Dict[str, Any]] = None,
|
|
254
|
+
params: Optional[Dict[str, Any]] = None,
|
|
255
|
+
) -> Dict[str, Any]:
|
|
256
|
+
"""Make async API request."""
|
|
257
|
+
url = urljoin(self.config.base_url, endpoint)
|
|
258
|
+
|
|
259
|
+
async with self.session.request(
|
|
260
|
+
method=method,
|
|
261
|
+
url=url,
|
|
262
|
+
json=json_data,
|
|
263
|
+
data=data,
|
|
264
|
+
params=params,
|
|
265
|
+
) as response:
|
|
266
|
+
return await self._handle_async_response(response)
|
|
267
|
+
|
|
268
|
+
async def _handle_async_response(self, response: aiohttp.ClientResponse) -> Dict[str, Any]:
|
|
269
|
+
if response.status == 204:
|
|
270
|
+
return {}
|
|
271
|
+
try:
|
|
272
|
+
response.raise_for_status()
|
|
273
|
+
return await response.json()
|
|
274
|
+
except aiohttp.ClientResponseError as e:
|
|
275
|
+
if response.status == 401:
|
|
276
|
+
raise AuthenticationError("Invalid API key")
|
|
277
|
+
elif response.status == 402:
|
|
278
|
+
try:
|
|
279
|
+
data = await response.json()
|
|
280
|
+
raise InsufficientBalanceError(
|
|
281
|
+
data.get("message", "Insufficient balance"),
|
|
282
|
+
required_cents=data.get("required_cents"),
|
|
283
|
+
available_cents=data.get("available_cents"),
|
|
284
|
+
)
|
|
285
|
+
except:
|
|
286
|
+
raise InsufficientBalanceError("Insufficient wallet balance")
|
|
287
|
+
elif response.status == 429:
|
|
288
|
+
raise RateLimitError("Rate limit exceeded")
|
|
289
|
+
else:
|
|
290
|
+
raise APIError(f"API error: {e}", status_code=response.status)
|
|
291
|
+
|
|
292
|
+
async def get_user_info(self) -> Dict[str, Any]:
|
|
293
|
+
"""Get current user information."""
|
|
294
|
+
return await self.request("GET", "/api/v1/auth/me")
|
|
295
|
+
|
|
296
|
+
async def close(self):
|
|
297
|
+
"""Close async client session."""
|
|
298
|
+
if self._session and not self._session.closed:
|
|
299
|
+
await self._session.close()
|
|
300
|
+
|
|
301
|
+
async def __aenter__(self):
|
|
302
|
+
return self
|
|
303
|
+
|
|
304
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
305
|
+
await self.close()
|
|
306
|
+
|