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 CHANGED
@@ -1,83 +1,29 @@
1
1
  """
2
- AudioPod API Client
3
- Professional Audio Processing SDK for Python
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
- ProcessingError
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
- "ProcessingError",
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, Union
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, will try to read from AUDIOPOD_API_KEY env var
50
- base_url: API base URL. Defaults to production endpoint
51
- timeout: Request timeout in seconds
52
- max_retries: Maximum number of retries for failed requests
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 is required. Pass it as 'api_key' parameter or set AUDIOPOD_API_KEY environment variable."
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. AudioPod API keys start with 'ap_'")
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 or authentication failed")
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. Please try again later.")
99
- elif response.status_code >= 400:
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 request failed: {message}", status_code=response.status_code)
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
- Provides access to all AudioPod services through a simple Python interface.
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
- method_whitelist=["HEAD", "GET", "OPTIONS", "POST"],
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
- # Initialize services
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
- try:
176
- response = self.session.request(
177
- method=method,
178
- url=url,
179
- headers=headers,
180
- json=data,
181
- files=files,
182
- params=params,
183
- timeout=self.config.timeout,
184
- verify=self.config.verify_ssl,
185
- **kwargs
186
- )
187
- return self._handle_response(response)
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 the client session"""
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
- Provides async/await support for better performance in async applications.
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
- # Initialize async services
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(verify_ssl=self.config.verify_ssl)
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
- try:
273
- if files:
274
- # Handle file uploads
275
- form_data = aiohttp.FormData()
276
- for key, value in (data or {}).items():
277
- form_data.add_field(key, str(value))
278
- for key, file_data in files.items():
279
- form_data.add_field(key, file_data)
280
- data = form_data
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
- """Handle async API response and raise appropriate exceptions"""
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 or authentication failed")
304
- elif response.status == 429:
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
- error_data = await response.json()
309
- message = error_data.get("detail", str(e))
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
- message = str(e)
312
- raise APIError(f"API request failed: {message}", status_code=response.status)
286
+ raise InsufficientBalanceError("Insufficient wallet balance")
287
+ elif response.status == 429:
288
+ raise RateLimitError("Rate limit exceeded")
313
289
  else:
314
- raise APIError(f"Unexpected HTTP error: {e}")
315
- except aiohttp.ClientError as e:
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 the async client session"""
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 Client Configuration
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
- """Configuration for AudioPod API client"""
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.0.0"
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
+