audiopod 2.1.0__py3-none-any.whl → 2.2.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 +12 -13
- audiopod/client.py +238 -124
- audiopod/config.py +17 -0
- audiopod/exceptions.py +19 -27
- audiopod/services/__init__.py +30 -0
- audiopod/services/base.py +69 -0
- audiopod/services/credits.py +42 -0
- audiopod/services/denoiser.py +136 -0
- audiopod/services/music.py +217 -0
- audiopod/services/speaker.py +134 -0
- audiopod/services/stem_extraction.py +287 -0
- audiopod/services/transcription.py +210 -0
- audiopod/services/translation.py +135 -0
- audiopod/services/video.py +329 -0
- audiopod/services/voice.py +187 -0
- audiopod/services/wallet.py +235 -0
- audiopod-2.2.0.dist-info/METADATA +206 -0
- audiopod-2.2.0.dist-info/RECORD +21 -0
- {audiopod-2.1.0.dist-info → audiopod-2.2.0.dist-info}/WHEEL +1 -1
- audiopod/resources/__init__.py +0 -23
- audiopod/resources/denoiser.py +0 -116
- audiopod/resources/music.py +0 -166
- audiopod/resources/speaker.py +0 -132
- audiopod/resources/stems.py +0 -267
- audiopod/resources/transcription.py +0 -205
- audiopod/resources/voice.py +0 -139
- audiopod/resources/wallet.py +0 -110
- audiopod-2.1.0.dist-info/METADATA +0 -205
- audiopod-2.1.0.dist-info/RECORD +0 -16
- {audiopod-2.1.0.dist-info → audiopod-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {audiopod-2.1.0.dist-info → audiopod-2.2.0.dist-info}/top_level.txt +0 -0
audiopod/__init__.py
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
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)
|
|
13
4
|
"""
|
|
14
5
|
|
|
15
|
-
__version__ = "2.
|
|
6
|
+
__version__ = "2.2.0"
|
|
16
7
|
|
|
17
|
-
from .client import
|
|
8
|
+
from .client import Client, AsyncClient
|
|
18
9
|
from .exceptions import (
|
|
19
10
|
AudioPodError,
|
|
20
11
|
AuthenticationError,
|
|
21
12
|
APIError,
|
|
22
13
|
RateLimitError,
|
|
14
|
+
ValidationError,
|
|
23
15
|
InsufficientBalanceError,
|
|
24
16
|
)
|
|
25
17
|
|
|
18
|
+
# Alias for consistency with documentation and Node.js SDK
|
|
19
|
+
AudioPod = Client
|
|
20
|
+
|
|
26
21
|
__all__ = [
|
|
27
|
-
"
|
|
22
|
+
"Client",
|
|
23
|
+
"AsyncClient",
|
|
24
|
+
"AudioPod", # Alias for Client
|
|
28
25
|
"AudioPodError",
|
|
29
|
-
"AuthenticationError",
|
|
26
|
+
"AuthenticationError",
|
|
30
27
|
"APIError",
|
|
31
28
|
"RateLimitError",
|
|
29
|
+
"ValidationError",
|
|
32
30
|
"InsufficientBalanceError",
|
|
33
31
|
"__version__",
|
|
34
32
|
]
|
|
33
|
+
|
audiopod/client.py
CHANGED
|
@@ -1,102 +1,84 @@
|
|
|
1
1
|
"""
|
|
2
2
|
AudioPod API Client
|
|
3
|
-
|
|
4
|
-
Clean, minimal API inspired by OpenAI's SDK design.
|
|
5
3
|
"""
|
|
6
4
|
|
|
7
5
|
import os
|
|
8
|
-
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Optional, Dict, Any
|
|
9
8
|
from urllib.parse import urljoin
|
|
10
9
|
|
|
11
10
|
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
|
-
|
|
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
|
+
VideoService,
|
|
20
28
|
)
|
|
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
29
|
|
|
34
|
-
|
|
35
|
-
"""
|
|
36
|
-
AudioPod API Client.
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
37
31
|
|
|
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
32
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
>>> client = AudioPod(api_key="ap_...")
|
|
48
|
-
>>> result = client.transcription.transcribe(url="https://...")
|
|
49
|
-
"""
|
|
33
|
+
class BaseClient:
|
|
34
|
+
"""Base client with common functionality"""
|
|
50
35
|
|
|
51
36
|
def __init__(
|
|
52
37
|
self,
|
|
53
38
|
api_key: Optional[str] = None,
|
|
54
39
|
base_url: Optional[str] = None,
|
|
55
|
-
timeout: int =
|
|
40
|
+
timeout: int = 30,
|
|
56
41
|
max_retries: int = 3,
|
|
42
|
+
verify_ssl: bool = True,
|
|
43
|
+
debug: bool = False,
|
|
57
44
|
):
|
|
58
|
-
|
|
45
|
+
"""
|
|
46
|
+
Initialize the AudioPod API client.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
api_key: Your AudioPod API key. If None, reads from AUDIOPOD_API_KEY env var.
|
|
50
|
+
base_url: API base URL. Defaults to https://api.audiopod.ai
|
|
51
|
+
timeout: Request timeout in seconds.
|
|
52
|
+
max_retries: Maximum retries for failed requests.
|
|
53
|
+
verify_ssl: Whether to verify SSL certificates.
|
|
54
|
+
debug: Enable debug logging.
|
|
55
|
+
"""
|
|
56
|
+
if debug:
|
|
57
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
59
58
|
|
|
59
|
+
self.api_key = api_key or os.getenv("AUDIOPOD_API_KEY")
|
|
60
60
|
if not self.api_key:
|
|
61
61
|
raise AuthenticationError(
|
|
62
|
-
"API key
|
|
62
|
+
"API key required. Pass api_key or set AUDIOPOD_API_KEY environment variable."
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
if not self.api_key.startswith("ap_"):
|
|
66
|
-
raise AuthenticationError(
|
|
67
|
-
"Invalid API key format. AudioPod API keys start with 'ap_'"
|
|
68
|
-
)
|
|
66
|
+
raise AuthenticationError("Invalid API key format. Keys start with 'ap_'")
|
|
69
67
|
|
|
70
|
-
self.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
total=max_retries,
|
|
77
|
-
status_forcelist=[429, 500, 502, 503, 504],
|
|
78
|
-
allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "DELETE"],
|
|
79
|
-
backoff_factor=1,
|
|
68
|
+
self.config = ClientConfig(
|
|
69
|
+
base_url=base_url or "https://api.audiopod.ai",
|
|
70
|
+
timeout=timeout,
|
|
71
|
+
max_retries=max_retries,
|
|
72
|
+
verify_ssl=verify_ssl,
|
|
73
|
+
debug=debug,
|
|
80
74
|
)
|
|
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
75
|
|
|
94
76
|
def _get_headers(self) -> Dict[str, str]:
|
|
95
77
|
return {
|
|
96
78
|
"Authorization": f"Bearer {self.api_key}",
|
|
97
79
|
"X-API-Key": self.api_key,
|
|
98
80
|
"Content-Type": "application/json",
|
|
99
|
-
"User-Agent": f"audiopod-python/{
|
|
81
|
+
"User-Agent": f"audiopod-python/{self.config.version}",
|
|
100
82
|
"Accept": "application/json",
|
|
101
83
|
}
|
|
102
84
|
|
|
@@ -106,93 +88,225 @@ class AudioPod:
|
|
|
106
88
|
if response.status_code == 204:
|
|
107
89
|
return {}
|
|
108
90
|
return response.json()
|
|
109
|
-
except requests.exceptions.HTTPError:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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:
|
|
91
|
+
except requests.exceptions.HTTPError as e:
|
|
92
|
+
if response.status_code == 401:
|
|
93
|
+
raise AuthenticationError("Invalid API key")
|
|
94
|
+
elif response.status_code == 402:
|
|
120
95
|
try:
|
|
121
96
|
data = response.json()
|
|
122
97
|
raise InsufficientBalanceError(
|
|
123
|
-
message,
|
|
98
|
+
data.get("message", "Insufficient balance"),
|
|
124
99
|
required_cents=data.get("required_cents"),
|
|
125
100
|
available_cents=data.get("available_cents"),
|
|
126
101
|
)
|
|
127
102
|
except (ValueError, KeyError):
|
|
128
|
-
raise InsufficientBalanceError(
|
|
129
|
-
elif
|
|
130
|
-
raise RateLimitError(
|
|
103
|
+
raise InsufficientBalanceError("Insufficient wallet balance")
|
|
104
|
+
elif response.status_code == 429:
|
|
105
|
+
raise RateLimitError("Rate limit exceeded")
|
|
131
106
|
else:
|
|
132
|
-
|
|
107
|
+
try:
|
|
108
|
+
error_data = response.json()
|
|
109
|
+
message = error_data.get("detail", str(e))
|
|
110
|
+
except:
|
|
111
|
+
message = str(e)
|
|
112
|
+
raise APIError(f"API error: {message}", status_code=response.status_code)
|
|
133
113
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
114
|
+
|
|
115
|
+
class Client(BaseClient):
|
|
116
|
+
"""
|
|
117
|
+
Synchronous AudioPod API Client.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
```python
|
|
121
|
+
from audiopod import Client
|
|
122
|
+
|
|
123
|
+
client = Client(api_key="ap_your_key")
|
|
124
|
+
|
|
125
|
+
# Check wallet balance
|
|
126
|
+
balance = client.wallet.get_balance()
|
|
127
|
+
print(f"Balance: {balance['balance_usd']}")
|
|
128
|
+
|
|
129
|
+
# Extract stems
|
|
130
|
+
job = client.stem_extraction.extract_stems(
|
|
131
|
+
audio_file="song.mp3",
|
|
132
|
+
stem_types=["vocals", "drums", "bass", "other"]
|
|
140
133
|
)
|
|
141
|
-
|
|
134
|
+
```
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, **kwargs):
|
|
138
|
+
super().__init__(**kwargs)
|
|
139
|
+
|
|
140
|
+
self.session = requests.Session()
|
|
141
|
+
retry_strategy = Retry(
|
|
142
|
+
total=self.config.max_retries,
|
|
143
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
144
|
+
allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "DELETE"],
|
|
145
|
+
backoff_factor=1,
|
|
146
|
+
)
|
|
147
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
148
|
+
self.session.mount("http://", adapter)
|
|
149
|
+
self.session.mount("https://", adapter)
|
|
142
150
|
|
|
143
|
-
|
|
151
|
+
# Services
|
|
152
|
+
self.voice = VoiceService(self)
|
|
153
|
+
self.music = MusicService(self)
|
|
154
|
+
self.transcription = TranscriptionService(self)
|
|
155
|
+
self.translation = TranslationService(self)
|
|
156
|
+
self.speaker = SpeakerService(self)
|
|
157
|
+
self.denoiser = DenoiserService(self)
|
|
158
|
+
self.credits = CreditService(self)
|
|
159
|
+
self.stem_extraction = StemExtractionService(self)
|
|
160
|
+
self.stems = self.stem_extraction # Alias for consistency with Node.js SDK
|
|
161
|
+
self.wallet = WalletService(self)
|
|
162
|
+
self.video = VideoService(self)
|
|
163
|
+
|
|
164
|
+
def request(
|
|
144
165
|
self,
|
|
166
|
+
method: str,
|
|
145
167
|
endpoint: str,
|
|
146
168
|
data: Optional[Dict[str, Any]] = None,
|
|
147
169
|
json_data: Optional[Dict[str, Any]] = None,
|
|
170
|
+
files: Optional[Dict[str, Any]] = None,
|
|
171
|
+
params: Optional[Dict[str, Any]] = None,
|
|
148
172
|
) -> Dict[str, Any]:
|
|
149
|
-
"""Make
|
|
150
|
-
url = urljoin(self.base_url, endpoint)
|
|
173
|
+
"""Make API request."""
|
|
174
|
+
url = urljoin(self.config.base_url, endpoint)
|
|
151
175
|
headers = self._get_headers()
|
|
152
|
-
|
|
153
|
-
|
|
176
|
+
|
|
177
|
+
if files:
|
|
178
|
+
headers.pop("Content-Type", None)
|
|
179
|
+
|
|
180
|
+
response = self.session.request(
|
|
181
|
+
method=method,
|
|
182
|
+
url=url,
|
|
183
|
+
headers=headers,
|
|
184
|
+
data=data,
|
|
185
|
+
json=json_data,
|
|
186
|
+
files=files,
|
|
187
|
+
params=params,
|
|
188
|
+
timeout=self.config.timeout,
|
|
189
|
+
verify=self.config.verify_ssl,
|
|
154
190
|
)
|
|
155
191
|
return self._handle_response(response)
|
|
156
192
|
|
|
157
|
-
def
|
|
158
|
-
"""
|
|
159
|
-
|
|
160
|
-
headers = self._get_headers()
|
|
161
|
-
response = self._session.delete(url, headers=headers, timeout=self.timeout)
|
|
162
|
-
return self._handle_response(response)
|
|
193
|
+
def get_user_info(self) -> Dict[str, Any]:
|
|
194
|
+
"""Get current user information."""
|
|
195
|
+
return self.request("GET", "/api/v1/auth/me")
|
|
163
196
|
|
|
164
|
-
def
|
|
197
|
+
def close(self):
|
|
198
|
+
"""Close client session."""
|
|
199
|
+
self.session.close()
|
|
200
|
+
|
|
201
|
+
def __enter__(self):
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
205
|
+
self.close()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class AsyncClient(BaseClient):
|
|
209
|
+
"""
|
|
210
|
+
Asynchronous AudioPod API Client.
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
```python
|
|
214
|
+
import asyncio
|
|
215
|
+
from audiopod import AsyncClient
|
|
216
|
+
|
|
217
|
+
async def main():
|
|
218
|
+
async with AsyncClient(api_key="ap_your_key") as client:
|
|
219
|
+
balance = await client.wallet.get_balance()
|
|
220
|
+
print(f"Balance: {balance['balance_usd']}")
|
|
221
|
+
|
|
222
|
+
asyncio.run(main())
|
|
223
|
+
```
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
def __init__(self, **kwargs):
|
|
227
|
+
super().__init__(**kwargs)
|
|
228
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
229
|
+
|
|
230
|
+
# Services
|
|
231
|
+
self.voice = VoiceService(self, async_mode=True)
|
|
232
|
+
self.music = MusicService(self, async_mode=True)
|
|
233
|
+
self.transcription = TranscriptionService(self, async_mode=True)
|
|
234
|
+
self.translation = TranslationService(self, async_mode=True)
|
|
235
|
+
self.speaker = SpeakerService(self, async_mode=True)
|
|
236
|
+
self.denoiser = DenoiserService(self, async_mode=True)
|
|
237
|
+
self.credits = CreditService(self, async_mode=True)
|
|
238
|
+
self.stem_extraction = StemExtractionService(self, async_mode=True)
|
|
239
|
+
self.stems = self.stem_extraction # Alias for consistency with Node.js SDK
|
|
240
|
+
self.wallet = WalletService(self, async_mode=True)
|
|
241
|
+
self.video = VideoService(self, async_mode=True)
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def session(self) -> aiohttp.ClientSession:
|
|
245
|
+
if self._session is None or self._session.closed:
|
|
246
|
+
timeout = aiohttp.ClientTimeout(total=self.config.timeout)
|
|
247
|
+
connector = aiohttp.TCPConnector(ssl=self.config.verify_ssl)
|
|
248
|
+
self._session = aiohttp.ClientSession(
|
|
249
|
+
timeout=timeout, connector=connector, headers=self._get_headers()
|
|
250
|
+
)
|
|
251
|
+
return self._session
|
|
252
|
+
|
|
253
|
+
async def request(
|
|
165
254
|
self,
|
|
255
|
+
method: str,
|
|
166
256
|
endpoint: str,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
257
|
+
data: Optional[Dict[str, Any]] = None,
|
|
258
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
259
|
+
files: Optional[Dict[str, Any]] = None,
|
|
260
|
+
params: Optional[Dict[str, Any]] = None,
|
|
170
261
|
) -> Dict[str, Any]:
|
|
171
|
-
"""
|
|
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
|
-
)
|
|
262
|
+
"""Make async API request."""
|
|
263
|
+
url = urljoin(self.config.base_url, endpoint)
|
|
187
264
|
|
|
188
|
-
|
|
265
|
+
async with self.session.request(
|
|
266
|
+
method=method,
|
|
267
|
+
url=url,
|
|
268
|
+
json=json_data,
|
|
269
|
+
data=data,
|
|
270
|
+
params=params,
|
|
271
|
+
) as response:
|
|
272
|
+
return await self._handle_async_response(response)
|
|
189
273
|
|
|
190
|
-
def
|
|
191
|
-
|
|
192
|
-
|
|
274
|
+
async def _handle_async_response(self, response: aiohttp.ClientResponse) -> Dict[str, Any]:
|
|
275
|
+
if response.status == 204:
|
|
276
|
+
return {}
|
|
277
|
+
try:
|
|
278
|
+
response.raise_for_status()
|
|
279
|
+
return await response.json()
|
|
280
|
+
except aiohttp.ClientResponseError as e:
|
|
281
|
+
if response.status == 401:
|
|
282
|
+
raise AuthenticationError("Invalid API key")
|
|
283
|
+
elif response.status == 402:
|
|
284
|
+
try:
|
|
285
|
+
data = await response.json()
|
|
286
|
+
raise InsufficientBalanceError(
|
|
287
|
+
data.get("message", "Insufficient balance"),
|
|
288
|
+
required_cents=data.get("required_cents"),
|
|
289
|
+
available_cents=data.get("available_cents"),
|
|
290
|
+
)
|
|
291
|
+
except:
|
|
292
|
+
raise InsufficientBalanceError("Insufficient wallet balance")
|
|
293
|
+
elif response.status == 429:
|
|
294
|
+
raise RateLimitError("Rate limit exceeded")
|
|
295
|
+
else:
|
|
296
|
+
raise APIError(f"API error: {e}", status_code=response.status)
|
|
193
297
|
|
|
194
|
-
def
|
|
298
|
+
async def get_user_info(self) -> Dict[str, Any]:
|
|
299
|
+
"""Get current user information."""
|
|
300
|
+
return await self.request("GET", "/api/v1/auth/me")
|
|
301
|
+
|
|
302
|
+
async def close(self):
|
|
303
|
+
"""Close async client session."""
|
|
304
|
+
if self._session and not self._session.closed:
|
|
305
|
+
await self._session.close()
|
|
306
|
+
|
|
307
|
+
async def __aenter__(self):
|
|
195
308
|
return self
|
|
196
309
|
|
|
197
|
-
def
|
|
198
|
-
self.close()
|
|
310
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
311
|
+
await self.close()
|
|
312
|
+
|
audiopod/config.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AudioPod SDK Configuration
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ClientConfig:
|
|
10
|
+
"""Client configuration settings"""
|
|
11
|
+
base_url: str = "https://api.audiopod.ai"
|
|
12
|
+
timeout: int = 30
|
|
13
|
+
max_retries: int = 3
|
|
14
|
+
verify_ssl: bool = True
|
|
15
|
+
debug: bool = False
|
|
16
|
+
version: str = "1.3.0"
|
|
17
|
+
|
audiopod/exceptions.py
CHANGED
|
@@ -2,48 +2,40 @@
|
|
|
2
2
|
AudioPod SDK Exceptions
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
5
|
|
|
8
6
|
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)
|
|
7
|
+
"""Base exception for AudioPod SDK"""
|
|
8
|
+
pass
|
|
14
9
|
|
|
15
10
|
|
|
16
11
|
class AuthenticationError(AudioPodError):
|
|
17
|
-
"""Raised when authentication fails
|
|
18
|
-
|
|
19
|
-
def __init__(self, message: str = "Authentication failed"):
|
|
20
|
-
super().__init__(message)
|
|
12
|
+
"""Raised when authentication fails"""
|
|
13
|
+
pass
|
|
21
14
|
|
|
22
15
|
|
|
23
16
|
class APIError(AudioPodError):
|
|
24
|
-
"""Raised when
|
|
25
|
-
|
|
26
|
-
def __init__(self, message: str
|
|
27
|
-
self.status_code = status_code
|
|
17
|
+
"""Raised when API returns an error"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, message: str, status_code: int = None):
|
|
28
20
|
super().__init__(message)
|
|
21
|
+
self.status_code = status_code
|
|
29
22
|
|
|
30
23
|
|
|
31
24
|
class RateLimitError(AudioPodError):
|
|
32
|
-
"""Raised when rate limit is exceeded
|
|
25
|
+
"""Raised when rate limit is exceeded"""
|
|
26
|
+
pass
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
|
|
29
|
+
class ValidationError(AudioPodError):
|
|
30
|
+
"""Raised when input validation fails"""
|
|
31
|
+
pass
|
|
36
32
|
|
|
37
33
|
|
|
38
34
|
class InsufficientBalanceError(AudioPodError):
|
|
39
|
-
"""Raised when wallet balance is insufficient
|
|
40
|
-
|
|
41
|
-
def __init__(
|
|
42
|
-
|
|
43
|
-
message: str = "Insufficient wallet balance",
|
|
44
|
-
required_cents: Optional[int] = None,
|
|
45
|
-
available_cents: Optional[int] = None,
|
|
46
|
-
):
|
|
35
|
+
"""Raised when wallet balance is insufficient"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, message: str, required_cents: int = None, available_cents: int = None):
|
|
38
|
+
super().__init__(message)
|
|
47
39
|
self.required_cents = required_cents
|
|
48
40
|
self.available_cents = available_cents
|
|
49
|
-
|
|
41
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AudioPod SDK Services
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .base import BaseService
|
|
6
|
+
from .voice import VoiceService
|
|
7
|
+
from .music import MusicService
|
|
8
|
+
from .transcription import TranscriptionService
|
|
9
|
+
from .translation import TranslationService
|
|
10
|
+
from .speaker import SpeakerService
|
|
11
|
+
from .denoiser import DenoiserService
|
|
12
|
+
from .credits import CreditService
|
|
13
|
+
from .stem_extraction import StemExtractionService
|
|
14
|
+
from .wallet import WalletService
|
|
15
|
+
from .video import VideoService
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"BaseService",
|
|
19
|
+
"VoiceService",
|
|
20
|
+
"MusicService",
|
|
21
|
+
"TranscriptionService",
|
|
22
|
+
"TranslationService",
|
|
23
|
+
"SpeakerService",
|
|
24
|
+
"DenoiserService",
|
|
25
|
+
"CreditService",
|
|
26
|
+
"StemExtractionService",
|
|
27
|
+
"WalletService",
|
|
28
|
+
"VideoService",
|
|
29
|
+
]
|
|
30
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Service Class
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, Dict, Optional, Tuple, BinaryIO
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseService:
|
|
10
|
+
"""Base class for all services"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, client: Any, async_mode: bool = False):
|
|
13
|
+
self.client = client
|
|
14
|
+
self.async_mode = async_mode
|
|
15
|
+
|
|
16
|
+
def _prepare_file_upload(
|
|
17
|
+
self, file_path: str, field_name: str = "file"
|
|
18
|
+
) -> Dict[str, Tuple[str, BinaryIO, str]]:
|
|
19
|
+
"""Prepare file for upload."""
|
|
20
|
+
import mimetypes
|
|
21
|
+
|
|
22
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
23
|
+
mime_type = mime_type or "application/octet-stream"
|
|
24
|
+
|
|
25
|
+
file_handle = open(file_path, "rb")
|
|
26
|
+
filename = file_path.split("/")[-1]
|
|
27
|
+
|
|
28
|
+
return {field_name: (filename, file_handle, mime_type)}
|
|
29
|
+
|
|
30
|
+
def _wait_for_completion(
|
|
31
|
+
self, job_id: int, timeout: int = 900, poll_interval: int = 5
|
|
32
|
+
) -> Dict[str, Any]:
|
|
33
|
+
"""Wait for job completion."""
|
|
34
|
+
start_time = time.time()
|
|
35
|
+
|
|
36
|
+
while time.time() - start_time < timeout:
|
|
37
|
+
response = self.client.request("GET", f"/api/v1/jobs/{job_id}")
|
|
38
|
+
|
|
39
|
+
status = response.get("status", "").upper()
|
|
40
|
+
if status == "COMPLETED":
|
|
41
|
+
return response
|
|
42
|
+
elif status in ["FAILED", "ERROR"]:
|
|
43
|
+
raise Exception(f"Job failed: {response.get('error_message', 'Unknown error')}")
|
|
44
|
+
|
|
45
|
+
time.sleep(poll_interval)
|
|
46
|
+
|
|
47
|
+
raise TimeoutError(f"Job {job_id} did not complete within {timeout} seconds")
|
|
48
|
+
|
|
49
|
+
async def _async_wait_for_completion(
|
|
50
|
+
self, job_id: int, timeout: int = 900, poll_interval: int = 5
|
|
51
|
+
) -> Dict[str, Any]:
|
|
52
|
+
"""Async wait for job completion."""
|
|
53
|
+
import asyncio
|
|
54
|
+
|
|
55
|
+
start_time = time.time()
|
|
56
|
+
|
|
57
|
+
while time.time() - start_time < timeout:
|
|
58
|
+
response = await self.client.request("GET", f"/api/v1/jobs/{job_id}")
|
|
59
|
+
|
|
60
|
+
status = response.get("status", "").upper()
|
|
61
|
+
if status == "COMPLETED":
|
|
62
|
+
return response
|
|
63
|
+
elif status in ["FAILED", "ERROR"]:
|
|
64
|
+
raise Exception(f"Job failed: {response.get('error_message', 'Unknown error')}")
|
|
65
|
+
|
|
66
|
+
await asyncio.sleep(poll_interval)
|
|
67
|
+
|
|
68
|
+
raise TimeoutError(f"Job {job_id} did not complete within {timeout} seconds")
|
|
69
|
+
|