audiopod 1.2.0__py3-none-any.whl → 1.4.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 +10 -64
- audiopod/client.py +143 -172
- audiopod/config.py +4 -50
- audiopod/exceptions.py +16 -71
- audiopod/services/__init__.py +8 -6
- audiopod/services/base.py +51 -195
- audiopod/services/credits.py +26 -30
- audiopod/services/denoiser.py +120 -40
- audiopod/services/music.py +180 -485
- audiopod/services/speaker.py +117 -36
- audiopod/services/stem_extraction.py +130 -142
- audiopod/services/transcription.py +159 -184
- audiopod/services/translation.py +109 -170
- audiopod/services/voice.py +141 -424
- audiopod/services/wallet.py +235 -0
- audiopod-1.4.0.dist-info/METADATA +206 -0
- audiopod-1.4.0.dist-info/RECORD +20 -0
- {audiopod-1.2.0.dist-info → audiopod-1.4.0.dist-info}/WHEEL +1 -1
- audiopod/cli.py +0 -285
- audiopod/models.py +0 -250
- audiopod/py.typed +0 -2
- audiopod/services/karaoke.py +0 -61
- audiopod-1.2.0.dist-info/METADATA +0 -454
- audiopod-1.2.0.dist-info/RECORD +0 -24
- audiopod-1.2.0.dist-info/entry_points.txt +0 -2
- {audiopod-1.2.0.dist-info → audiopod-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {audiopod-1.2.0.dist-info → audiopod-1.4.0.dist-info}/top_level.txt +0 -0
audiopod/__init__.py
CHANGED
|
@@ -1,83 +1,29 @@
|
|
|
1
1
|
"""
|
|
2
|
-
AudioPod
|
|
3
|
-
Professional Audio Processing
|
|
4
|
-
|
|
5
|
-
This package provides a comprehensive Python SDK for the AudioPod API,
|
|
6
|
-
enabling developers to integrate advanced audio processing capabilities
|
|
7
|
-
into their applications.
|
|
8
|
-
|
|
9
|
-
Basic Usage:
|
|
10
|
-
>>> import audiopod
|
|
11
|
-
>>> client = audiopod.Client(api_key="your-api-key")
|
|
12
|
-
>>>
|
|
13
|
-
>>> # Voice cloning
|
|
14
|
-
>>> job = client.voice.clone_voice(
|
|
15
|
-
... voice_file="path/to/voice.wav",
|
|
16
|
-
... text="Hello, this is a cloned voice!"
|
|
17
|
-
... )
|
|
18
|
-
>>>
|
|
19
|
-
>>> # Music generation
|
|
20
|
-
>>> music = client.music.generate(
|
|
21
|
-
... prompt="upbeat electronic dance music"
|
|
22
|
-
... )
|
|
23
|
-
>>>
|
|
24
|
-
>>> # Audio transcription
|
|
25
|
-
>>> transcript = client.transcription.transcribe(
|
|
26
|
-
... audio_file="path/to/audio.mp3",
|
|
27
|
-
... language="en"
|
|
28
|
-
... )
|
|
29
|
-
|
|
30
|
-
For more examples and documentation, visit: https://docs.audiopod.ai
|
|
2
|
+
AudioPod SDK for Python
|
|
3
|
+
Professional Audio Processing powered by AI
|
|
31
4
|
"""
|
|
32
5
|
|
|
6
|
+
__version__ = "1.3.0"
|
|
7
|
+
|
|
33
8
|
from .client import Client, AsyncClient
|
|
34
9
|
from .exceptions import (
|
|
35
10
|
AudioPodError,
|
|
36
|
-
AuthenticationError,
|
|
11
|
+
AuthenticationError,
|
|
37
12
|
APIError,
|
|
38
13
|
RateLimitError,
|
|
39
14
|
ValidationError,
|
|
40
|
-
|
|
15
|
+
InsufficientBalanceError,
|
|
41
16
|
)
|
|
42
|
-
from .models import (
|
|
43
|
-
Job,
|
|
44
|
-
VoiceProfile,
|
|
45
|
-
TranscriptionResult,
|
|
46
|
-
MusicGenerationResult,
|
|
47
|
-
TranslationResult
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
__version__ = "1.2.0"
|
|
51
|
-
__author__ = "AudioPod AI"
|
|
52
|
-
__email__ = "support@audiopod.ai"
|
|
53
|
-
__license__ = "MIT"
|
|
54
17
|
|
|
55
|
-
# Public API
|
|
56
18
|
__all__ = [
|
|
57
|
-
# Main clients
|
|
58
19
|
"Client",
|
|
59
20
|
"AsyncClient",
|
|
60
|
-
|
|
61
|
-
# Exceptions
|
|
62
21
|
"AudioPodError",
|
|
63
|
-
"AuthenticationError",
|
|
64
|
-
"APIError",
|
|
22
|
+
"AuthenticationError",
|
|
23
|
+
"APIError",
|
|
65
24
|
"RateLimitError",
|
|
66
25
|
"ValidationError",
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
# Models
|
|
70
|
-
"Job",
|
|
71
|
-
"VoiceProfile",
|
|
72
|
-
"TranscriptionResult",
|
|
73
|
-
"MusicGenerationResult",
|
|
74
|
-
"TranslationResult",
|
|
26
|
+
"InsufficientBalanceError",
|
|
27
|
+
"__version__",
|
|
75
28
|
]
|
|
76
29
|
|
|
77
|
-
# Package metadata
|
|
78
|
-
__all__.extend([
|
|
79
|
-
"__version__",
|
|
80
|
-
"__author__",
|
|
81
|
-
"__email__",
|
|
82
|
-
"__license__"
|
|
83
|
-
])
|
audiopod/client.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
2
|
AudioPod API Client
|
|
3
|
-
Main client classes for synchronous and asynchronous API access
|
|
4
3
|
"""
|
|
5
4
|
|
|
6
5
|
import os
|
|
7
6
|
import logging
|
|
8
|
-
from typing import Optional, Dict, Any
|
|
7
|
+
from typing import Optional, Dict, Any
|
|
9
8
|
from urllib.parse import urljoin
|
|
10
9
|
|
|
11
10
|
import requests
|
|
@@ -14,17 +13,17 @@ from requests.adapters import HTTPAdapter
|
|
|
14
13
|
from urllib3.util.retry import Retry
|
|
15
14
|
|
|
16
15
|
from .config import ClientConfig
|
|
17
|
-
from .exceptions import AuthenticationError, APIError, RateLimitError
|
|
16
|
+
from .exceptions import AuthenticationError, APIError, RateLimitError, InsufficientBalanceError
|
|
18
17
|
from .services import (
|
|
19
18
|
VoiceService,
|
|
20
|
-
MusicService,
|
|
19
|
+
MusicService,
|
|
21
20
|
TranscriptionService,
|
|
22
21
|
TranslationService,
|
|
23
22
|
SpeakerService,
|
|
24
23
|
DenoiserService,
|
|
25
|
-
KaraokeService,
|
|
26
24
|
CreditService,
|
|
27
|
-
StemExtractionService
|
|
25
|
+
StemExtractionService,
|
|
26
|
+
WalletService,
|
|
28
27
|
)
|
|
29
28
|
|
|
30
29
|
logger = logging.getLogger(__name__)
|
|
@@ -32,7 +31,7 @@ logger = logging.getLogger(__name__)
|
|
|
32
31
|
|
|
33
32
|
class BaseClient:
|
|
34
33
|
"""Base client with common functionality"""
|
|
35
|
-
|
|
34
|
+
|
|
36
35
|
def __init__(
|
|
37
36
|
self,
|
|
38
37
|
api_key: Optional[str] = None,
|
|
@@ -40,296 +39,268 @@ class BaseClient:
|
|
|
40
39
|
timeout: int = 30,
|
|
41
40
|
max_retries: int = 3,
|
|
42
41
|
verify_ssl: bool = True,
|
|
43
|
-
debug: bool = False
|
|
42
|
+
debug: bool = False,
|
|
44
43
|
):
|
|
45
44
|
"""
|
|
46
|
-
Initialize the AudioPod API client
|
|
47
|
-
|
|
45
|
+
Initialize the AudioPod API client.
|
|
46
|
+
|
|
48
47
|
Args:
|
|
49
|
-
api_key: Your AudioPod API key. If None,
|
|
50
|
-
base_url: API base URL. Defaults to
|
|
51
|
-
timeout: Request timeout in seconds
|
|
52
|
-
max_retries: Maximum
|
|
53
|
-
verify_ssl: Whether to verify SSL certificates
|
|
54
|
-
debug: Enable debug logging
|
|
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.
|
|
55
54
|
"""
|
|
56
|
-
# Set up logging
|
|
57
55
|
if debug:
|
|
58
56
|
logging.basicConfig(level=logging.DEBUG)
|
|
59
|
-
|
|
60
|
-
# Get API key
|
|
57
|
+
|
|
61
58
|
self.api_key = api_key or os.getenv("AUDIOPOD_API_KEY")
|
|
62
59
|
if not self.api_key:
|
|
63
60
|
raise AuthenticationError(
|
|
64
|
-
"API key
|
|
61
|
+
"API key required. Pass api_key or set AUDIOPOD_API_KEY environment variable."
|
|
65
62
|
)
|
|
66
|
-
|
|
67
|
-
# Validate API key format
|
|
63
|
+
|
|
68
64
|
if not self.api_key.startswith("ap_"):
|
|
69
|
-
raise AuthenticationError("Invalid API key format.
|
|
70
|
-
|
|
71
|
-
# Configuration
|
|
65
|
+
raise AuthenticationError("Invalid API key format. Keys start with 'ap_'")
|
|
66
|
+
|
|
72
67
|
self.config = ClientConfig(
|
|
73
68
|
base_url=base_url or "https://api.audiopod.ai",
|
|
74
69
|
timeout=timeout,
|
|
75
70
|
max_retries=max_retries,
|
|
76
71
|
verify_ssl=verify_ssl,
|
|
77
|
-
debug=debug
|
|
72
|
+
debug=debug,
|
|
78
73
|
)
|
|
79
|
-
|
|
74
|
+
|
|
80
75
|
def _get_headers(self) -> Dict[str, str]:
|
|
81
|
-
"""Get standard headers for API requests"""
|
|
82
76
|
return {
|
|
83
77
|
"Authorization": f"Bearer {self.api_key}",
|
|
84
78
|
"Content-Type": "application/json",
|
|
85
79
|
"User-Agent": f"audiopod-python/{self.config.version}",
|
|
86
|
-
"Accept": "application/json"
|
|
80
|
+
"Accept": "application/json",
|
|
87
81
|
}
|
|
88
|
-
|
|
82
|
+
|
|
89
83
|
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
|
|
90
|
-
"""Handle API response and raise appropriate exceptions"""
|
|
91
84
|
try:
|
|
92
85
|
response.raise_for_status()
|
|
86
|
+
if response.status_code == 204:
|
|
87
|
+
return {}
|
|
93
88
|
return response.json()
|
|
94
89
|
except requests.exceptions.HTTPError as e:
|
|
95
90
|
if response.status_code == 401:
|
|
96
|
-
raise AuthenticationError("Invalid API key
|
|
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")
|
|
97
102
|
elif response.status_code == 429:
|
|
98
|
-
raise RateLimitError("Rate limit exceeded
|
|
99
|
-
|
|
103
|
+
raise RateLimitError("Rate limit exceeded")
|
|
104
|
+
else:
|
|
100
105
|
try:
|
|
101
106
|
error_data = response.json()
|
|
102
107
|
message = error_data.get("detail", str(e))
|
|
103
108
|
except:
|
|
104
109
|
message = str(e)
|
|
105
|
-
raise APIError(f"API
|
|
106
|
-
else:
|
|
107
|
-
raise APIError(f"Unexpected HTTP error: {e}")
|
|
108
|
-
except requests.exceptions.RequestException as e:
|
|
109
|
-
raise APIError(f"Request failed: {e}")
|
|
110
|
+
raise APIError(f"API error: {message}", status_code=response.status_code)
|
|
110
111
|
|
|
111
112
|
|
|
112
113
|
class Client(BaseClient):
|
|
113
114
|
"""
|
|
114
|
-
Synchronous AudioPod API Client
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
```
|
|
117
133
|
"""
|
|
118
|
-
|
|
134
|
+
|
|
119
135
|
def __init__(self, **kwargs):
|
|
120
136
|
super().__init__(**kwargs)
|
|
121
|
-
|
|
122
|
-
# Configure session with retries
|
|
137
|
+
|
|
123
138
|
self.session = requests.Session()
|
|
124
139
|
retry_strategy = Retry(
|
|
125
140
|
total=self.config.max_retries,
|
|
126
141
|
status_forcelist=[429, 500, 502, 503, 504],
|
|
127
|
-
|
|
128
|
-
backoff_factor=1
|
|
142
|
+
allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "DELETE"],
|
|
143
|
+
backoff_factor=1,
|
|
129
144
|
)
|
|
130
145
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
131
146
|
self.session.mount("http://", adapter)
|
|
132
147
|
self.session.mount("https://", adapter)
|
|
133
|
-
|
|
134
|
-
#
|
|
148
|
+
|
|
149
|
+
# Services
|
|
135
150
|
self.voice = VoiceService(self)
|
|
136
151
|
self.music = MusicService(self)
|
|
137
152
|
self.transcription = TranscriptionService(self)
|
|
138
153
|
self.translation = TranslationService(self)
|
|
139
154
|
self.speaker = SpeakerService(self)
|
|
140
155
|
self.denoiser = DenoiserService(self)
|
|
141
|
-
self.karaoke = KaraokeService(self)
|
|
142
156
|
self.credits = CreditService(self)
|
|
143
157
|
self.stem_extraction = StemExtractionService(self)
|
|
144
|
-
|
|
158
|
+
self.wallet = WalletService(self)
|
|
159
|
+
|
|
145
160
|
def request(
|
|
146
161
|
self,
|
|
147
162
|
method: str,
|
|
148
163
|
endpoint: str,
|
|
149
164
|
data: Optional[Dict[str, Any]] = None,
|
|
165
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
150
166
|
files: Optional[Dict[str, Any]] = None,
|
|
151
167
|
params: Optional[Dict[str, Any]] = None,
|
|
152
|
-
**kwargs
|
|
153
168
|
) -> Dict[str, Any]:
|
|
154
|
-
"""
|
|
155
|
-
Make a request to the AudioPod API
|
|
156
|
-
|
|
157
|
-
Args:
|
|
158
|
-
method: HTTP method (GET, POST, etc.)
|
|
159
|
-
endpoint: API endpoint path
|
|
160
|
-
data: JSON data to send
|
|
161
|
-
files: Files to upload
|
|
162
|
-
params: URL parameters
|
|
163
|
-
**kwargs: Additional requests parameters
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
API response data
|
|
167
|
-
"""
|
|
169
|
+
"""Make API request."""
|
|
168
170
|
url = urljoin(self.config.base_url, endpoint)
|
|
169
171
|
headers = self._get_headers()
|
|
170
|
-
|
|
171
|
-
# Handle file uploads (don't set Content-Type for multipart)
|
|
172
|
+
|
|
172
173
|
if files:
|
|
173
174
|
headers.pop("Content-Type", None)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
except Exception as e:
|
|
190
|
-
logger.error(f"Request failed: {method} {url} - {e}")
|
|
191
|
-
raise
|
|
192
|
-
|
|
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
|
+
|
|
193
189
|
def get_user_info(self) -> Dict[str, Any]:
|
|
194
|
-
"""Get current user information"""
|
|
190
|
+
"""Get current user information."""
|
|
195
191
|
return self.request("GET", "/api/v1/auth/me")
|
|
196
|
-
|
|
197
|
-
def check_health(self) -> Dict[str, Any]:
|
|
198
|
-
"""Check API health status"""
|
|
199
|
-
return self.request("GET", "/api/v1/health")
|
|
200
|
-
|
|
192
|
+
|
|
201
193
|
def close(self):
|
|
202
|
-
"""Close
|
|
194
|
+
"""Close client session."""
|
|
203
195
|
self.session.close()
|
|
204
|
-
|
|
196
|
+
|
|
205
197
|
def __enter__(self):
|
|
206
198
|
return self
|
|
207
|
-
|
|
199
|
+
|
|
208
200
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
209
201
|
self.close()
|
|
210
202
|
|
|
211
203
|
|
|
212
204
|
class AsyncClient(BaseClient):
|
|
213
205
|
"""
|
|
214
|
-
Asynchronous AudioPod API Client
|
|
215
|
-
|
|
216
|
-
|
|
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
|
+
```
|
|
217
220
|
"""
|
|
218
|
-
|
|
221
|
+
|
|
219
222
|
def __init__(self, **kwargs):
|
|
220
223
|
super().__init__(**kwargs)
|
|
221
224
|
self._session: Optional[aiohttp.ClientSession] = None
|
|
222
|
-
|
|
223
|
-
#
|
|
225
|
+
|
|
226
|
+
# Services
|
|
224
227
|
self.voice = VoiceService(self, async_mode=True)
|
|
225
228
|
self.music = MusicService(self, async_mode=True)
|
|
226
229
|
self.transcription = TranscriptionService(self, async_mode=True)
|
|
227
230
|
self.translation = TranslationService(self, async_mode=True)
|
|
228
231
|
self.speaker = SpeakerService(self, async_mode=True)
|
|
229
232
|
self.denoiser = DenoiserService(self, async_mode=True)
|
|
230
|
-
self.karaoke = KaraokeService(self, async_mode=True)
|
|
231
233
|
self.credits = CreditService(self, async_mode=True)
|
|
232
234
|
self.stem_extraction = StemExtractionService(self, async_mode=True)
|
|
233
|
-
|
|
235
|
+
self.wallet = WalletService(self, async_mode=True)
|
|
236
|
+
|
|
234
237
|
@property
|
|
235
238
|
def session(self) -> aiohttp.ClientSession:
|
|
236
|
-
"""Get or create aiohttp session"""
|
|
237
239
|
if self._session is None or self._session.closed:
|
|
238
240
|
timeout = aiohttp.ClientTimeout(total=self.config.timeout)
|
|
239
|
-
connector = aiohttp.TCPConnector(
|
|
241
|
+
connector = aiohttp.TCPConnector(ssl=self.config.verify_ssl)
|
|
240
242
|
self._session = aiohttp.ClientSession(
|
|
241
|
-
timeout=timeout,
|
|
242
|
-
connector=connector,
|
|
243
|
-
headers=self._get_headers()
|
|
243
|
+
timeout=timeout, connector=connector, headers=self._get_headers()
|
|
244
244
|
)
|
|
245
245
|
return self._session
|
|
246
|
-
|
|
246
|
+
|
|
247
247
|
async def request(
|
|
248
248
|
self,
|
|
249
249
|
method: str,
|
|
250
250
|
endpoint: str,
|
|
251
251
|
data: Optional[Dict[str, Any]] = None,
|
|
252
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
252
253
|
files: Optional[Dict[str, Any]] = None,
|
|
253
254
|
params: Optional[Dict[str, Any]] = None,
|
|
254
|
-
**kwargs
|
|
255
255
|
) -> Dict[str, Any]:
|
|
256
|
-
"""
|
|
257
|
-
Make an async request to the AudioPod API
|
|
258
|
-
|
|
259
|
-
Args:
|
|
260
|
-
method: HTTP method (GET, POST, etc.)
|
|
261
|
-
endpoint: API endpoint path
|
|
262
|
-
data: JSON data to send
|
|
263
|
-
files: Files to upload
|
|
264
|
-
params: URL parameters
|
|
265
|
-
**kwargs: Additional aiohttp parameters
|
|
266
|
-
|
|
267
|
-
Returns:
|
|
268
|
-
API response data
|
|
269
|
-
"""
|
|
256
|
+
"""Make async API request."""
|
|
270
257
|
url = urljoin(self.config.base_url, endpoint)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
async with self.session.request(
|
|
283
|
-
method=method,
|
|
284
|
-
url=url,
|
|
285
|
-
json=data if not files else None,
|
|
286
|
-
data=data if files else None,
|
|
287
|
-
params=params,
|
|
288
|
-
**kwargs
|
|
289
|
-
) as response:
|
|
290
|
-
return await self._handle_async_response(response)
|
|
291
|
-
|
|
292
|
-
except Exception as e:
|
|
293
|
-
logger.error(f"Async request failed: {method} {url} - {e}")
|
|
294
|
-
raise
|
|
295
|
-
|
|
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
|
+
|
|
296
268
|
async def _handle_async_response(self, response: aiohttp.ClientResponse) -> Dict[str, Any]:
|
|
297
|
-
|
|
269
|
+
if response.status == 204:
|
|
270
|
+
return {}
|
|
298
271
|
try:
|
|
299
272
|
response.raise_for_status()
|
|
300
273
|
return await response.json()
|
|
301
274
|
except aiohttp.ClientResponseError as e:
|
|
302
275
|
if response.status == 401:
|
|
303
|
-
raise AuthenticationError("Invalid API key
|
|
304
|
-
elif response.status ==
|
|
305
|
-
raise RateLimitError("Rate limit exceeded. Please try again later.")
|
|
306
|
-
elif response.status >= 400:
|
|
276
|
+
raise AuthenticationError("Invalid API key")
|
|
277
|
+
elif response.status == 402:
|
|
307
278
|
try:
|
|
308
|
-
|
|
309
|
-
|
|
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
|
+
)
|
|
310
285
|
except:
|
|
311
|
-
|
|
312
|
-
|
|
286
|
+
raise InsufficientBalanceError("Insufficient wallet balance")
|
|
287
|
+
elif response.status == 429:
|
|
288
|
+
raise RateLimitError("Rate limit exceeded")
|
|
313
289
|
else:
|
|
314
|
-
raise APIError(f"
|
|
315
|
-
|
|
316
|
-
raise APIError(f"Request failed: {e}")
|
|
317
|
-
|
|
290
|
+
raise APIError(f"API error: {e}", status_code=response.status)
|
|
291
|
+
|
|
318
292
|
async def get_user_info(self) -> Dict[str, Any]:
|
|
319
|
-
"""Get current user information"""
|
|
293
|
+
"""Get current user information."""
|
|
320
294
|
return await self.request("GET", "/api/v1/auth/me")
|
|
321
|
-
|
|
322
|
-
async def check_health(self) -> Dict[str, Any]:
|
|
323
|
-
"""Check API health status"""
|
|
324
|
-
return await self.request("GET", "/api/v1/health")
|
|
325
|
-
|
|
295
|
+
|
|
326
296
|
async def close(self):
|
|
327
|
-
"""Close
|
|
297
|
+
"""Close async client session."""
|
|
328
298
|
if self._session and not self._session.closed:
|
|
329
299
|
await self._session.close()
|
|
330
|
-
|
|
300
|
+
|
|
331
301
|
async def __aenter__(self):
|
|
332
302
|
return self
|
|
333
|
-
|
|
303
|
+
|
|
334
304
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
335
305
|
await self.close()
|
|
306
|
+
|
audiopod/config.py
CHANGED
|
@@ -1,63 +1,17 @@
|
|
|
1
1
|
"""
|
|
2
|
-
AudioPod
|
|
2
|
+
AudioPod SDK Configuration
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from typing import Optional
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@dataclass
|
|
10
9
|
class ClientConfig:
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
# API settings
|
|
10
|
+
"""Client configuration settings"""
|
|
14
11
|
base_url: str = "https://api.audiopod.ai"
|
|
15
|
-
api_version: str = "v1"
|
|
16
|
-
|
|
17
|
-
# Request settings
|
|
18
12
|
timeout: int = 30
|
|
19
13
|
max_retries: int = 3
|
|
20
14
|
verify_ssl: bool = True
|
|
21
|
-
|
|
22
|
-
# Client settings
|
|
23
15
|
debug: bool = False
|
|
24
|
-
version: str = "1.
|
|
25
|
-
|
|
26
|
-
# Rate limiting
|
|
27
|
-
rate_limit_per_minute: int = 600
|
|
28
|
-
|
|
29
|
-
# File upload limits
|
|
30
|
-
max_file_size_mb: int = 100
|
|
31
|
-
supported_audio_formats: tuple = (
|
|
32
|
-
".mp3", ".wav", ".m4a", ".flac", ".ogg",
|
|
33
|
-
".aac", ".wma", ".aiff", ".au"
|
|
34
|
-
)
|
|
35
|
-
supported_video_formats: tuple = (
|
|
36
|
-
".mp4", ".avi", ".mov", ".mkv", ".wmv",
|
|
37
|
-
".flv", ".webm", ".m4v"
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def api_base_url(self) -> str:
|
|
42
|
-
"""Get full API base URL with version"""
|
|
43
|
-
return f"{self.base_url}/api/{self.api_version}"
|
|
44
|
-
|
|
45
|
-
def validate_file_format(self, filename: str, file_type: str = "audio") -> bool:
|
|
46
|
-
"""
|
|
47
|
-
Validate if file format is supported
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
filename: Name of the file
|
|
51
|
-
file_type: Type of file ('audio' or 'video')
|
|
52
|
-
|
|
53
|
-
Returns:
|
|
54
|
-
True if format is supported
|
|
55
|
-
"""
|
|
56
|
-
filename = filename.lower()
|
|
57
|
-
|
|
58
|
-
if file_type == "audio":
|
|
59
|
-
return any(filename.endswith(fmt) for fmt in self.supported_audio_formats)
|
|
60
|
-
elif file_type == "video":
|
|
61
|
-
return any(filename.endswith(fmt) for fmt in self.supported_video_formats)
|
|
62
|
-
else:
|
|
63
|
-
return False
|
|
16
|
+
version: str = "1.3.0"
|
|
17
|
+
|