audiopod 1.4.0__py3-none-any.whl → 2.1.0__py3-none-any.whl
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/__init__.py +13 -8
- audiopod/client.py +125 -233
- audiopod/exceptions.py +27 -19
- audiopod/resources/__init__.py +23 -0
- audiopod/resources/denoiser.py +116 -0
- audiopod/resources/music.py +166 -0
- audiopod/resources/speaker.py +132 -0
- audiopod/resources/stems.py +267 -0
- audiopod/resources/transcription.py +205 -0
- audiopod/resources/voice.py +139 -0
- audiopod/resources/wallet.py +110 -0
- audiopod-2.1.0.dist-info/METADATA +205 -0
- audiopod-2.1.0.dist-info/RECORD +16 -0
- {audiopod-1.4.0.dist-info → audiopod-2.1.0.dist-info}/WHEEL +1 -1
- audiopod/config.py +0 -17
- audiopod/services/__init__.py +0 -28
- audiopod/services/base.py +0 -69
- audiopod/services/credits.py +0 -42
- audiopod/services/denoiser.py +0 -131
- audiopod/services/music.py +0 -217
- audiopod/services/speaker.py +0 -134
- audiopod/services/stem_extraction.py +0 -168
- audiopod/services/transcription.py +0 -187
- audiopod/services/translation.py +0 -135
- audiopod/services/voice.py +0 -187
- audiopod/services/wallet.py +0 -235
- audiopod-1.4.0.dist-info/METADATA +0 -206
- audiopod-1.4.0.dist-info/RECORD +0 -20
- {audiopod-1.4.0.dist-info → audiopod-2.1.0.dist-info}/licenses/LICENSE +0 -0
- {audiopod-1.4.0.dist-info → audiopod-2.1.0.dist-info}/top_level.txt +0 -0
audiopod/__init__.py
CHANGED
|
@@ -1,29 +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
26
|
__all__ = [
|
|
19
|
-
"
|
|
20
|
-
"AsyncClient",
|
|
27
|
+
"AudioPod",
|
|
21
28
|
"AudioPodError",
|
|
22
|
-
"AuthenticationError",
|
|
29
|
+
"AuthenticationError",
|
|
23
30
|
"APIError",
|
|
24
31
|
"RateLimitError",
|
|
25
|
-
"ValidationError",
|
|
26
32
|
"InsufficientBalanceError",
|
|
27
33
|
"__version__",
|
|
28
34
|
]
|
|
29
|
-
|
audiopod/client.py
CHANGED
|
@@ -1,82 +1,102 @@
|
|
|
1
1
|
"""
|
|
2
2
|
AudioPod API Client
|
|
3
|
+
|
|
4
|
+
Clean, minimal API inspired by OpenAI's SDK design.
|
|
3
5
|
"""
|
|
4
6
|
|
|
5
7
|
import os
|
|
6
|
-
import
|
|
7
|
-
from typing import Optional, Dict, Any
|
|
8
|
+
from typing import Optional, Dict, Any, BinaryIO
|
|
8
9
|
from urllib.parse import urljoin
|
|
9
10
|
|
|
10
11
|
import requests
|
|
11
|
-
import aiohttp
|
|
12
12
|
from requests.adapters import HTTPAdapter
|
|
13
13
|
from urllib3.util.retry import Retry
|
|
14
14
|
|
|
15
|
-
from .
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
TranscriptionService,
|
|
21
|
-
TranslationService,
|
|
22
|
-
SpeakerService,
|
|
23
|
-
DenoiserService,
|
|
24
|
-
CreditService,
|
|
25
|
-
StemExtractionService,
|
|
26
|
-
WalletService,
|
|
15
|
+
from .exceptions import (
|
|
16
|
+
AuthenticationError,
|
|
17
|
+
APIError,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
InsufficientBalanceError,
|
|
27
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
|
+
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
class AudioPod:
|
|
35
|
+
"""
|
|
36
|
+
AudioPod API Client.
|
|
30
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)
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
Example:
|
|
46
|
+
>>> from audiopod import AudioPod
|
|
47
|
+
>>> client = AudioPod(api_key="ap_...")
|
|
48
|
+
>>> result = client.transcription.transcribe(url="https://...")
|
|
49
|
+
"""
|
|
34
50
|
|
|
35
51
|
def __init__(
|
|
36
52
|
self,
|
|
37
53
|
api_key: Optional[str] = None,
|
|
38
54
|
base_url: Optional[str] = None,
|
|
39
|
-
timeout: int =
|
|
55
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
40
56
|
max_retries: int = 3,
|
|
41
|
-
verify_ssl: bool = True,
|
|
42
|
-
debug: bool = False,
|
|
43
57
|
):
|
|
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
58
|
self.api_key = api_key or os.getenv("AUDIOPOD_API_KEY")
|
|
59
|
+
|
|
59
60
|
if not self.api_key:
|
|
60
61
|
raise AuthenticationError(
|
|
61
|
-
"API key required. Pass api_key or set AUDIOPOD_API_KEY environment variable."
|
|
62
|
+
"API key is required. Pass api_key or set AUDIOPOD_API_KEY environment variable."
|
|
62
63
|
)
|
|
63
64
|
|
|
64
65
|
if not self.api_key.startswith("ap_"):
|
|
65
|
-
raise AuthenticationError(
|
|
66
|
+
raise AuthenticationError(
|
|
67
|
+
"Invalid API key format. AudioPod API keys start with 'ap_'"
|
|
68
|
+
)
|
|
66
69
|
|
|
67
|
-
self.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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,
|
|
73
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)
|
|
74
93
|
|
|
75
94
|
def _get_headers(self) -> Dict[str, str]:
|
|
76
95
|
return {
|
|
77
96
|
"Authorization": f"Bearer {self.api_key}",
|
|
97
|
+
"X-API-Key": self.api_key,
|
|
78
98
|
"Content-Type": "application/json",
|
|
79
|
-
"User-Agent": f"audiopod-python/{
|
|
99
|
+
"User-Agent": f"audiopod-python/{VERSION}",
|
|
80
100
|
"Accept": "application/json",
|
|
81
101
|
}
|
|
82
102
|
|
|
@@ -86,221 +106,93 @@ class BaseClient:
|
|
|
86
106
|
if response.status_code == 204:
|
|
87
107
|
return {}
|
|
88
108
|
return response.json()
|
|
89
|
-
except requests.exceptions.HTTPError
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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:
|
|
93
120
|
try:
|
|
94
121
|
data = response.json()
|
|
95
122
|
raise InsufficientBalanceError(
|
|
96
|
-
|
|
123
|
+
message,
|
|
97
124
|
required_cents=data.get("required_cents"),
|
|
98
125
|
available_cents=data.get("available_cents"),
|
|
99
126
|
)
|
|
100
127
|
except (ValueError, KeyError):
|
|
101
|
-
raise InsufficientBalanceError(
|
|
102
|
-
elif
|
|
103
|
-
raise RateLimitError(
|
|
128
|
+
raise InsufficientBalanceError(message)
|
|
129
|
+
elif status == 429:
|
|
130
|
+
raise RateLimitError(message)
|
|
104
131
|
else:
|
|
105
|
-
|
|
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.
|
|
132
|
+
raise APIError(message, status_code=status)
|
|
116
133
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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,
|
|
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
|
|
144
140
|
)
|
|
145
|
-
|
|
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)
|
|
141
|
+
return self._handle_response(response)
|
|
159
142
|
|
|
160
|
-
def
|
|
143
|
+
def post(
|
|
161
144
|
self,
|
|
162
|
-
method: str,
|
|
163
145
|
endpoint: str,
|
|
164
146
|
data: Optional[Dict[str, Any]] = None,
|
|
165
147
|
json_data: Optional[Dict[str, Any]] = None,
|
|
166
|
-
files: Optional[Dict[str, Any]] = None,
|
|
167
|
-
params: Optional[Dict[str, Any]] = None,
|
|
168
148
|
) -> Dict[str, Any]:
|
|
169
|
-
"""Make
|
|
170
|
-
url = urljoin(self.
|
|
149
|
+
"""Make a POST request."""
|
|
150
|
+
url = urljoin(self.base_url, endpoint)
|
|
171
151
|
headers = self._get_headers()
|
|
172
|
-
|
|
173
|
-
|
|
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,
|
|
152
|
+
response = self._session.post(
|
|
153
|
+
url, headers=headers, data=data, json=json_data, timeout=self.timeout
|
|
186
154
|
)
|
|
187
155
|
return self._handle_response(response)
|
|
188
156
|
|
|
189
|
-
def
|
|
190
|
-
"""
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
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)
|
|
246
163
|
|
|
247
|
-
|
|
164
|
+
def upload(
|
|
248
165
|
self,
|
|
249
|
-
method: str,
|
|
250
166
|
endpoint: str,
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
params: Optional[Dict[str, Any]] = None,
|
|
167
|
+
file_path: str,
|
|
168
|
+
field_name: str = "file",
|
|
169
|
+
additional_fields: Optional[Dict[str, Any]] = None,
|
|
255
170
|
) -> Dict[str, Any]:
|
|
256
|
-
"""
|
|
257
|
-
url = urljoin(self.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
data=
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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)
|
|
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
|
+
)
|
|
291
187
|
|
|
292
|
-
|
|
293
|
-
"""Get current user information."""
|
|
294
|
-
return await self.request("GET", "/api/v1/auth/me")
|
|
188
|
+
return self._handle_response(response)
|
|
295
189
|
|
|
296
|
-
|
|
297
|
-
"""Close
|
|
298
|
-
|
|
299
|
-
await self._session.close()
|
|
190
|
+
def close(self):
|
|
191
|
+
"""Close the client session."""
|
|
192
|
+
self._session.close()
|
|
300
193
|
|
|
301
|
-
|
|
194
|
+
def __enter__(self):
|
|
302
195
|
return self
|
|
303
196
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
197
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
198
|
+
self.close()
|
audiopod/exceptions.py
CHANGED
|
@@ -2,40 +2,48 @@
|
|
|
2
2
|
AudioPod SDK Exceptions
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class AudioPodError(Exception):
|
|
7
|
-
"""Base exception for AudioPod SDK"""
|
|
8
|
-
|
|
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)
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
class AuthenticationError(AudioPodError):
|
|
12
|
-
"""Raised when authentication fails"""
|
|
13
|
-
|
|
17
|
+
"""Raised when authentication fails."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, message: str = "Authentication failed"):
|
|
20
|
+
super().__init__(message)
|
|
14
21
|
|
|
15
22
|
|
|
16
23
|
class APIError(AudioPodError):
|
|
17
|
-
"""Raised when API
|
|
18
|
-
|
|
19
|
-
def __init__(self, message: str, status_code: int = None):
|
|
20
|
-
super().__init__(message)
|
|
24
|
+
"""Raised when an API request fails."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, message: str = "API request failed", status_code: Optional[int] = None):
|
|
21
27
|
self.status_code = status_code
|
|
28
|
+
super().__init__(message)
|
|
22
29
|
|
|
23
30
|
|
|
24
31
|
class RateLimitError(AudioPodError):
|
|
25
|
-
"""Raised when rate limit is exceeded"""
|
|
26
|
-
pass
|
|
27
|
-
|
|
32
|
+
"""Raised when rate limit is exceeded."""
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
pass
|
|
34
|
+
def __init__(self, message: str = "Rate limit exceeded"):
|
|
35
|
+
super().__init__(message)
|
|
32
36
|
|
|
33
37
|
|
|
34
38
|
class InsufficientBalanceError(AudioPodError):
|
|
35
|
-
"""Raised when wallet balance is insufficient"""
|
|
36
|
-
|
|
37
|
-
def __init__(
|
|
38
|
-
|
|
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
|
+
):
|
|
39
47
|
self.required_cents = required_cents
|
|
40
48
|
self.available_cents = available_cents
|
|
41
|
-
|
|
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
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Denoiser Service - Audio Noise Reduction
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import Optional, List, Dict, Any, TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..client import AudioPod
|
|
10
|
+
|
|
11
|
+
POLL_INTERVAL = 3 # seconds
|
|
12
|
+
DEFAULT_TIMEOUT = 600 # 10 minutes
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Denoiser:
|
|
16
|
+
"""Audio noise reduction service."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, client: "AudioPod"):
|
|
19
|
+
self._client = client
|
|
20
|
+
|
|
21
|
+
def create(
|
|
22
|
+
self,
|
|
23
|
+
*,
|
|
24
|
+
file: Optional[str] = None,
|
|
25
|
+
url: Optional[str] = None,
|
|
26
|
+
) -> Dict[str, Any]:
|
|
27
|
+
"""
|
|
28
|
+
Create a denoise job.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
file: Path to audio file
|
|
32
|
+
url: URL of audio/video
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Denoise job object
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> job = client.denoiser.create(file="./noisy-audio.mp3")
|
|
39
|
+
"""
|
|
40
|
+
if file:
|
|
41
|
+
return self._client.upload(
|
|
42
|
+
"/api/v1/denoiser/denoise",
|
|
43
|
+
file,
|
|
44
|
+
field_name="file",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if url:
|
|
48
|
+
return self._client.post(
|
|
49
|
+
"/api/v1/denoiser/denoise",
|
|
50
|
+
json_data={"url": url},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
raise ValueError("Either file or url must be provided")
|
|
54
|
+
|
|
55
|
+
def get(self, job_id: int) -> Dict[str, Any]:
|
|
56
|
+
"""Get a denoise job by ID."""
|
|
57
|
+
return self._client.get(f"/api/v1/denoiser/jobs/{job_id}")
|
|
58
|
+
|
|
59
|
+
def list(
|
|
60
|
+
self,
|
|
61
|
+
*,
|
|
62
|
+
skip: int = 0,
|
|
63
|
+
limit: int = 50,
|
|
64
|
+
status: Optional[str] = None,
|
|
65
|
+
) -> List[Dict[str, Any]]:
|
|
66
|
+
"""List denoise jobs."""
|
|
67
|
+
return self._client.get(
|
|
68
|
+
"/api/v1/denoiser/jobs",
|
|
69
|
+
params={"skip": skip, "limit": limit, "status": status},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def delete(self, job_id: int) -> None:
|
|
73
|
+
"""Delete a denoise job."""
|
|
74
|
+
self._client.delete(f"/api/v1/denoiser/jobs/{job_id}")
|
|
75
|
+
|
|
76
|
+
def wait_for_completion(
|
|
77
|
+
self, job_id: int, timeout: int = DEFAULT_TIMEOUT
|
|
78
|
+
) -> Dict[str, Any]:
|
|
79
|
+
"""Wait for denoising to complete."""
|
|
80
|
+
start_time = time.time()
|
|
81
|
+
|
|
82
|
+
while time.time() - start_time < timeout:
|
|
83
|
+
job = self.get(job_id)
|
|
84
|
+
status = job.get("status", "")
|
|
85
|
+
|
|
86
|
+
if status == "COMPLETED":
|
|
87
|
+
return job
|
|
88
|
+
if status == "FAILED":
|
|
89
|
+
raise RuntimeError(
|
|
90
|
+
f"Denoising failed: {job.get('error_message', 'Unknown error')}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
time.sleep(POLL_INTERVAL)
|
|
94
|
+
|
|
95
|
+
raise TimeoutError(f"Denoising timed out after {timeout}s")
|
|
96
|
+
|
|
97
|
+
def denoise(
|
|
98
|
+
self,
|
|
99
|
+
*,
|
|
100
|
+
file: Optional[str] = None,
|
|
101
|
+
url: Optional[str] = None,
|
|
102
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
103
|
+
) -> Dict[str, Any]:
|
|
104
|
+
"""
|
|
105
|
+
Denoise audio and wait for completion.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
>>> result = client.denoiser.denoise(file="./noisy-audio.mp3")
|
|
109
|
+
>>> print(result["output_url"])
|
|
110
|
+
"""
|
|
111
|
+
job = self.create(file=file, url=url)
|
|
112
|
+
return self.wait_for_completion(job["id"], timeout=timeout)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|