audiopod 1.2.0__tar.gz → 1.4.0__tar.gz

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.
Files changed (53) hide show
  1. audiopod-1.4.0/PKG-INFO +206 -0
  2. audiopod-1.4.0/README.md +173 -0
  3. audiopod-1.4.0/audiopod/__init__.py +29 -0
  4. audiopod-1.4.0/audiopod/client.py +306 -0
  5. audiopod-1.4.0/audiopod/config.py +17 -0
  6. audiopod-1.4.0/audiopod/exceptions.py +41 -0
  7. {audiopod-1.2.0 → audiopod-1.4.0}/audiopod/services/__init__.py +8 -6
  8. audiopod-1.4.0/audiopod/services/base.py +69 -0
  9. audiopod-1.4.0/audiopod/services/credits.py +42 -0
  10. audiopod-1.4.0/audiopod/services/denoiser.py +131 -0
  11. audiopod-1.4.0/audiopod/services/music.py +217 -0
  12. audiopod-1.4.0/audiopod/services/speaker.py +134 -0
  13. audiopod-1.4.0/audiopod/services/stem_extraction.py +168 -0
  14. audiopod-1.4.0/audiopod/services/transcription.py +187 -0
  15. audiopod-1.4.0/audiopod/services/translation.py +135 -0
  16. audiopod-1.4.0/audiopod/services/voice.py +187 -0
  17. audiopod-1.4.0/audiopod/services/wallet.py +235 -0
  18. audiopod-1.4.0/audiopod.egg-info/PKG-INFO +206 -0
  19. {audiopod-1.2.0 → audiopod-1.4.0}/audiopod.egg-info/SOURCES.txt +6 -12
  20. audiopod-1.4.0/audiopod.egg-info/dependency_links.txt +1 -0
  21. audiopod-1.4.0/audiopod.egg-info/requires.txt +8 -0
  22. audiopod-1.4.0/audiopod.egg-info/top_level.txt +1 -0
  23. audiopod-1.4.0/pyproject.toml +59 -0
  24. audiopod-1.2.0/CHANGELOG.md +0 -279
  25. audiopod-1.2.0/MANIFEST.in +0 -25
  26. audiopod-1.2.0/PKG-INFO +0 -454
  27. audiopod-1.2.0/README.md +0 -402
  28. audiopod-1.2.0/audiopod/__init__.py +0 -83
  29. audiopod-1.2.0/audiopod/cli.py +0 -285
  30. audiopod-1.2.0/audiopod/client.py +0 -335
  31. audiopod-1.2.0/audiopod/config.py +0 -63
  32. audiopod-1.2.0/audiopod/exceptions.py +0 -96
  33. audiopod-1.2.0/audiopod/models.py +0 -250
  34. audiopod-1.2.0/audiopod/py.typed +0 -2
  35. audiopod-1.2.0/audiopod/services/base.py +0 -213
  36. audiopod-1.2.0/audiopod/services/credits.py +0 -46
  37. audiopod-1.2.0/audiopod/services/denoiser.py +0 -51
  38. audiopod-1.2.0/audiopod/services/karaoke.py +0 -61
  39. audiopod-1.2.0/audiopod/services/music.py +0 -522
  40. audiopod-1.2.0/audiopod/services/speaker.py +0 -53
  41. audiopod-1.2.0/audiopod/services/stem_extraction.py +0 -180
  42. audiopod-1.2.0/audiopod/services/transcription.py +0 -212
  43. audiopod-1.2.0/audiopod/services/translation.py +0 -196
  44. audiopod-1.2.0/audiopod/services/voice.py +0 -470
  45. audiopod-1.2.0/examples/README.md +0 -81
  46. audiopod-1.2.0/examples/basic_usage.py +0 -536
  47. audiopod-1.2.0/pyproject.toml +0 -146
  48. audiopod-1.2.0/requirements.txt +0 -7
  49. audiopod-1.2.0/setup.py +0 -93
  50. audiopod-1.2.0/tests/test_end_to_end_integration.py +0 -617
  51. audiopod-1.2.0/tests/test_sdk_api_compatibility.py +0 -892
  52. {audiopod-1.2.0 → audiopod-1.4.0}/LICENSE +0 -0
  53. {audiopod-1.2.0 → audiopod-1.4.0}/setup.cfg +0 -0
@@ -0,0 +1,206 @@
1
+ Metadata-Version: 2.4
2
+ Name: audiopod
3
+ Version: 1.4.0
4
+ Summary: AudioPod SDK for Python - Professional Audio Processing powered by AI
5
+ Author-email: AudioPod AI <support@audiopod.ai>
6
+ License: MIT
7
+ Project-URL: Homepage, https://audiopod.ai
8
+ Project-URL: Documentation, https://docs.audiopod.ai
9
+ Project-URL: Repository, https://github.com/AudiopodAI/audiopod-python
10
+ Keywords: audiopod,audio,ai,voice,cloning,transcription,stem-separation,music,sdk
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Multimedia :: Sound/Audio
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: requests>=2.28.0
26
+ Requires-Dist: aiohttp>=3.8.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
29
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
30
+ Requires-Dist: black>=23.0.0; extra == "dev"
31
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # AudioPod Python SDK
35
+
36
+ Official Python SDK for [AudioPod AI](https://audiopod.ai) - Professional Audio Processing powered by AI.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install audiopod
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ```python
47
+ from audiopod import Client
48
+
49
+ # Initialize client
50
+ client = Client(api_key="ap_your_api_key")
51
+ # Or set AUDIOPOD_API_KEY environment variable
52
+
53
+ # Check wallet balance
54
+ balance = client.wallet.get_balance()
55
+ print(f"Balance: {balance['balance_usd']}")
56
+
57
+ # Extract stems from audio
58
+ job = client.stem_extraction.extract_stems(
59
+ audio_file="song.mp3",
60
+ stem_types=["vocals", "drums", "bass", "other"],
61
+ wait_for_completion=True
62
+ )
63
+
64
+ # Download stems
65
+ for stem, url in job["download_urls"].items():
66
+ print(f"{stem}: {url}")
67
+ ```
68
+
69
+ ## Services
70
+
71
+ ### Wallet (API Billing)
72
+
73
+ ```python
74
+ # Get balance
75
+ balance = client.wallet.get_balance()
76
+
77
+ # Get pricing
78
+ pricing = client.wallet.get_pricing()
79
+
80
+ # Estimate cost
81
+ estimate = client.wallet.estimate_cost("stem_extraction", duration_seconds=300)
82
+
83
+ # Top up wallet
84
+ checkout = client.wallet.create_topup_checkout(amount_cents=2500) # $25
85
+ print(f"Pay at: {checkout['url']}")
86
+
87
+ # Usage history
88
+ usage = client.wallet.get_usage_history(page=1, limit=50)
89
+ ```
90
+
91
+ ### Stem Extraction
92
+
93
+ ```python
94
+ # Extract all stems
95
+ job = client.stem_extraction.extract_stems(
96
+ audio_file="song.mp3",
97
+ stem_types=["vocals", "drums", "bass", "other"],
98
+ wait_for_completion=True
99
+ )
100
+
101
+ # From URL
102
+ job = client.stem_extraction.extract_stems(
103
+ url="https://example.com/song.mp3",
104
+ stem_types=["vocals", "other"]
105
+ )
106
+
107
+ # Check status
108
+ status = client.stem_extraction.get_job(job_id=123)
109
+ ```
110
+
111
+ ### Transcription
112
+
113
+ ```python
114
+ # Transcribe audio
115
+ result = client.transcription.transcribe(
116
+ audio_file="podcast.mp3",
117
+ speaker_diarization=True,
118
+ wait_for_completion=True
119
+ )
120
+ print(result["transcript"])
121
+ ```
122
+
123
+ ### Voice Cloning & TTS
124
+
125
+ ```python
126
+ # List voices
127
+ voices = client.voice.list_voices()
128
+
129
+ # Generate speech
130
+ audio = client.voice.generate_speech(
131
+ text="Hello, world!",
132
+ voice_id=123,
133
+ wait_for_completion=True
134
+ )
135
+ ```
136
+
137
+ ### Music Generation
138
+
139
+ ```python
140
+ # Generate music
141
+ result = client.music.generate(
142
+ prompt="upbeat electronic dance music",
143
+ duration=30,
144
+ wait_for_completion=True
145
+ )
146
+ ```
147
+
148
+ ### Noise Reduction
149
+
150
+ ```python
151
+ # Denoise audio
152
+ result = client.denoiser.denoise(
153
+ audio_file="noisy.mp3",
154
+ mode="studio",
155
+ wait_for_completion=True
156
+ )
157
+ ```
158
+
159
+ ## Async Support
160
+
161
+ ```python
162
+ import asyncio
163
+ from audiopod import AsyncClient
164
+
165
+ async def main():
166
+ async with AsyncClient() as client:
167
+ balance = await client.wallet.get_balance()
168
+ print(f"Balance: {balance['balance_usd']}")
169
+
170
+ asyncio.run(main())
171
+ ```
172
+
173
+ ## Error Handling
174
+
175
+ ```python
176
+ from audiopod import Client
177
+ from audiopod.exceptions import (
178
+ AuthenticationError,
179
+ InsufficientBalanceError,
180
+ RateLimitError,
181
+ APIError
182
+ )
183
+
184
+ try:
185
+ client = Client()
186
+ job = client.stem_extraction.extract_stems(audio_file="song.mp3")
187
+ except AuthenticationError:
188
+ print("Invalid API key")
189
+ except InsufficientBalanceError as e:
190
+ print(f"Need to top up: required {e.required_cents} cents")
191
+ except RateLimitError:
192
+ print("Rate limit exceeded, try again later")
193
+ except APIError as e:
194
+ print(f"API error ({e.status_code}): {e}")
195
+ ```
196
+
197
+ ## Documentation
198
+
199
+ - [API Reference](https://docs.audiopod.ai)
200
+ - [API Wallet](https://docs.audiopod.ai/account/api-wallet)
201
+ - [Stem Separation](https://docs.audiopod.ai/api-reference/stem-splitter)
202
+
203
+ ## License
204
+
205
+ MIT
206
+
@@ -0,0 +1,173 @@
1
+ # AudioPod Python SDK
2
+
3
+ Official Python SDK for [AudioPod AI](https://audiopod.ai) - Professional Audio Processing powered by AI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install audiopod
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from audiopod import Client
15
+
16
+ # Initialize client
17
+ client = Client(api_key="ap_your_api_key")
18
+ # Or set AUDIOPOD_API_KEY environment variable
19
+
20
+ # Check wallet balance
21
+ balance = client.wallet.get_balance()
22
+ print(f"Balance: {balance['balance_usd']}")
23
+
24
+ # Extract stems from audio
25
+ job = client.stem_extraction.extract_stems(
26
+ audio_file="song.mp3",
27
+ stem_types=["vocals", "drums", "bass", "other"],
28
+ wait_for_completion=True
29
+ )
30
+
31
+ # Download stems
32
+ for stem, url in job["download_urls"].items():
33
+ print(f"{stem}: {url}")
34
+ ```
35
+
36
+ ## Services
37
+
38
+ ### Wallet (API Billing)
39
+
40
+ ```python
41
+ # Get balance
42
+ balance = client.wallet.get_balance()
43
+
44
+ # Get pricing
45
+ pricing = client.wallet.get_pricing()
46
+
47
+ # Estimate cost
48
+ estimate = client.wallet.estimate_cost("stem_extraction", duration_seconds=300)
49
+
50
+ # Top up wallet
51
+ checkout = client.wallet.create_topup_checkout(amount_cents=2500) # $25
52
+ print(f"Pay at: {checkout['url']}")
53
+
54
+ # Usage history
55
+ usage = client.wallet.get_usage_history(page=1, limit=50)
56
+ ```
57
+
58
+ ### Stem Extraction
59
+
60
+ ```python
61
+ # Extract all stems
62
+ job = client.stem_extraction.extract_stems(
63
+ audio_file="song.mp3",
64
+ stem_types=["vocals", "drums", "bass", "other"],
65
+ wait_for_completion=True
66
+ )
67
+
68
+ # From URL
69
+ job = client.stem_extraction.extract_stems(
70
+ url="https://example.com/song.mp3",
71
+ stem_types=["vocals", "other"]
72
+ )
73
+
74
+ # Check status
75
+ status = client.stem_extraction.get_job(job_id=123)
76
+ ```
77
+
78
+ ### Transcription
79
+
80
+ ```python
81
+ # Transcribe audio
82
+ result = client.transcription.transcribe(
83
+ audio_file="podcast.mp3",
84
+ speaker_diarization=True,
85
+ wait_for_completion=True
86
+ )
87
+ print(result["transcript"])
88
+ ```
89
+
90
+ ### Voice Cloning & TTS
91
+
92
+ ```python
93
+ # List voices
94
+ voices = client.voice.list_voices()
95
+
96
+ # Generate speech
97
+ audio = client.voice.generate_speech(
98
+ text="Hello, world!",
99
+ voice_id=123,
100
+ wait_for_completion=True
101
+ )
102
+ ```
103
+
104
+ ### Music Generation
105
+
106
+ ```python
107
+ # Generate music
108
+ result = client.music.generate(
109
+ prompt="upbeat electronic dance music",
110
+ duration=30,
111
+ wait_for_completion=True
112
+ )
113
+ ```
114
+
115
+ ### Noise Reduction
116
+
117
+ ```python
118
+ # Denoise audio
119
+ result = client.denoiser.denoise(
120
+ audio_file="noisy.mp3",
121
+ mode="studio",
122
+ wait_for_completion=True
123
+ )
124
+ ```
125
+
126
+ ## Async Support
127
+
128
+ ```python
129
+ import asyncio
130
+ from audiopod import AsyncClient
131
+
132
+ async def main():
133
+ async with AsyncClient() as client:
134
+ balance = await client.wallet.get_balance()
135
+ print(f"Balance: {balance['balance_usd']}")
136
+
137
+ asyncio.run(main())
138
+ ```
139
+
140
+ ## Error Handling
141
+
142
+ ```python
143
+ from audiopod import Client
144
+ from audiopod.exceptions import (
145
+ AuthenticationError,
146
+ InsufficientBalanceError,
147
+ RateLimitError,
148
+ APIError
149
+ )
150
+
151
+ try:
152
+ client = Client()
153
+ job = client.stem_extraction.extract_stems(audio_file="song.mp3")
154
+ except AuthenticationError:
155
+ print("Invalid API key")
156
+ except InsufficientBalanceError as e:
157
+ print(f"Need to top up: required {e.required_cents} cents")
158
+ except RateLimitError:
159
+ print("Rate limit exceeded, try again later")
160
+ except APIError as e:
161
+ print(f"API error ({e.status_code}): {e}")
162
+ ```
163
+
164
+ ## Documentation
165
+
166
+ - [API Reference](https://docs.audiopod.ai)
167
+ - [API Wallet](https://docs.audiopod.ai/account/api-wallet)
168
+ - [Stem Separation](https://docs.audiopod.ai/api-reference/stem-splitter)
169
+
170
+ ## License
171
+
172
+ MIT
173
+
@@ -0,0 +1,29 @@
1
+ """
2
+ AudioPod SDK for Python
3
+ Professional Audio Processing powered by AI
4
+ """
5
+
6
+ __version__ = "1.3.0"
7
+
8
+ from .client import Client, AsyncClient
9
+ from .exceptions import (
10
+ AudioPodError,
11
+ AuthenticationError,
12
+ APIError,
13
+ RateLimitError,
14
+ ValidationError,
15
+ InsufficientBalanceError,
16
+ )
17
+
18
+ __all__ = [
19
+ "Client",
20
+ "AsyncClient",
21
+ "AudioPodError",
22
+ "AuthenticationError",
23
+ "APIError",
24
+ "RateLimitError",
25
+ "ValidationError",
26
+ "InsufficientBalanceError",
27
+ "__version__",
28
+ ]
29
+
@@ -0,0 +1,306 @@
1
+ """
2
+ AudioPod API Client
3
+ """
4
+
5
+ import os
6
+ import logging
7
+ from typing import Optional, Dict, Any
8
+ from urllib.parse import urljoin
9
+
10
+ import requests
11
+ import aiohttp
12
+ from requests.adapters import HTTPAdapter
13
+ from urllib3.util.retry import Retry
14
+
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
+ )
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class BaseClient:
33
+ """Base client with common functionality"""
34
+
35
+ def __init__(
36
+ self,
37
+ api_key: Optional[str] = None,
38
+ base_url: Optional[str] = None,
39
+ timeout: int = 30,
40
+ max_retries: int = 3,
41
+ verify_ssl: bool = True,
42
+ debug: bool = False,
43
+ ):
44
+ """
45
+ Initialize the AudioPod API client.
46
+
47
+ Args:
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.
54
+ """
55
+ if debug:
56
+ logging.basicConfig(level=logging.DEBUG)
57
+
58
+ self.api_key = api_key or os.getenv("AUDIOPOD_API_KEY")
59
+ if not self.api_key:
60
+ raise AuthenticationError(
61
+ "API key required. Pass api_key or set AUDIOPOD_API_KEY environment variable."
62
+ )
63
+
64
+ if not self.api_key.startswith("ap_"):
65
+ raise AuthenticationError("Invalid API key format. Keys start with 'ap_'")
66
+
67
+ self.config = ClientConfig(
68
+ base_url=base_url or "https://api.audiopod.ai",
69
+ timeout=timeout,
70
+ max_retries=max_retries,
71
+ verify_ssl=verify_ssl,
72
+ debug=debug,
73
+ )
74
+
75
+ def _get_headers(self) -> Dict[str, str]:
76
+ return {
77
+ "Authorization": f"Bearer {self.api_key}",
78
+ "Content-Type": "application/json",
79
+ "User-Agent": f"audiopod-python/{self.config.version}",
80
+ "Accept": "application/json",
81
+ }
82
+
83
+ def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
84
+ try:
85
+ response.raise_for_status()
86
+ if response.status_code == 204:
87
+ return {}
88
+ return response.json()
89
+ except requests.exceptions.HTTPError as e:
90
+ if response.status_code == 401:
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")
102
+ elif response.status_code == 429:
103
+ raise RateLimitError("Rate limit exceeded")
104
+ else:
105
+ try:
106
+ error_data = response.json()
107
+ message = error_data.get("detail", str(e))
108
+ except:
109
+ message = str(e)
110
+ raise APIError(f"API error: {message}", status_code=response.status_code)
111
+
112
+
113
+ class Client(BaseClient):
114
+ """
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
+ ```
133
+ """
134
+
135
+ def __init__(self, **kwargs):
136
+ super().__init__(**kwargs)
137
+
138
+ self.session = requests.Session()
139
+ retry_strategy = Retry(
140
+ total=self.config.max_retries,
141
+ status_forcelist=[429, 500, 502, 503, 504],
142
+ allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "DELETE"],
143
+ backoff_factor=1,
144
+ )
145
+ adapter = HTTPAdapter(max_retries=retry_strategy)
146
+ self.session.mount("http://", adapter)
147
+ self.session.mount("https://", adapter)
148
+
149
+ # Services
150
+ self.voice = VoiceService(self)
151
+ self.music = MusicService(self)
152
+ self.transcription = TranscriptionService(self)
153
+ self.translation = TranslationService(self)
154
+ self.speaker = SpeakerService(self)
155
+ self.denoiser = DenoiserService(self)
156
+ self.credits = CreditService(self)
157
+ self.stem_extraction = StemExtractionService(self)
158
+ self.wallet = WalletService(self)
159
+
160
+ def request(
161
+ self,
162
+ method: str,
163
+ endpoint: str,
164
+ data: Optional[Dict[str, Any]] = None,
165
+ json_data: Optional[Dict[str, Any]] = None,
166
+ files: Optional[Dict[str, Any]] = None,
167
+ params: Optional[Dict[str, Any]] = None,
168
+ ) -> Dict[str, Any]:
169
+ """Make API request."""
170
+ url = urljoin(self.config.base_url, endpoint)
171
+ headers = self._get_headers()
172
+
173
+ if files:
174
+ headers.pop("Content-Type", None)
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
+
189
+ def get_user_info(self) -> Dict[str, Any]:
190
+ """Get current user information."""
191
+ return self.request("GET", "/api/v1/auth/me")
192
+
193
+ def close(self):
194
+ """Close client session."""
195
+ self.session.close()
196
+
197
+ def __enter__(self):
198
+ return self
199
+
200
+ def __exit__(self, exc_type, exc_val, exc_tb):
201
+ self.close()
202
+
203
+
204
+ class AsyncClient(BaseClient):
205
+ """
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
+ ```
220
+ """
221
+
222
+ def __init__(self, **kwargs):
223
+ super().__init__(**kwargs)
224
+ self._session: Optional[aiohttp.ClientSession] = None
225
+
226
+ # Services
227
+ self.voice = VoiceService(self, async_mode=True)
228
+ self.music = MusicService(self, async_mode=True)
229
+ self.transcription = TranscriptionService(self, async_mode=True)
230
+ self.translation = TranslationService(self, async_mode=True)
231
+ self.speaker = SpeakerService(self, async_mode=True)
232
+ self.denoiser = DenoiserService(self, async_mode=True)
233
+ self.credits = CreditService(self, async_mode=True)
234
+ self.stem_extraction = StemExtractionService(self, async_mode=True)
235
+ self.wallet = WalletService(self, async_mode=True)
236
+
237
+ @property
238
+ def session(self) -> aiohttp.ClientSession:
239
+ if self._session is None or self._session.closed:
240
+ timeout = aiohttp.ClientTimeout(total=self.config.timeout)
241
+ connector = aiohttp.TCPConnector(ssl=self.config.verify_ssl)
242
+ self._session = aiohttp.ClientSession(
243
+ timeout=timeout, connector=connector, headers=self._get_headers()
244
+ )
245
+ return self._session
246
+
247
+ async def request(
248
+ self,
249
+ method: str,
250
+ endpoint: str,
251
+ data: Optional[Dict[str, Any]] = None,
252
+ json_data: Optional[Dict[str, Any]] = None,
253
+ files: Optional[Dict[str, Any]] = None,
254
+ params: Optional[Dict[str, Any]] = None,
255
+ ) -> Dict[str, Any]:
256
+ """Make async API request."""
257
+ url = urljoin(self.config.base_url, endpoint)
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
+
268
+ async def _handle_async_response(self, response: aiohttp.ClientResponse) -> Dict[str, Any]:
269
+ if response.status == 204:
270
+ return {}
271
+ try:
272
+ response.raise_for_status()
273
+ return await response.json()
274
+ except aiohttp.ClientResponseError as e:
275
+ if response.status == 401:
276
+ raise AuthenticationError("Invalid API key")
277
+ elif response.status == 402:
278
+ try:
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
+ )
285
+ except:
286
+ raise InsufficientBalanceError("Insufficient wallet balance")
287
+ elif response.status == 429:
288
+ raise RateLimitError("Rate limit exceeded")
289
+ else:
290
+ raise APIError(f"API error: {e}", status_code=response.status)
291
+
292
+ async def get_user_info(self) -> Dict[str, Any]:
293
+ """Get current user information."""
294
+ return await self.request("GET", "/api/v1/auth/me")
295
+
296
+ async def close(self):
297
+ """Close async client session."""
298
+ if self._session and not self._session.closed:
299
+ await self._session.close()
300
+
301
+ async def __aenter__(self):
302
+ return self
303
+
304
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
305
+ await self.close()
306
+