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 CHANGED
@@ -1,83 +1,33 @@
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.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
- ProcessingError
41
- )
42
- from .models import (
43
- Job,
44
- VoiceProfile,
45
- TranscriptionResult,
46
- MusicGenerationResult,
47
- TranslationResult
15
+ InsufficientBalanceError,
48
16
  )
49
17
 
50
- __version__ = "1.2.0"
51
- __author__ = "AudioPod AI"
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
- "ProcessingError",
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, Union
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, 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
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 is required. Pass it as 'api_key' parameter or set AUDIOPOD_API_KEY environment variable."
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. AudioPod API keys start with 'ap_'")
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 or authentication failed")
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. Please try again later.")
99
- elif response.status_code >= 400:
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 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}")
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
- Provides access to all AudioPod services through a simple Python interface.
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
- method_whitelist=["HEAD", "GET", "OPTIONS", "POST"],
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
- # Initialize services
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
- 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
-
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 the client session"""
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
- Provides async/await support for better performance in async applications.
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
- # Initialize async services
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(verify_ssl=self.config.verify_ssl)
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
- 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
-
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
- """Handle async API response and raise appropriate exceptions"""
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 or authentication failed")
304
- elif response.status == 429:
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
- error_data = await response.json()
309
- message = error_data.get("detail", str(e))
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
- message = str(e)
312
- raise APIError(f"API request failed: {message}", status_code=response.status)
292
+ raise InsufficientBalanceError("Insufficient wallet balance")
293
+ elif response.status == 429:
294
+ raise RateLimitError("Rate limit exceeded")
313
295
  else:
314
- raise APIError(f"Unexpected HTTP error: {e}")
315
- except aiohttp.ClientError as e:
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 the async client session"""
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 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
+