audiopod 1.5.0__tar.gz → 2.1.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-2.1.0/PKG-INFO +205 -0
- audiopod-2.1.0/README.md +165 -0
- {audiopod-1.5.0 → audiopod-2.1.0}/audiopod/__init__.py +13 -12
- audiopod-2.1.0/audiopod/client.py +198 -0
- audiopod-2.1.0/audiopod/exceptions.py +49 -0
- audiopod-2.1.0/audiopod/resources/__init__.py +23 -0
- audiopod-2.1.0/audiopod/resources/denoiser.py +116 -0
- audiopod-2.1.0/audiopod/resources/music.py +166 -0
- audiopod-2.1.0/audiopod/resources/speaker.py +132 -0
- audiopod-2.1.0/audiopod/resources/stems.py +267 -0
- audiopod-2.1.0/audiopod/resources/transcription.py +205 -0
- audiopod-2.1.0/audiopod/resources/voice.py +139 -0
- audiopod-2.1.0/audiopod/resources/wallet.py +110 -0
- audiopod-2.1.0/audiopod.egg-info/PKG-INFO +205 -0
- audiopod-2.1.0/audiopod.egg-info/SOURCES.txt +19 -0
- audiopod-2.1.0/audiopod.egg-info/requires.txt +10 -0
- {audiopod-1.5.0 → audiopod-2.1.0}/pyproject.toml +37 -14
- audiopod-1.5.0/PKG-INFO +0 -206
- audiopod-1.5.0/README.md +0 -173
- audiopod-1.5.0/audiopod/client.py +0 -312
- audiopod-1.5.0/audiopod/config.py +0 -17
- audiopod-1.5.0/audiopod/exceptions.py +0 -41
- audiopod-1.5.0/audiopod/services/__init__.py +0 -30
- audiopod-1.5.0/audiopod/services/base.py +0 -69
- audiopod-1.5.0/audiopod/services/credits.py +0 -42
- audiopod-1.5.0/audiopod/services/denoiser.py +0 -136
- audiopod-1.5.0/audiopod/services/music.py +0 -217
- audiopod-1.5.0/audiopod/services/speaker.py +0 -134
- audiopod-1.5.0/audiopod/services/stem_extraction.py +0 -287
- audiopod-1.5.0/audiopod/services/transcription.py +0 -210
- audiopod-1.5.0/audiopod/services/translation.py +0 -135
- audiopod-1.5.0/audiopod/services/video.py +0 -329
- audiopod-1.5.0/audiopod/services/voice.py +0 -187
- audiopod-1.5.0/audiopod/services/wallet.py +0 -235
- audiopod-1.5.0/audiopod.egg-info/PKG-INFO +0 -206
- audiopod-1.5.0/audiopod.egg-info/SOURCES.txt +0 -24
- audiopod-1.5.0/audiopod.egg-info/requires.txt +0 -8
- {audiopod-1.5.0 → audiopod-2.1.0}/LICENSE +0 -0
- {audiopod-1.5.0 → audiopod-2.1.0}/audiopod.egg-info/dependency_links.txt +0 -0
- {audiopod-1.5.0 → audiopod-2.1.0}/audiopod.egg-info/top_level.txt +0 -0
- {audiopod-1.5.0 → audiopod-2.1.0}/setup.cfg +0 -0
audiopod-2.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: audiopod
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: Official 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-sdk-python
|
|
10
|
+
Project-URL: Issues, https://github.com/AudiopodAI/audiopod-sdk-python/issues
|
|
11
|
+
Keywords: audiopod,audio,ai,speech-to-text,transcription,text-to-speech,tts,voice-cloning,music-generation,stem-separation,denoiser,speaker-diarization
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
23
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
24
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: requests>=2.28.0
|
|
31
|
+
Requires-Dist: urllib3>=1.26.0
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
37
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: types-requests>=2.28.0; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# AudioPod Python SDK
|
|
42
|
+
|
|
43
|
+
Official Python SDK for [AudioPod AI](https://audiopod.ai) - Professional Audio Processing powered by AI.
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/audiopod/)
|
|
46
|
+
[](https://www.python.org/downloads/)
|
|
47
|
+
[](https://opensource.org/licenses/MIT)
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install audiopod
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from audiopod import AudioPod
|
|
59
|
+
|
|
60
|
+
# Initialize client
|
|
61
|
+
client = AudioPod(api_key="ap_your_api_key")
|
|
62
|
+
|
|
63
|
+
# Separate audio into 6 stems
|
|
64
|
+
result = client.stems.separate(
|
|
65
|
+
url="https://youtube.com/watch?v=VIDEO_ID",
|
|
66
|
+
mode="six"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Download stems
|
|
70
|
+
for stem, url in result["download_urls"].items():
|
|
71
|
+
print(f"{stem}: {url}")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Stem Separation
|
|
75
|
+
|
|
76
|
+
Extract individual audio components from mixed recordings.
|
|
77
|
+
|
|
78
|
+
### Available Modes
|
|
79
|
+
|
|
80
|
+
| Mode | Stems | Output |
|
|
81
|
+
|------|-------|--------|
|
|
82
|
+
| `single` | 1 | Specified stem only (vocals, drums, bass, guitar, piano, other) |
|
|
83
|
+
| `two` | 2 | Vocals + Instrumental |
|
|
84
|
+
| `four` | 4 | Vocals, Drums, Bass, Other |
|
|
85
|
+
| `six` | 6 | Vocals, Drums, Bass, Guitar, Piano, Other |
|
|
86
|
+
| `producer` | 8 | + Kick, Snare, Hihat |
|
|
87
|
+
| `studio` | 12 | Full production toolkit |
|
|
88
|
+
| `mastering` | 16 | Maximum detail |
|
|
89
|
+
|
|
90
|
+
### Examples
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from audiopod import AudioPod
|
|
94
|
+
|
|
95
|
+
client = AudioPod(api_key="ap_your_api_key")
|
|
96
|
+
|
|
97
|
+
# Six-stem separation from YouTube
|
|
98
|
+
job = client.stems.extract(
|
|
99
|
+
url="https://youtube.com/watch?v=VIDEO_ID",
|
|
100
|
+
mode="six"
|
|
101
|
+
)
|
|
102
|
+
print(f"Job ID: {job['id']}")
|
|
103
|
+
|
|
104
|
+
# Wait for completion
|
|
105
|
+
result = client.stems.wait_for_completion(job["id"])
|
|
106
|
+
print(result["download_urls"])
|
|
107
|
+
|
|
108
|
+
# Or use the convenience method (extract + wait)
|
|
109
|
+
result = client.stems.separate(
|
|
110
|
+
url="https://youtube.com/watch?v=VIDEO_ID",
|
|
111
|
+
mode="six"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# From local file
|
|
115
|
+
result = client.stems.separate(
|
|
116
|
+
file="./song.mp3",
|
|
117
|
+
mode="four"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Extract only vocals
|
|
121
|
+
result = client.stems.separate(
|
|
122
|
+
url="https://youtube.com/watch?v=VIDEO_ID",
|
|
123
|
+
mode="single",
|
|
124
|
+
stem="vocals"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Get available modes
|
|
128
|
+
modes = client.stems.modes()
|
|
129
|
+
for m in modes["modes"]:
|
|
130
|
+
print(f"{m['mode']}: {m['description']}")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## API Wallet
|
|
134
|
+
|
|
135
|
+
Check balance and manage your wallet.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
# Check balance
|
|
139
|
+
balance = client.wallet.balance()
|
|
140
|
+
print(f"Balance: {balance['balance_usd']}")
|
|
141
|
+
|
|
142
|
+
# Estimate cost
|
|
143
|
+
estimate = client.wallet.estimate("stem_extraction", duration_seconds=180)
|
|
144
|
+
print(f"Estimated cost: {estimate['cost_usd']}")
|
|
145
|
+
|
|
146
|
+
# Get usage history
|
|
147
|
+
usage = client.wallet.usage()
|
|
148
|
+
for log in usage["logs"]:
|
|
149
|
+
print(f"{log['service_type']}: {log['amount_usd']}")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Other Services
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# Transcription
|
|
156
|
+
job = client.transcription.create(url="https://...")
|
|
157
|
+
result = client.transcription.wait_for_completion(job["id"])
|
|
158
|
+
|
|
159
|
+
# Voice cloning
|
|
160
|
+
voice = client.voice.clone(file="./sample.wav", name="My Voice")
|
|
161
|
+
|
|
162
|
+
# Music generation
|
|
163
|
+
song = client.music.generate(prompt="upbeat electronic dance music")
|
|
164
|
+
|
|
165
|
+
# Noise reduction
|
|
166
|
+
clean = client.denoiser.denoise(file="./noisy.wav")
|
|
167
|
+
|
|
168
|
+
# Speaker diarization
|
|
169
|
+
speakers = client.speaker.diarize(url="https://...")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Error Handling
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from audiopod import AudioPod, InsufficientBalanceError, AuthenticationError
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
client = AudioPod(api_key="ap_...")
|
|
179
|
+
result = client.stems.separate(url="...", mode="six")
|
|
180
|
+
except AuthenticationError:
|
|
181
|
+
print("Invalid API key")
|
|
182
|
+
except InsufficientBalanceError as e:
|
|
183
|
+
print(f"Need more credits. Required: {e.required_cents} cents")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Environment Variables
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
export AUDIOPOD_API_KEY="ap_your_api_key"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
# Client reads from env automatically
|
|
194
|
+
client = AudioPod()
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Documentation
|
|
198
|
+
|
|
199
|
+
- [API Documentation](https://docs.audiopod.ai)
|
|
200
|
+
- [API Reference](https://docs.audiopod.ai/api-reference/stem-splitter)
|
|
201
|
+
- [Get API Key](https://www.audiopod.ai/dashboard/account/api-keys)
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
audiopod-2.1.0/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# AudioPod Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for [AudioPod AI](https://audiopod.ai) - Professional Audio Processing powered by AI.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/audiopod/)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install audiopod
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from audiopod import AudioPod
|
|
19
|
+
|
|
20
|
+
# Initialize client
|
|
21
|
+
client = AudioPod(api_key="ap_your_api_key")
|
|
22
|
+
|
|
23
|
+
# Separate audio into 6 stems
|
|
24
|
+
result = client.stems.separate(
|
|
25
|
+
url="https://youtube.com/watch?v=VIDEO_ID",
|
|
26
|
+
mode="six"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Download stems
|
|
30
|
+
for stem, url in result["download_urls"].items():
|
|
31
|
+
print(f"{stem}: {url}")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Stem Separation
|
|
35
|
+
|
|
36
|
+
Extract individual audio components from mixed recordings.
|
|
37
|
+
|
|
38
|
+
### Available Modes
|
|
39
|
+
|
|
40
|
+
| Mode | Stems | Output |
|
|
41
|
+
|------|-------|--------|
|
|
42
|
+
| `single` | 1 | Specified stem only (vocals, drums, bass, guitar, piano, other) |
|
|
43
|
+
| `two` | 2 | Vocals + Instrumental |
|
|
44
|
+
| `four` | 4 | Vocals, Drums, Bass, Other |
|
|
45
|
+
| `six` | 6 | Vocals, Drums, Bass, Guitar, Piano, Other |
|
|
46
|
+
| `producer` | 8 | + Kick, Snare, Hihat |
|
|
47
|
+
| `studio` | 12 | Full production toolkit |
|
|
48
|
+
| `mastering` | 16 | Maximum detail |
|
|
49
|
+
|
|
50
|
+
### Examples
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from audiopod import AudioPod
|
|
54
|
+
|
|
55
|
+
client = AudioPod(api_key="ap_your_api_key")
|
|
56
|
+
|
|
57
|
+
# Six-stem separation from YouTube
|
|
58
|
+
job = client.stems.extract(
|
|
59
|
+
url="https://youtube.com/watch?v=VIDEO_ID",
|
|
60
|
+
mode="six"
|
|
61
|
+
)
|
|
62
|
+
print(f"Job ID: {job['id']}")
|
|
63
|
+
|
|
64
|
+
# Wait for completion
|
|
65
|
+
result = client.stems.wait_for_completion(job["id"])
|
|
66
|
+
print(result["download_urls"])
|
|
67
|
+
|
|
68
|
+
# Or use the convenience method (extract + wait)
|
|
69
|
+
result = client.stems.separate(
|
|
70
|
+
url="https://youtube.com/watch?v=VIDEO_ID",
|
|
71
|
+
mode="six"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# From local file
|
|
75
|
+
result = client.stems.separate(
|
|
76
|
+
file="./song.mp3",
|
|
77
|
+
mode="four"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Extract only vocals
|
|
81
|
+
result = client.stems.separate(
|
|
82
|
+
url="https://youtube.com/watch?v=VIDEO_ID",
|
|
83
|
+
mode="single",
|
|
84
|
+
stem="vocals"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Get available modes
|
|
88
|
+
modes = client.stems.modes()
|
|
89
|
+
for m in modes["modes"]:
|
|
90
|
+
print(f"{m['mode']}: {m['description']}")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API Wallet
|
|
94
|
+
|
|
95
|
+
Check balance and manage your wallet.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# Check balance
|
|
99
|
+
balance = client.wallet.balance()
|
|
100
|
+
print(f"Balance: {balance['balance_usd']}")
|
|
101
|
+
|
|
102
|
+
# Estimate cost
|
|
103
|
+
estimate = client.wallet.estimate("stem_extraction", duration_seconds=180)
|
|
104
|
+
print(f"Estimated cost: {estimate['cost_usd']}")
|
|
105
|
+
|
|
106
|
+
# Get usage history
|
|
107
|
+
usage = client.wallet.usage()
|
|
108
|
+
for log in usage["logs"]:
|
|
109
|
+
print(f"{log['service_type']}: {log['amount_usd']}")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Other Services
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
# Transcription
|
|
116
|
+
job = client.transcription.create(url="https://...")
|
|
117
|
+
result = client.transcription.wait_for_completion(job["id"])
|
|
118
|
+
|
|
119
|
+
# Voice cloning
|
|
120
|
+
voice = client.voice.clone(file="./sample.wav", name="My Voice")
|
|
121
|
+
|
|
122
|
+
# Music generation
|
|
123
|
+
song = client.music.generate(prompt="upbeat electronic dance music")
|
|
124
|
+
|
|
125
|
+
# Noise reduction
|
|
126
|
+
clean = client.denoiser.denoise(file="./noisy.wav")
|
|
127
|
+
|
|
128
|
+
# Speaker diarization
|
|
129
|
+
speakers = client.speaker.diarize(url="https://...")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Error Handling
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from audiopod import AudioPod, InsufficientBalanceError, AuthenticationError
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
client = AudioPod(api_key="ap_...")
|
|
139
|
+
result = client.stems.separate(url="...", mode="six")
|
|
140
|
+
except AuthenticationError:
|
|
141
|
+
print("Invalid API key")
|
|
142
|
+
except InsufficientBalanceError as e:
|
|
143
|
+
print(f"Need more credits. Required: {e.required_cents} cents")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Environment Variables
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
export AUDIOPOD_API_KEY="ap_your_api_key"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# Client reads from env automatically
|
|
154
|
+
client = AudioPod()
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Documentation
|
|
158
|
+
|
|
159
|
+
- [API Documentation](https://docs.audiopod.ai)
|
|
160
|
+
- [API Reference](https://docs.audiopod.ai/api-reference/stem-splitter)
|
|
161
|
+
- [Get API Key](https://www.audiopod.ai/dashboard/account/api-keys)
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
"""
|
|
2
2
|
AudioPod SDK for Python
|
|
3
3
|
Professional Audio Processing powered by AI
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
from audiopod import AudioPod
|
|
7
|
+
|
|
8
|
+
client = AudioPod(api_key="ap_...")
|
|
9
|
+
|
|
10
|
+
# Transcribe audio
|
|
11
|
+
job = client.transcription.create(url="https://...")
|
|
12
|
+
result = client.transcription.wait_for_completion(job.id)
|
|
4
13
|
"""
|
|
5
14
|
|
|
6
|
-
__version__ = "1.
|
|
15
|
+
__version__ = "2.1.0"
|
|
7
16
|
|
|
8
|
-
from .client import
|
|
17
|
+
from .client import AudioPod
|
|
9
18
|
from .exceptions import (
|
|
10
19
|
AudioPodError,
|
|
11
20
|
AuthenticationError,
|
|
12
21
|
APIError,
|
|
13
22
|
RateLimitError,
|
|
14
|
-
ValidationError,
|
|
15
23
|
InsufficientBalanceError,
|
|
16
24
|
)
|
|
17
25
|
|
|
18
|
-
# Alias for consistency with documentation and Node.js SDK
|
|
19
|
-
AudioPod = Client
|
|
20
|
-
|
|
21
26
|
__all__ = [
|
|
22
|
-
"
|
|
23
|
-
"AsyncClient",
|
|
24
|
-
"AudioPod", # Alias for Client
|
|
27
|
+
"AudioPod",
|
|
25
28
|
"AudioPodError",
|
|
26
|
-
"AuthenticationError",
|
|
29
|
+
"AuthenticationError",
|
|
27
30
|
"APIError",
|
|
28
31
|
"RateLimitError",
|
|
29
|
-
"ValidationError",
|
|
30
32
|
"InsufficientBalanceError",
|
|
31
33
|
"__version__",
|
|
32
34
|
]
|
|
33
|
-
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AudioPod API Client
|
|
3
|
+
|
|
4
|
+
Clean, minimal API inspired by OpenAI's SDK design.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional, Dict, Any, BinaryIO
|
|
9
|
+
from urllib.parse import urljoin
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
from requests.adapters import HTTPAdapter
|
|
13
|
+
from urllib3.util.retry import Retry
|
|
14
|
+
|
|
15
|
+
from .exceptions import (
|
|
16
|
+
AuthenticationError,
|
|
17
|
+
APIError,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
InsufficientBalanceError,
|
|
20
|
+
)
|
|
21
|
+
from .resources.transcription import Transcription
|
|
22
|
+
from .resources.voice import Voice
|
|
23
|
+
from .resources.music import Music
|
|
24
|
+
from .resources.stems import StemExtraction
|
|
25
|
+
from .resources.denoiser import Denoiser
|
|
26
|
+
from .resources.speaker import Speaker
|
|
27
|
+
from .resources.wallet import Wallet
|
|
28
|
+
|
|
29
|
+
VERSION = "2.1.0"
|
|
30
|
+
DEFAULT_BASE_URL = "https://api.audiopod.ai"
|
|
31
|
+
DEFAULT_TIMEOUT = 60
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AudioPod:
|
|
35
|
+
"""
|
|
36
|
+
AudioPod API Client.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
api_key: Your AudioPod API key (starts with 'ap_').
|
|
40
|
+
If not provided, reads from AUDIOPOD_API_KEY env var.
|
|
41
|
+
base_url: Base URL for the API (default: https://api.audiopod.ai)
|
|
42
|
+
timeout: Request timeout in seconds (default: 60)
|
|
43
|
+
max_retries: Maximum retries for failed requests (default: 3)
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
>>> from audiopod import AudioPod
|
|
47
|
+
>>> client = AudioPod(api_key="ap_...")
|
|
48
|
+
>>> result = client.transcription.transcribe(url="https://...")
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
api_key: Optional[str] = None,
|
|
54
|
+
base_url: Optional[str] = None,
|
|
55
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
56
|
+
max_retries: int = 3,
|
|
57
|
+
):
|
|
58
|
+
self.api_key = api_key or os.getenv("AUDIOPOD_API_KEY")
|
|
59
|
+
|
|
60
|
+
if not self.api_key:
|
|
61
|
+
raise AuthenticationError(
|
|
62
|
+
"API key is required. Pass api_key or set AUDIOPOD_API_KEY environment variable."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if not self.api_key.startswith("ap_"):
|
|
66
|
+
raise AuthenticationError(
|
|
67
|
+
"Invalid API key format. AudioPod API keys start with 'ap_'"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
self.base_url = base_url or DEFAULT_BASE_URL
|
|
71
|
+
self.timeout = timeout
|
|
72
|
+
|
|
73
|
+
# Configure session with retries
|
|
74
|
+
self._session = requests.Session()
|
|
75
|
+
retry_strategy = Retry(
|
|
76
|
+
total=max_retries,
|
|
77
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
78
|
+
allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "DELETE"],
|
|
79
|
+
backoff_factor=1,
|
|
80
|
+
)
|
|
81
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
82
|
+
self._session.mount("http://", adapter)
|
|
83
|
+
self._session.mount("https://", adapter)
|
|
84
|
+
|
|
85
|
+
# Initialize services
|
|
86
|
+
self.transcription = Transcription(self)
|
|
87
|
+
self.voice = Voice(self)
|
|
88
|
+
self.music = Music(self)
|
|
89
|
+
self.stems = StemExtraction(self)
|
|
90
|
+
self.denoiser = Denoiser(self)
|
|
91
|
+
self.speaker = Speaker(self)
|
|
92
|
+
self.wallet = Wallet(self)
|
|
93
|
+
|
|
94
|
+
def _get_headers(self) -> Dict[str, str]:
|
|
95
|
+
return {
|
|
96
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
97
|
+
"X-API-Key": self.api_key,
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
"User-Agent": f"audiopod-python/{VERSION}",
|
|
100
|
+
"Accept": "application/json",
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
|
|
104
|
+
try:
|
|
105
|
+
response.raise_for_status()
|
|
106
|
+
if response.status_code == 204:
|
|
107
|
+
return {}
|
|
108
|
+
return response.json()
|
|
109
|
+
except requests.exceptions.HTTPError:
|
|
110
|
+
status = response.status_code
|
|
111
|
+
try:
|
|
112
|
+
data = response.json()
|
|
113
|
+
message = data.get("detail") or data.get("message") or str(data)
|
|
114
|
+
except Exception:
|
|
115
|
+
message = response.text or f"HTTP {status}"
|
|
116
|
+
|
|
117
|
+
if status == 401:
|
|
118
|
+
raise AuthenticationError(message)
|
|
119
|
+
elif status == 402:
|
|
120
|
+
try:
|
|
121
|
+
data = response.json()
|
|
122
|
+
raise InsufficientBalanceError(
|
|
123
|
+
message,
|
|
124
|
+
required_cents=data.get("required_cents"),
|
|
125
|
+
available_cents=data.get("available_cents"),
|
|
126
|
+
)
|
|
127
|
+
except (ValueError, KeyError):
|
|
128
|
+
raise InsufficientBalanceError(message)
|
|
129
|
+
elif status == 429:
|
|
130
|
+
raise RateLimitError(message)
|
|
131
|
+
else:
|
|
132
|
+
raise APIError(message, status_code=status)
|
|
133
|
+
|
|
134
|
+
def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
135
|
+
"""Make a GET request."""
|
|
136
|
+
url = urljoin(self.base_url, endpoint)
|
|
137
|
+
headers = self._get_headers()
|
|
138
|
+
response = self._session.get(
|
|
139
|
+
url, headers=headers, params=params, timeout=self.timeout
|
|
140
|
+
)
|
|
141
|
+
return self._handle_response(response)
|
|
142
|
+
|
|
143
|
+
def post(
|
|
144
|
+
self,
|
|
145
|
+
endpoint: str,
|
|
146
|
+
data: Optional[Dict[str, Any]] = None,
|
|
147
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
148
|
+
) -> Dict[str, Any]:
|
|
149
|
+
"""Make a POST request."""
|
|
150
|
+
url = urljoin(self.base_url, endpoint)
|
|
151
|
+
headers = self._get_headers()
|
|
152
|
+
response = self._session.post(
|
|
153
|
+
url, headers=headers, data=data, json=json_data, timeout=self.timeout
|
|
154
|
+
)
|
|
155
|
+
return self._handle_response(response)
|
|
156
|
+
|
|
157
|
+
def delete(self, endpoint: str) -> Dict[str, Any]:
|
|
158
|
+
"""Make a DELETE request."""
|
|
159
|
+
url = urljoin(self.base_url, endpoint)
|
|
160
|
+
headers = self._get_headers()
|
|
161
|
+
response = self._session.delete(url, headers=headers, timeout=self.timeout)
|
|
162
|
+
return self._handle_response(response)
|
|
163
|
+
|
|
164
|
+
def upload(
|
|
165
|
+
self,
|
|
166
|
+
endpoint: str,
|
|
167
|
+
file_path: str,
|
|
168
|
+
field_name: str = "file",
|
|
169
|
+
additional_fields: Optional[Dict[str, Any]] = None,
|
|
170
|
+
) -> Dict[str, Any]:
|
|
171
|
+
"""Upload a file."""
|
|
172
|
+
url = urljoin(self.base_url, endpoint)
|
|
173
|
+
headers = self._get_headers()
|
|
174
|
+
headers.pop("Content-Type", None) # Let requests set multipart boundary
|
|
175
|
+
|
|
176
|
+
with open(file_path, "rb") as f:
|
|
177
|
+
files = {field_name: f}
|
|
178
|
+
data = {}
|
|
179
|
+
if additional_fields:
|
|
180
|
+
for key, value in additional_fields.items():
|
|
181
|
+
if value is not None:
|
|
182
|
+
data[key] = str(value) if not isinstance(value, str) else value
|
|
183
|
+
|
|
184
|
+
response = self._session.post(
|
|
185
|
+
url, headers=headers, files=files, data=data, timeout=self.timeout
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return self._handle_response(response)
|
|
189
|
+
|
|
190
|
+
def close(self):
|
|
191
|
+
"""Close the client session."""
|
|
192
|
+
self._session.close()
|
|
193
|
+
|
|
194
|
+
def __enter__(self):
|
|
195
|
+
return self
|
|
196
|
+
|
|
197
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
198
|
+
self.close()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AudioPod SDK Exceptions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AudioPodError(Exception):
|
|
9
|
+
"""Base exception for AudioPod SDK."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message: str = "An error occurred"):
|
|
12
|
+
self.message = message
|
|
13
|
+
super().__init__(self.message)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AuthenticationError(AudioPodError):
|
|
17
|
+
"""Raised when authentication fails."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, message: str = "Authentication failed"):
|
|
20
|
+
super().__init__(message)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class APIError(AudioPodError):
|
|
24
|
+
"""Raised when an API request fails."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, message: str = "API request failed", status_code: Optional[int] = None):
|
|
27
|
+
self.status_code = status_code
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RateLimitError(AudioPodError):
|
|
32
|
+
"""Raised when rate limit is exceeded."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, message: str = "Rate limit exceeded"):
|
|
35
|
+
super().__init__(message)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class InsufficientBalanceError(AudioPodError):
|
|
39
|
+
"""Raised when wallet balance is insufficient."""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
message: str = "Insufficient wallet balance",
|
|
44
|
+
required_cents: Optional[int] = None,
|
|
45
|
+
available_cents: Optional[int] = None,
|
|
46
|
+
):
|
|
47
|
+
self.required_cents = required_cents
|
|
48
|
+
self.available_cents = available_cents
|
|
49
|
+
super().__init__(message)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""AudioPod SDK Resources"""
|
|
2
|
+
|
|
3
|
+
from .transcription import Transcription
|
|
4
|
+
from .voice import Voice
|
|
5
|
+
from .music import Music
|
|
6
|
+
from .stems import StemExtraction
|
|
7
|
+
from .denoiser import Denoiser
|
|
8
|
+
from .speaker import Speaker
|
|
9
|
+
from .wallet import Wallet
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"Transcription",
|
|
13
|
+
"Voice",
|
|
14
|
+
"Music",
|
|
15
|
+
"StemExtraction",
|
|
16
|
+
"Denoiser",
|
|
17
|
+
"Speaker",
|
|
18
|
+
"Wallet",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|