audiopod 1.5.0__py3-none-any.whl → 2.1.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.
@@ -1,287 +0,0 @@
1
- """
2
- Stem Extraction Service - Audio stem separation
3
- """
4
-
5
- from typing import List, Optional, Dict, Any, Literal
6
- from .base import BaseService
7
- from ..exceptions import ValidationError
8
-
9
-
10
- # Valid separation modes for the new API
11
- StemMode = Literal["single", "two", "four", "six", "producer", "studio", "mastering"]
12
- SingleStem = Literal["vocals", "drums", "bass", "guitar", "piano", "other", "instrumental"]
13
-
14
-
15
- class StemExtractionService(BaseService):
16
- """
17
- Service for audio stem separation.
18
-
19
- Example:
20
- ```python
21
- from audiopod import Client
22
-
23
- client = Client()
24
-
25
- # Simple mode-based extraction (recommended)
26
- result = client.stem_extraction.separate(
27
- url="https://youtube.com/watch?v=VIDEO_ID",
28
- mode="six"
29
- )
30
- for stem, url in result["download_urls"].items():
31
- print(f"{stem}: {url}")
32
-
33
- # Or extract only vocals
34
- result = client.stem_extraction.separate(
35
- file="song.mp3",
36
- mode="single",
37
- stem="vocals"
38
- )
39
- ```
40
- """
41
-
42
- def separate(
43
- self,
44
- file: Optional[str] = None,
45
- url: Optional[str] = None,
46
- mode: StemMode = "four",
47
- stem: Optional[SingleStem] = None,
48
- wait_for_completion: bool = True,
49
- timeout: int = 900,
50
- ) -> Dict[str, Any]:
51
- """
52
- Separate audio into stems using simple mode selection.
53
-
54
- Args:
55
- file: Path to local audio file
56
- url: URL of audio/video (YouTube, SoundCloud, direct link)
57
- mode: Separation mode:
58
- - "single": Extract one stem (specify stem param)
59
- - "two": Vocals + Instrumental
60
- - "four": Vocals, Drums, Bass, Other (default)
61
- - "six": Vocals, Drums, Bass, Guitar, Piano, Other
62
- - "producer": 8 stems with kick, snare, hihat
63
- - "studio": 12 stems for professional mixing
64
- - "mastering": 16 stems maximum detail
65
- stem: For mode="single", which stem to extract
66
- wait_for_completion: Wait for job to complete (default: True)
67
- timeout: Max wait time in seconds
68
-
69
- Returns:
70
- Job dict with id, status, download_urls (when completed)
71
- """
72
- if not file and not url:
73
- raise ValidationError("Provide file or url")
74
-
75
- if file and url:
76
- raise ValidationError("Provide file or url, not both")
77
-
78
- if mode == "single" and not stem:
79
- raise ValidationError(
80
- "stem parameter required for mode='single'. "
81
- "Options: vocals, drums, bass, guitar, piano, other, instrumental"
82
- )
83
-
84
- data = {"mode": mode}
85
- if stem:
86
- data["stem"] = stem
87
- if url:
88
- data["url"] = url
89
-
90
- files = self._prepare_file_upload(file, "file") if file else None
91
-
92
- if self.async_mode:
93
- return self._async_separate(data, files, wait_for_completion, timeout)
94
-
95
- response = self.client.request(
96
- "POST", "/api/v1/stem-extraction/api/extract", data=data, files=files
97
- )
98
-
99
- if wait_for_completion:
100
- return self._wait_for_stem_job(response["id"], timeout)
101
-
102
- return response
103
-
104
- async def _async_separate(
105
- self,
106
- data: Dict[str, Any],
107
- files: Optional[Dict[str, Any]],
108
- wait_for_completion: bool,
109
- timeout: int,
110
- ) -> Dict[str, Any]:
111
- response = await self.client.request(
112
- "POST", "/api/v1/stem-extraction/api/extract", data=data, files=files
113
- )
114
- if wait_for_completion:
115
- return await self._async_wait_for_stem_job(response["id"], timeout)
116
- return response
117
-
118
- def extract(
119
- self,
120
- file: Optional[str] = None,
121
- url: Optional[str] = None,
122
- mode: StemMode = "four",
123
- stem: Optional[SingleStem] = None,
124
- ) -> Dict[str, Any]:
125
- """
126
- Submit stem extraction job (returns immediately without waiting).
127
-
128
- Use wait_for_completion() to poll for results.
129
- """
130
- return self.separate(file=file, url=url, mode=mode, stem=stem, wait_for_completion=False)
131
-
132
- def wait_for_completion(self, job_id: int, timeout: int = 900) -> Dict[str, Any]:
133
- """Wait for stem extraction job to complete."""
134
- return self._wait_for_stem_job(job_id, timeout)
135
-
136
- def status(self, job_id: int) -> Dict[str, Any]:
137
- """Get stem extraction job status (alias for get_job)."""
138
- return self.get_job(job_id)
139
-
140
- def modes(self) -> Dict[str, Any]:
141
- """Get available separation modes."""
142
- if self.async_mode:
143
- return self._async_modes()
144
- return self.client.request("GET", "/api/v1/stem-extraction/modes")
145
-
146
- async def _async_modes(self) -> Dict[str, Any]:
147
- return await self.client.request("GET", "/api/v1/stem-extraction/modes")
148
-
149
- # Legacy method - kept for backward compatibility
150
- def extract_stems(
151
- self,
152
- audio_file: Optional[str] = None,
153
- url: Optional[str] = None,
154
- stem_types: Optional[List[str]] = None,
155
- model_name: str = "htdemucs",
156
- two_stems_mode: Optional[str] = None,
157
- wait_for_completion: bool = False,
158
- timeout: int = 900,
159
- ) -> Dict[str, Any]:
160
- """
161
- Extract stems from audio (legacy method).
162
-
163
- For new code, use separate() instead which uses the simpler mode-based API.
164
-
165
- Args:
166
- audio_file: Path to local audio file
167
- url: URL of audio file (alternative to audio_file)
168
- stem_types: Stems to extract (e.g., ["vocals", "drums", "bass", "other"])
169
- model_name: Model to use ("htdemucs" or "htdemucs_6s")
170
- two_stems_mode: Two-stem mode ("vocals", "drums", or "bass")
171
- wait_for_completion: Wait for job to complete
172
- timeout: Max wait time in seconds
173
-
174
- Returns:
175
- Job dict with id, status, download_urls (when completed)
176
- """
177
- if not audio_file and not url:
178
- raise ValidationError("Provide audio_file or url")
179
-
180
- if audio_file and url:
181
- raise ValidationError("Provide audio_file or url, not both")
182
-
183
- if stem_types is None:
184
- stem_types = (
185
- ["vocals", "drums", "bass", "other", "piano", "guitar"]
186
- if model_name == "htdemucs_6s"
187
- else ["vocals", "drums", "bass", "other"]
188
- )
189
-
190
- data = {"stem_types": str(stem_types), "model_name": model_name}
191
-
192
- if url:
193
- data["url"] = url
194
-
195
- if two_stems_mode:
196
- data["two_stems_mode"] = two_stems_mode
197
-
198
- files = self._prepare_file_upload(audio_file, "file") if audio_file else None
199
-
200
- if self.async_mode:
201
- return self._async_extract_stems(data, files, wait_for_completion, timeout)
202
-
203
- response = self.client.request(
204
- "POST", "/api/v1/stem-extraction/extract", data=data, files=files
205
- )
206
-
207
- if wait_for_completion:
208
- return self._wait_for_stem_job(response["id"], timeout)
209
-
210
- return response
211
-
212
- async def _async_extract_stems(
213
- self,
214
- data: Dict[str, Any],
215
- files: Optional[Dict[str, Any]],
216
- wait_for_completion: bool,
217
- timeout: int,
218
- ) -> Dict[str, Any]:
219
- response = await self.client.request(
220
- "POST", "/api/v1/stem-extraction/extract", data=data, files=files
221
- )
222
- if wait_for_completion:
223
- return await self._async_wait_for_stem_job(response["id"], timeout)
224
- return response
225
-
226
- def get_job(self, job_id: int) -> Dict[str, Any]:
227
- """Get stem extraction job status."""
228
- if self.async_mode:
229
- return self._async_get_job(job_id)
230
- return self.client.request("GET", f"/api/v1/stem-extraction/status/{job_id}")
231
-
232
- async def _async_get_job(self, job_id: int) -> Dict[str, Any]:
233
- return await self.client.request("GET", f"/api/v1/stem-extraction/status/{job_id}")
234
-
235
- def list_jobs(self, skip: int = 0, limit: int = 50) -> List[Dict[str, Any]]:
236
- """List stem extraction jobs."""
237
- if self.async_mode:
238
- return self._async_list_jobs(skip, limit)
239
- return self.client.request(
240
- "GET", "/api/v1/stem-extraction/jobs", params={"skip": skip, "limit": limit}
241
- )
242
-
243
- async def _async_list_jobs(self, skip: int, limit: int) -> List[Dict[str, Any]]:
244
- return await self.client.request(
245
- "GET", "/api/v1/stem-extraction/jobs", params={"skip": skip, "limit": limit}
246
- )
247
-
248
- def delete_job(self, job_id: int) -> Dict[str, str]:
249
- """Delete a stem extraction job."""
250
- if self.async_mode:
251
- return self._async_delete_job(job_id)
252
- return self.client.request("DELETE", f"/api/v1/stem-extraction/jobs/{job_id}")
253
-
254
- async def _async_delete_job(self, job_id: int) -> Dict[str, str]:
255
- return await self.client.request("DELETE", f"/api/v1/stem-extraction/jobs/{job_id}")
256
-
257
- def _wait_for_stem_job(self, job_id: int, timeout: int = 900) -> Dict[str, Any]:
258
- """Wait for stem job completion."""
259
- import time
260
-
261
- start = time.time()
262
- while time.time() - start < timeout:
263
- job = self.get_job(job_id)
264
- status = job.get("status", "").upper()
265
- if status == "COMPLETED":
266
- return job
267
- elif status in ["FAILED", "ERROR"]:
268
- raise Exception(f"Job failed: {job.get('error_message', 'Unknown')}")
269
- time.sleep(5)
270
- raise TimeoutError(f"Job {job_id} timed out after {timeout}s")
271
-
272
- async def _async_wait_for_stem_job(self, job_id: int, timeout: int = 900) -> Dict[str, Any]:
273
- """Async wait for stem job completion."""
274
- import asyncio
275
- import time
276
-
277
- start = time.time()
278
- while time.time() - start < timeout:
279
- job = await self.get_job(job_id)
280
- status = job.get("status", "").upper()
281
- if status == "COMPLETED":
282
- return job
283
- elif status in ["FAILED", "ERROR"]:
284
- raise Exception(f"Job failed: {job.get('error_message', 'Unknown')}")
285
- await asyncio.sleep(5)
286
- raise TimeoutError(f"Job {job_id} timed out after {timeout}s")
287
-
@@ -1,210 +0,0 @@
1
- """
2
- Transcription Service - Speech-to-text
3
-
4
- API Routes:
5
- - POST /api/v1/transcription/transcribe - Transcribe from URL
6
- - POST /api/v1/transcription/transcribe-upload - Transcribe from file upload
7
- - GET /api/v1/transcription/jobs/{id} - Get job details
8
- - GET /api/v1/transcription/jobs - List jobs
9
- - DELETE /api/v1/transcription/jobs/{id} - Delete job
10
- """
11
-
12
- from typing import Optional, Dict, Any, List
13
- from .base import BaseService
14
-
15
-
16
- class TranscriptionService(BaseService):
17
- """Service for speech-to-text transcription."""
18
-
19
- def create(
20
- self,
21
- file: Optional[str] = None,
22
- url: Optional[str] = None,
23
- language: Optional[str] = None,
24
- speaker_diarization: bool = False,
25
- wait_for_completion: bool = False,
26
- timeout: int = 600,
27
- ) -> Dict[str, Any]:
28
- """Alias for transcribe() - matches Node.js SDK."""
29
- return self.transcribe(
30
- audio_file=file,
31
- url=url,
32
- language=language,
33
- speaker_diarization=speaker_diarization,
34
- wait_for_completion=wait_for_completion,
35
- timeout=timeout,
36
- )
37
-
38
- def transcribe(
39
- self,
40
- audio_file: Optional[str] = None,
41
- url: Optional[str] = None,
42
- language: Optional[str] = None,
43
- speaker_diarization: bool = False,
44
- wait_for_completion: bool = False,
45
- timeout: int = 600,
46
- ) -> Dict[str, Any]:
47
- """
48
- Transcribe audio to text.
49
-
50
- Args:
51
- audio_file: Path to local audio file
52
- url: URL of audio file (or list of URLs)
53
- language: Language code (auto-detected if not provided)
54
- speaker_diarization: Enable speaker separation
55
- wait_for_completion: Wait for completion
56
- timeout: Max wait time in seconds
57
-
58
- Returns:
59
- Job dict with transcript when completed
60
- """
61
- if audio_file:
62
- # File upload endpoint
63
- data = {
64
- "enable_speaker_diarization": speaker_diarization,
65
- }
66
- if language:
67
- data["language"] = language
68
-
69
- files = self._prepare_file_upload(audio_file, "files")
70
-
71
- if self.async_mode:
72
- return self._async_transcribe_upload(data, files, wait_for_completion, timeout)
73
-
74
- response = self.client.request(
75
- "POST", "/api/v1/transcription/transcribe-upload", data=data, files=files
76
- )
77
- else:
78
- # URL-based endpoint
79
- data = {
80
- "source_urls": [url] if isinstance(url, str) else url,
81
- "enable_speaker_diarization": speaker_diarization,
82
- }
83
- if language:
84
- data["language"] = language
85
-
86
- if self.async_mode:
87
- return self._async_transcribe(data, wait_for_completion, timeout)
88
-
89
- response = self.client.request(
90
- "POST", "/api/v1/transcription/transcribe", json_data=data
91
- )
92
-
93
- if wait_for_completion:
94
- job_id = response.get("id") or response.get("job_id")
95
- return self._wait_for_transcription(job_id, timeout)
96
- return response
97
-
98
- async def _async_transcribe(
99
- self, data: Dict, wait_for_completion: bool, timeout: int
100
- ) -> Dict[str, Any]:
101
- response = await self.client.request(
102
- "POST", "/api/v1/transcription/transcribe", json_data=data
103
- )
104
- if wait_for_completion:
105
- job_id = response.get("id") or response.get("job_id")
106
- return await self._async_wait_for_transcription(job_id, timeout)
107
- return response
108
-
109
- async def _async_transcribe_upload(
110
- self, data: Dict, files: Dict, wait_for_completion: bool, timeout: int
111
- ) -> Dict[str, Any]:
112
- response = await self.client.request(
113
- "POST", "/api/v1/transcription/transcribe-upload", data=data, files=files
114
- )
115
- if wait_for_completion:
116
- job_id = response.get("id") or response.get("job_id")
117
- return await self._async_wait_for_transcription(job_id, timeout)
118
- return response
119
-
120
- def get_job(self, job_id: int) -> Dict[str, Any]:
121
- """Get transcription job details and status."""
122
- if self.async_mode:
123
- return self._async_get_job(job_id)
124
- return self.client.request("GET", f"/api/v1/transcription/jobs/{job_id}")
125
-
126
- async def _async_get_job(self, job_id: int) -> Dict[str, Any]:
127
- return await self.client.request("GET", f"/api/v1/transcription/jobs/{job_id}")
128
-
129
- def list_jobs(self, skip: int = 0, limit: int = 50) -> List[Dict[str, Any]]:
130
- """List transcription jobs."""
131
- if self.async_mode:
132
- return self._async_list_jobs(skip, limit)
133
- return self.client.request(
134
- "GET", "/api/v1/transcription/jobs", params={"skip": skip, "limit": limit}
135
- )
136
-
137
- async def _async_list_jobs(self, skip: int, limit: int) -> List[Dict[str, Any]]:
138
- return await self.client.request(
139
- "GET", "/api/v1/transcription/jobs", params={"skip": skip, "limit": limit}
140
- )
141
-
142
- def delete_job(self, job_id: int) -> Dict[str, str]:
143
- """Delete a transcription job."""
144
- if self.async_mode:
145
- return self._async_delete_job(job_id)
146
- return self.client.request("DELETE", f"/api/v1/transcription/jobs/{job_id}")
147
-
148
- async def _async_delete_job(self, job_id: int) -> Dict[str, str]:
149
- return await self.client.request("DELETE", f"/api/v1/transcription/jobs/{job_id}")
150
-
151
- def get_transcript(self, job_id: int, format: str = "json") -> Any:
152
- """
153
- Get transcript content.
154
-
155
- Args:
156
- job_id: Job ID
157
- format: Output format - 'json', 'txt', 'srt', 'vtt'
158
- """
159
- if self.async_mode:
160
- return self._async_get_transcript(job_id, format)
161
- return self.client.request(
162
- "GET", f"/api/v1/transcription/jobs/{job_id}/transcript", params={"format": format}
163
- )
164
-
165
- async def _async_get_transcript(self, job_id: int, format: str) -> Any:
166
- return await self.client.request(
167
- "GET", f"/api/v1/transcription/jobs/{job_id}/transcript", params={"format": format}
168
- )
169
-
170
- def wait_for_completion(self, job_id: int, timeout: int = 600) -> Dict[str, Any]:
171
- """Wait for transcription job completion (matches Node.js SDK)."""
172
- return self._wait_for_transcription(job_id, timeout)
173
-
174
- def _wait_for_transcription(self, job_id: int, timeout: int) -> Dict[str, Any]:
175
- """Wait for transcription job completion."""
176
- import time
177
- start_time = time.time()
178
-
179
- while time.time() - start_time < timeout:
180
- job = self.get_job(job_id)
181
- status = job.get("status", "").upper()
182
-
183
- if status == "COMPLETED":
184
- return job
185
- elif status in ("FAILED", "ERROR", "CANCELLED"):
186
- raise Exception(f"Transcription failed: {job.get('error_message', 'Unknown error')}")
187
-
188
- time.sleep(3)
189
-
190
- raise TimeoutError(f"Transcription {job_id} did not complete within {timeout} seconds")
191
-
192
- async def _async_wait_for_transcription(self, job_id: int, timeout: int) -> Dict[str, Any]:
193
- """Async wait for transcription job completion."""
194
- import asyncio
195
- import time
196
- start_time = time.time()
197
-
198
- while time.time() - start_time < timeout:
199
- job = await self.get_job(job_id)
200
- status = job.get("status", "").upper()
201
-
202
- if status == "COMPLETED":
203
- return job
204
- elif status in ("FAILED", "ERROR", "CANCELLED"):
205
- raise Exception(f"Transcription failed: {job.get('error_message', 'Unknown error')}")
206
-
207
- await asyncio.sleep(3)
208
-
209
- raise TimeoutError(f"Transcription {job_id} did not complete within {timeout} seconds")
210
-
@@ -1,135 +0,0 @@
1
- """
2
- Translation Service - Audio/speech translation
3
-
4
- API Routes:
5
- - POST /api/v1/translation/translate/speech - Translate speech
6
- - GET /api/v1/translation/translations/{id} - Get translation job
7
- - GET /api/v1/translation/translations - List translations
8
- - DELETE /api/v1/translation/translations/{id} - Delete translation
9
- """
10
-
11
- from typing import Optional, Dict, Any, List
12
- from .base import BaseService
13
-
14
-
15
- class TranslationService(BaseService):
16
- """Service for audio translation."""
17
-
18
- def translate(
19
- self,
20
- audio_file: Optional[str] = None,
21
- url: Optional[str] = None,
22
- target_language: str = "en",
23
- source_language: Optional[str] = None,
24
- wait_for_completion: bool = False,
25
- timeout: int = 900,
26
- ) -> Dict[str, Any]:
27
- """
28
- Translate audio to another language.
29
-
30
- Args:
31
- audio_file: Path to local audio file
32
- url: URL of audio file
33
- target_language: Target language code
34
- source_language: Source language (auto-detected if not provided)
35
- wait_for_completion: Wait for completion
36
- timeout: Max wait time in seconds
37
-
38
- Returns:
39
- Job dict with translated audio URL when completed
40
- """
41
- data = {"target_language": target_language}
42
- if source_language:
43
- data["source_language"] = source_language
44
- if url:
45
- data["url"] = url
46
-
47
- files = self._prepare_file_upload(audio_file, "file") if audio_file else None
48
-
49
- if self.async_mode:
50
- return self._async_translate(data, files, wait_for_completion, timeout)
51
-
52
- response = self.client.request("POST", "/api/v1/translation/translate/speech", data=data, files=files)
53
-
54
- if wait_for_completion:
55
- job_id = response.get("id") or response.get("job_id")
56
- return self._wait_for_translation(job_id, timeout)
57
- return response
58
-
59
- async def _async_translate(
60
- self, data: Dict, files: Optional[Dict], wait_for_completion: bool, timeout: int
61
- ) -> Dict[str, Any]:
62
- response = await self.client.request("POST", "/api/v1/translation/translate/speech", data=data, files=files)
63
- if wait_for_completion:
64
- job_id = response.get("id") or response.get("job_id")
65
- return await self._async_wait_for_translation(job_id, timeout)
66
- return response
67
-
68
- def get_job(self, job_id: int) -> Dict[str, Any]:
69
- """Get translation job details and status."""
70
- if self.async_mode:
71
- return self._async_get_job(job_id)
72
- return self.client.request("GET", f"/api/v1/translation/translations/{job_id}")
73
-
74
- async def _async_get_job(self, job_id: int) -> Dict[str, Any]:
75
- return await self.client.request("GET", f"/api/v1/translation/translations/{job_id}")
76
-
77
- def list_jobs(self, skip: int = 0, limit: int = 50) -> List[Dict[str, Any]]:
78
- """List translation jobs."""
79
- if self.async_mode:
80
- return self._async_list_jobs(skip, limit)
81
- return self.client.request(
82
- "GET", "/api/v1/translation/translations", params={"skip": skip, "limit": limit}
83
- )
84
-
85
- async def _async_list_jobs(self, skip: int, limit: int) -> List[Dict[str, Any]]:
86
- return await self.client.request(
87
- "GET", "/api/v1/translation/translations", params={"skip": skip, "limit": limit}
88
- )
89
-
90
- def delete_job(self, job_id: int) -> Dict[str, str]:
91
- """Delete a translation job."""
92
- if self.async_mode:
93
- return self._async_delete_job(job_id)
94
- return self.client.request("DELETE", f"/api/v1/translation/translations/{job_id}")
95
-
96
- async def _async_delete_job(self, job_id: int) -> Dict[str, str]:
97
- return await self.client.request("DELETE", f"/api/v1/translation/translations/{job_id}")
98
-
99
- def _wait_for_translation(self, job_id: int, timeout: int) -> Dict[str, Any]:
100
- """Wait for translation job completion."""
101
- import time
102
- start_time = time.time()
103
-
104
- while time.time() - start_time < timeout:
105
- job = self.get_job(job_id)
106
- status = job.get("status", "").upper()
107
-
108
- if status == "COMPLETED":
109
- return job
110
- elif status in ("FAILED", "ERROR"):
111
- raise Exception(f"Translation failed: {job.get('error_message', 'Unknown error')}")
112
-
113
- time.sleep(5)
114
-
115
- raise TimeoutError(f"Translation {job_id} did not complete within {timeout} seconds")
116
-
117
- async def _async_wait_for_translation(self, job_id: int, timeout: int) -> Dict[str, Any]:
118
- """Async wait for translation job completion."""
119
- import asyncio
120
- import time
121
- start_time = time.time()
122
-
123
- while time.time() - start_time < timeout:
124
- job = await self.get_job(job_id)
125
- status = job.get("status", "").upper()
126
-
127
- if status == "COMPLETED":
128
- return job
129
- elif status in ("FAILED", "ERROR"):
130
- raise Exception(f"Translation failed: {job.get('error_message', 'Unknown error')}")
131
-
132
- await asyncio.sleep(5)
133
-
134
- raise TimeoutError(f"Translation {job_id} did not complete within {timeout} seconds")
135
-