audiopod 1.2.0__py3-none-any.whl → 1.5.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 -63
- audiopod/client.py +149 -172
- audiopod/config.py +4 -50
- audiopod/exceptions.py +16 -71
- audiopod/services/__init__.py +10 -6
- audiopod/services/base.py +51 -195
- audiopod/services/credits.py +26 -30
- audiopod/services/denoiser.py +125 -40
- audiopod/services/music.py +180 -485
- audiopod/services/speaker.py +117 -36
- audiopod/services/stem_extraction.py +249 -142
- audiopod/services/transcription.py +182 -184
- audiopod/services/translation.py +109 -170
- audiopod/services/video.py +329 -0
- audiopod/services/voice.py +141 -424
- audiopod/services/wallet.py +235 -0
- audiopod-1.5.0.dist-info/METADATA +206 -0
- audiopod-1.5.0.dist-info/RECORD +21 -0
- {audiopod-1.2.0.dist-info → audiopod-1.5.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.5.0.dist-info}/licenses/LICENSE +0 -0
- {audiopod-1.2.0.dist-info → audiopod-1.5.0.dist-info}/top_level.txt +0 -0
audiopod/__init__.py
CHANGED
|
@@ -1,83 +1,33 @@
|
|
|
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.5.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
|
-
|
|
41
|
-
)
|
|
42
|
-
from .models import (
|
|
43
|
-
Job,
|
|
44
|
-
VoiceProfile,
|
|
45
|
-
TranscriptionResult,
|
|
46
|
-
MusicGenerationResult,
|
|
47
|
-
TranslationResult
|
|
15
|
+
InsufficientBalanceError,
|
|
48
16
|
)
|
|
49
17
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
__email__ = "support@audiopod.ai"
|
|
53
|
-
__license__ = "MIT"
|
|
18
|
+
# Alias for consistency with documentation and Node.js SDK
|
|
19
|
+
AudioPod = Client
|
|
54
20
|
|
|
55
|
-
# Public API
|
|
56
21
|
__all__ = [
|
|
57
|
-
# Main clients
|
|
58
22
|
"Client",
|
|
59
23
|
"AsyncClient",
|
|
60
|
-
|
|
61
|
-
# Exceptions
|
|
24
|
+
"AudioPod", # Alias for Client
|
|
62
25
|
"AudioPodError",
|
|
63
|
-
"AuthenticationError",
|
|
64
|
-
"APIError",
|
|
26
|
+
"AuthenticationError",
|
|
27
|
+
"APIError",
|
|
65
28
|
"RateLimitError",
|
|
66
29
|
"ValidationError",
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
# Models
|
|
70
|
-
"Job",
|
|
71
|
-
"VoiceProfile",
|
|
72
|
-
"TranscriptionResult",
|
|
73
|
-
"MusicGenerationResult",
|
|
74
|
-
"TranslationResult",
|
|
30
|
+
"InsufficientBalanceError",
|
|
31
|
+
"__version__",
|
|
75
32
|
]
|
|
76
33
|
|
|
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,18 @@ 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,
|
|
27
|
+
VideoService,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
logger = logging.getLogger(__name__)
|
|
@@ -32,7 +32,7 @@ logger = logging.getLogger(__name__)
|
|
|
32
32
|
|
|
33
33
|
class BaseClient:
|
|
34
34
|
"""Base client with common functionality"""
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
def __init__(
|
|
37
37
|
self,
|
|
38
38
|
api_key: Optional[str] = None,
|
|
@@ -40,296 +40,273 @@ class BaseClient:
|
|
|
40
40
|
timeout: int = 30,
|
|
41
41
|
max_retries: int = 3,
|
|
42
42
|
verify_ssl: bool = True,
|
|
43
|
-
debug: bool = False
|
|
43
|
+
debug: bool = False,
|
|
44
44
|
):
|
|
45
45
|
"""
|
|
46
|
-
Initialize the AudioPod API client
|
|
47
|
-
|
|
46
|
+
Initialize the AudioPod API client.
|
|
47
|
+
|
|
48
48
|
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
|
|
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
55
|
"""
|
|
56
|
-
# Set up logging
|
|
57
56
|
if debug:
|
|
58
57
|
logging.basicConfig(level=logging.DEBUG)
|
|
59
|
-
|
|
60
|
-
# Get API key
|
|
58
|
+
|
|
61
59
|
self.api_key = api_key or os.getenv("AUDIOPOD_API_KEY")
|
|
62
60
|
if not self.api_key:
|
|
63
61
|
raise AuthenticationError(
|
|
64
|
-
"API key
|
|
62
|
+
"API key required. Pass api_key or set AUDIOPOD_API_KEY environment variable."
|
|
65
63
|
)
|
|
66
|
-
|
|
67
|
-
# Validate API key format
|
|
64
|
+
|
|
68
65
|
if not self.api_key.startswith("ap_"):
|
|
69
|
-
raise AuthenticationError("Invalid API key format.
|
|
70
|
-
|
|
71
|
-
# Configuration
|
|
66
|
+
raise AuthenticationError("Invalid API key format. Keys start with 'ap_'")
|
|
67
|
+
|
|
72
68
|
self.config = ClientConfig(
|
|
73
69
|
base_url=base_url or "https://api.audiopod.ai",
|
|
74
70
|
timeout=timeout,
|
|
75
71
|
max_retries=max_retries,
|
|
76
72
|
verify_ssl=verify_ssl,
|
|
77
|
-
debug=debug
|
|
73
|
+
debug=debug,
|
|
78
74
|
)
|
|
79
|
-
|
|
75
|
+
|
|
80
76
|
def _get_headers(self) -> Dict[str, str]:
|
|
81
|
-
"""Get standard headers for API requests"""
|
|
82
77
|
return {
|
|
83
78
|
"Authorization": f"Bearer {self.api_key}",
|
|
79
|
+
"X-API-Key": self.api_key,
|
|
84
80
|
"Content-Type": "application/json",
|
|
85
81
|
"User-Agent": f"audiopod-python/{self.config.version}",
|
|
86
|
-
"Accept": "application/json"
|
|
82
|
+
"Accept": "application/json",
|
|
87
83
|
}
|
|
88
|
-
|
|
84
|
+
|
|
89
85
|
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
|
|
90
|
-
"""Handle API response and raise appropriate exceptions"""
|
|
91
86
|
try:
|
|
92
87
|
response.raise_for_status()
|
|
88
|
+
if response.status_code == 204:
|
|
89
|
+
return {}
|
|
93
90
|
return response.json()
|
|
94
91
|
except requests.exceptions.HTTPError as e:
|
|
95
92
|
if response.status_code == 401:
|
|
96
|
-
raise AuthenticationError("Invalid API key
|
|
93
|
+
raise AuthenticationError("Invalid API key")
|
|
94
|
+
elif response.status_code == 402:
|
|
95
|
+
try:
|
|
96
|
+
data = response.json()
|
|
97
|
+
raise InsufficientBalanceError(
|
|
98
|
+
data.get("message", "Insufficient balance"),
|
|
99
|
+
required_cents=data.get("required_cents"),
|
|
100
|
+
available_cents=data.get("available_cents"),
|
|
101
|
+
)
|
|
102
|
+
except (ValueError, KeyError):
|
|
103
|
+
raise InsufficientBalanceError("Insufficient wallet balance")
|
|
97
104
|
elif response.status_code == 429:
|
|
98
|
-
raise RateLimitError("Rate limit exceeded
|
|
99
|
-
|
|
105
|
+
raise RateLimitError("Rate limit exceeded")
|
|
106
|
+
else:
|
|
100
107
|
try:
|
|
101
108
|
error_data = response.json()
|
|
102
109
|
message = error_data.get("detail", str(e))
|
|
103
110
|
except:
|
|
104
111
|
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}")
|
|
112
|
+
raise APIError(f"API error: {message}", status_code=response.status_code)
|
|
110
113
|
|
|
111
114
|
|
|
112
115
|
class Client(BaseClient):
|
|
113
116
|
"""
|
|
114
|
-
Synchronous AudioPod API Client
|
|
115
|
-
|
|
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"]
|
|
133
|
+
)
|
|
134
|
+
```
|
|
117
135
|
"""
|
|
118
|
-
|
|
136
|
+
|
|
119
137
|
def __init__(self, **kwargs):
|
|
120
138
|
super().__init__(**kwargs)
|
|
121
|
-
|
|
122
|
-
# Configure session with retries
|
|
139
|
+
|
|
123
140
|
self.session = requests.Session()
|
|
124
141
|
retry_strategy = Retry(
|
|
125
142
|
total=self.config.max_retries,
|
|
126
143
|
status_forcelist=[429, 500, 502, 503, 504],
|
|
127
|
-
|
|
128
|
-
backoff_factor=1
|
|
144
|
+
allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "DELETE"],
|
|
145
|
+
backoff_factor=1,
|
|
129
146
|
)
|
|
130
147
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
131
148
|
self.session.mount("http://", adapter)
|
|
132
149
|
self.session.mount("https://", adapter)
|
|
133
|
-
|
|
134
|
-
#
|
|
150
|
+
|
|
151
|
+
# Services
|
|
135
152
|
self.voice = VoiceService(self)
|
|
136
153
|
self.music = MusicService(self)
|
|
137
154
|
self.transcription = TranscriptionService(self)
|
|
138
155
|
self.translation = TranslationService(self)
|
|
139
156
|
self.speaker = SpeakerService(self)
|
|
140
157
|
self.denoiser = DenoiserService(self)
|
|
141
|
-
self.karaoke = KaraokeService(self)
|
|
142
158
|
self.credits = CreditService(self)
|
|
143
159
|
self.stem_extraction = StemExtractionService(self)
|
|
144
|
-
|
|
160
|
+
self.stems = self.stem_extraction # Alias for consistency with Node.js SDK
|
|
161
|
+
self.wallet = WalletService(self)
|
|
162
|
+
self.video = VideoService(self)
|
|
163
|
+
|
|
145
164
|
def request(
|
|
146
165
|
self,
|
|
147
166
|
method: str,
|
|
148
167
|
endpoint: str,
|
|
149
168
|
data: Optional[Dict[str, Any]] = None,
|
|
169
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
150
170
|
files: Optional[Dict[str, Any]] = None,
|
|
151
171
|
params: Optional[Dict[str, Any]] = None,
|
|
152
|
-
**kwargs
|
|
153
172
|
) -> 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
|
-
"""
|
|
173
|
+
"""Make API request."""
|
|
168
174
|
url = urljoin(self.config.base_url, endpoint)
|
|
169
175
|
headers = self._get_headers()
|
|
170
|
-
|
|
171
|
-
# Handle file uploads (don't set Content-Type for multipart)
|
|
176
|
+
|
|
172
177
|
if files:
|
|
173
178
|
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
|
-
|
|
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,
|
|
190
|
+
)
|
|
191
|
+
return self._handle_response(response)
|
|
192
|
+
|
|
193
193
|
def get_user_info(self) -> Dict[str, Any]:
|
|
194
|
-
"""Get current user information"""
|
|
194
|
+
"""Get current user information."""
|
|
195
195
|
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
|
-
|
|
196
|
+
|
|
201
197
|
def close(self):
|
|
202
|
-
"""Close
|
|
198
|
+
"""Close client session."""
|
|
203
199
|
self.session.close()
|
|
204
|
-
|
|
200
|
+
|
|
205
201
|
def __enter__(self):
|
|
206
202
|
return self
|
|
207
|
-
|
|
203
|
+
|
|
208
204
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
209
205
|
self.close()
|
|
210
206
|
|
|
211
207
|
|
|
212
208
|
class AsyncClient(BaseClient):
|
|
213
209
|
"""
|
|
214
|
-
Asynchronous AudioPod API Client
|
|
215
|
-
|
|
216
|
-
|
|
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
|
+
```
|
|
217
224
|
"""
|
|
218
|
-
|
|
225
|
+
|
|
219
226
|
def __init__(self, **kwargs):
|
|
220
227
|
super().__init__(**kwargs)
|
|
221
228
|
self._session: Optional[aiohttp.ClientSession] = None
|
|
222
|
-
|
|
223
|
-
#
|
|
229
|
+
|
|
230
|
+
# Services
|
|
224
231
|
self.voice = VoiceService(self, async_mode=True)
|
|
225
232
|
self.music = MusicService(self, async_mode=True)
|
|
226
233
|
self.transcription = TranscriptionService(self, async_mode=True)
|
|
227
234
|
self.translation = TranslationService(self, async_mode=True)
|
|
228
235
|
self.speaker = SpeakerService(self, async_mode=True)
|
|
229
236
|
self.denoiser = DenoiserService(self, async_mode=True)
|
|
230
|
-
self.karaoke = KaraokeService(self, async_mode=True)
|
|
231
237
|
self.credits = CreditService(self, async_mode=True)
|
|
232
238
|
self.stem_extraction = StemExtractionService(self, async_mode=True)
|
|
233
|
-
|
|
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
|
+
|
|
234
243
|
@property
|
|
235
244
|
def session(self) -> aiohttp.ClientSession:
|
|
236
|
-
"""Get or create aiohttp session"""
|
|
237
245
|
if self._session is None or self._session.closed:
|
|
238
246
|
timeout = aiohttp.ClientTimeout(total=self.config.timeout)
|
|
239
|
-
connector = aiohttp.TCPConnector(
|
|
247
|
+
connector = aiohttp.TCPConnector(ssl=self.config.verify_ssl)
|
|
240
248
|
self._session = aiohttp.ClientSession(
|
|
241
|
-
timeout=timeout,
|
|
242
|
-
connector=connector,
|
|
243
|
-
headers=self._get_headers()
|
|
249
|
+
timeout=timeout, connector=connector, headers=self._get_headers()
|
|
244
250
|
)
|
|
245
251
|
return self._session
|
|
246
|
-
|
|
252
|
+
|
|
247
253
|
async def request(
|
|
248
254
|
self,
|
|
249
255
|
method: str,
|
|
250
256
|
endpoint: str,
|
|
251
257
|
data: Optional[Dict[str, Any]] = None,
|
|
258
|
+
json_data: Optional[Dict[str, Any]] = None,
|
|
252
259
|
files: Optional[Dict[str, Any]] = None,
|
|
253
260
|
params: Optional[Dict[str, Any]] = None,
|
|
254
|
-
**kwargs
|
|
255
261
|
) -> 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
|
-
"""
|
|
262
|
+
"""Make async API request."""
|
|
270
263
|
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
|
-
|
|
264
|
+
|
|
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)
|
|
273
|
+
|
|
296
274
|
async def _handle_async_response(self, response: aiohttp.ClientResponse) -> Dict[str, Any]:
|
|
297
|
-
|
|
275
|
+
if response.status == 204:
|
|
276
|
+
return {}
|
|
298
277
|
try:
|
|
299
278
|
response.raise_for_status()
|
|
300
279
|
return await response.json()
|
|
301
280
|
except aiohttp.ClientResponseError as e:
|
|
302
281
|
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:
|
|
282
|
+
raise AuthenticationError("Invalid API key")
|
|
283
|
+
elif response.status == 402:
|
|
307
284
|
try:
|
|
308
|
-
|
|
309
|
-
|
|
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
|
+
)
|
|
310
291
|
except:
|
|
311
|
-
|
|
312
|
-
|
|
292
|
+
raise InsufficientBalanceError("Insufficient wallet balance")
|
|
293
|
+
elif response.status == 429:
|
|
294
|
+
raise RateLimitError("Rate limit exceeded")
|
|
313
295
|
else:
|
|
314
|
-
raise APIError(f"
|
|
315
|
-
|
|
316
|
-
raise APIError(f"Request failed: {e}")
|
|
317
|
-
|
|
296
|
+
raise APIError(f"API error: {e}", status_code=response.status)
|
|
297
|
+
|
|
318
298
|
async def get_user_info(self) -> Dict[str, Any]:
|
|
319
|
-
"""Get current user information"""
|
|
299
|
+
"""Get current user information."""
|
|
320
300
|
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
|
-
|
|
301
|
+
|
|
326
302
|
async def close(self):
|
|
327
|
-
"""Close
|
|
303
|
+
"""Close async client session."""
|
|
328
304
|
if self._session and not self._session.closed:
|
|
329
305
|
await self._session.close()
|
|
330
|
-
|
|
306
|
+
|
|
331
307
|
async def __aenter__(self):
|
|
332
308
|
return self
|
|
333
|
-
|
|
309
|
+
|
|
334
310
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
335
311
|
await self.close()
|
|
312
|
+
|
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
|
+
|