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/services/speaker.py
CHANGED
|
@@ -1,53 +1,134 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Speaker Service - Speaker
|
|
2
|
+
Speaker Service - Speaker diarization and extraction
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import Optional,
|
|
5
|
+
from typing import Optional, Dict, Any, List
|
|
6
6
|
from .base import BaseService
|
|
7
|
-
from ..models import Job, SpeakerAnalysisResult
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class SpeakerService(BaseService):
|
|
11
|
-
"""Service for speaker diarization and
|
|
12
|
-
|
|
13
|
-
def
|
|
10
|
+
"""Service for speaker diarization and extraction."""
|
|
11
|
+
|
|
12
|
+
def diarize(
|
|
14
13
|
self,
|
|
15
|
-
audio_file: str,
|
|
14
|
+
audio_file: Optional[str] = None,
|
|
15
|
+
url: Optional[str] = None,
|
|
16
16
|
num_speakers: Optional[int] = None,
|
|
17
17
|
wait_for_completion: bool = False,
|
|
18
|
-
timeout: int = 600
|
|
19
|
-
) ->
|
|
20
|
-
"""
|
|
21
|
-
|
|
18
|
+
timeout: int = 600,
|
|
19
|
+
) -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Identify and separate speakers in audio.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
audio_file: Path to local audio file
|
|
25
|
+
url: URL of audio file
|
|
26
|
+
num_speakers: Expected number of speakers (auto-detected if not provided)
|
|
27
|
+
wait_for_completion: Wait for completion
|
|
28
|
+
timeout: Max wait time in seconds
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Job dict with speaker segments when completed
|
|
32
|
+
"""
|
|
22
33
|
data = {}
|
|
23
34
|
if num_speakers:
|
|
24
35
|
data["num_speakers"] = num_speakers
|
|
25
|
-
|
|
36
|
+
if url:
|
|
37
|
+
data["url"] = url
|
|
38
|
+
|
|
39
|
+
files = self._prepare_file_upload(audio_file, "file") if audio_file else None
|
|
40
|
+
|
|
26
41
|
if self.async_mode:
|
|
27
|
-
return self._async_diarize(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return job
|
|
40
|
-
|
|
41
|
-
async def _async_diarize(self, files, data, wait_for_completion, timeout):
|
|
42
|
-
"""Async version of diarize_speakers"""
|
|
42
|
+
return self._async_diarize(data, files, wait_for_completion, timeout)
|
|
43
|
+
|
|
44
|
+
response = self.client.request("POST", "/api/v1/speaker/diarize", data=data, files=files)
|
|
45
|
+
|
|
46
|
+
if wait_for_completion:
|
|
47
|
+
return self._wait_for_completion(response["id"], timeout)
|
|
48
|
+
return response
|
|
49
|
+
|
|
50
|
+
async def _async_diarize(
|
|
51
|
+
self, data: Dict, files: Optional[Dict], wait_for_completion: bool, timeout: int
|
|
52
|
+
) -> Dict[str, Any]:
|
|
43
53
|
response = await self.client.request(
|
|
44
|
-
"POST", "/api/v1/speaker/diarize",
|
|
45
|
-
data=data, files=files
|
|
54
|
+
"POST", "/api/v1/speaker/diarize", data=data, files=files
|
|
46
55
|
)
|
|
47
|
-
job = Job.from_dict(response)
|
|
48
|
-
|
|
49
56
|
if wait_for_completion:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
return await self._async_wait_for_completion(response["id"], timeout)
|
|
58
|
+
return response
|
|
59
|
+
|
|
60
|
+
def extract(
|
|
61
|
+
self,
|
|
62
|
+
audio_file: Optional[str] = None,
|
|
63
|
+
url: Optional[str] = None,
|
|
64
|
+
wait_for_completion: bool = False,
|
|
65
|
+
timeout: int = 600,
|
|
66
|
+
) -> Dict[str, Any]:
|
|
67
|
+
"""
|
|
68
|
+
Extract individual speaker audio tracks.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
audio_file: Path to local audio file
|
|
72
|
+
url: URL of audio file
|
|
73
|
+
wait_for_completion: Wait for completion
|
|
74
|
+
timeout: Max wait time in seconds
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Job dict with speaker audio URLs when completed
|
|
78
|
+
"""
|
|
79
|
+
data = {}
|
|
80
|
+
if url:
|
|
81
|
+
data["url"] = url
|
|
82
|
+
|
|
83
|
+
files = self._prepare_file_upload(audio_file, "file") if audio_file else None
|
|
84
|
+
|
|
85
|
+
if self.async_mode:
|
|
86
|
+
return self._async_extract(data, files, wait_for_completion, timeout)
|
|
87
|
+
|
|
88
|
+
response = self.client.request("POST", "/api/v1/speaker/extract", data=data, files=files)
|
|
89
|
+
|
|
90
|
+
if wait_for_completion:
|
|
91
|
+
return self._wait_for_completion(response["id"], timeout)
|
|
92
|
+
return response
|
|
93
|
+
|
|
94
|
+
async def _async_extract(
|
|
95
|
+
self, data: Dict, files: Optional[Dict], wait_for_completion: bool, timeout: int
|
|
96
|
+
) -> Dict[str, Any]:
|
|
97
|
+
response = await self.client.request(
|
|
98
|
+
"POST", "/api/v1/speaker/extract", data=data, files=files
|
|
99
|
+
)
|
|
100
|
+
if wait_for_completion:
|
|
101
|
+
return await self._async_wait_for_completion(response["id"], timeout)
|
|
102
|
+
return response
|
|
103
|
+
|
|
104
|
+
def get_job(self, job_id: int) -> Dict[str, Any]:
|
|
105
|
+
"""Get speaker job details and status."""
|
|
106
|
+
if self.async_mode:
|
|
107
|
+
return self._async_get_job(job_id)
|
|
108
|
+
return self.client.request("GET", f"/api/v1/speaker/jobs/{job_id}")
|
|
109
|
+
|
|
110
|
+
async def _async_get_job(self, job_id: int) -> Dict[str, Any]:
|
|
111
|
+
return await self.client.request("GET", f"/api/v1/speaker/jobs/{job_id}")
|
|
112
|
+
|
|
113
|
+
def list_jobs(self, skip: int = 0, limit: int = 50) -> List[Dict[str, Any]]:
|
|
114
|
+
"""List speaker jobs."""
|
|
115
|
+
if self.async_mode:
|
|
116
|
+
return self._async_list_jobs(skip, limit)
|
|
117
|
+
return self.client.request(
|
|
118
|
+
"GET", "/api/v1/speaker/jobs", params={"skip": skip, "limit": limit}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
async def _async_list_jobs(self, skip: int, limit: int) -> List[Dict[str, Any]]:
|
|
122
|
+
return await self.client.request(
|
|
123
|
+
"GET", "/api/v1/speaker/jobs", params={"skip": skip, "limit": limit}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def delete_job(self, job_id: int) -> Dict[str, str]:
|
|
127
|
+
"""Delete a speaker job."""
|
|
128
|
+
if self.async_mode:
|
|
129
|
+
return self._async_delete_job(job_id)
|
|
130
|
+
return self.client.request("DELETE", f"/api/v1/speaker/jobs/{job_id}")
|
|
131
|
+
|
|
132
|
+
async def _async_delete_job(self, job_id: int) -> Dict[str, str]:
|
|
133
|
+
return await self.client.request("DELETE", f"/api/v1/speaker/jobs/{job_id}")
|
|
134
|
+
|
|
@@ -1,180 +1,287 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Stem Extraction Service - Audio stem separation
|
|
2
|
+
Stem Extraction Service - Audio stem separation
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import List, Optional, Dict, Any,
|
|
5
|
+
from typing import List, Optional, Dict, Any, Literal
|
|
6
6
|
from .base import BaseService
|
|
7
|
-
from ..models import Job
|
|
8
7
|
from ..exceptions import ValidationError
|
|
9
8
|
|
|
10
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
|
+
|
|
11
15
|
class StemExtractionService(BaseService):
|
|
12
|
-
"""
|
|
13
|
-
|
|
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
|
|
14
150
|
def extract_stems(
|
|
15
151
|
self,
|
|
16
152
|
audio_file: Optional[str] = None,
|
|
17
153
|
url: Optional[str] = None,
|
|
18
|
-
stem_types: List[str] = None,
|
|
154
|
+
stem_types: Optional[List[str]] = None,
|
|
19
155
|
model_name: str = "htdemucs",
|
|
20
156
|
two_stems_mode: Optional[str] = None,
|
|
21
157
|
wait_for_completion: bool = False,
|
|
22
|
-
timeout: int = 900
|
|
23
|
-
) ->
|
|
158
|
+
timeout: int = 900,
|
|
159
|
+
) -> Dict[str, Any]:
|
|
24
160
|
"""
|
|
25
|
-
Extract stems from audio
|
|
26
|
-
|
|
161
|
+
Extract stems from audio (legacy method).
|
|
162
|
+
|
|
163
|
+
For new code, use separate() instead which uses the simpler mode-based API.
|
|
164
|
+
|
|
27
165
|
Args:
|
|
28
|
-
audio_file: Path to audio file
|
|
29
|
-
url: URL of audio file
|
|
30
|
-
stem_types:
|
|
31
|
-
model_name: Model to use
|
|
32
|
-
two_stems_mode: Two-stem mode
|
|
33
|
-
wait_for_completion:
|
|
34
|
-
timeout:
|
|
35
|
-
|
|
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
|
+
|
|
36
174
|
Returns:
|
|
37
|
-
Job
|
|
175
|
+
Job dict with id, status, download_urls (when completed)
|
|
38
176
|
"""
|
|
39
177
|
if not audio_file and not url:
|
|
40
|
-
raise ValidationError("
|
|
41
|
-
|
|
178
|
+
raise ValidationError("Provide audio_file or url")
|
|
179
|
+
|
|
42
180
|
if audio_file and url:
|
|
43
|
-
raise ValidationError("Provide
|
|
44
|
-
|
|
45
|
-
# Set default stem types based on model
|
|
181
|
+
raise ValidationError("Provide audio_file or url, not both")
|
|
182
|
+
|
|
46
183
|
if stem_types is None:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# Prepare request
|
|
57
|
-
files = {}
|
|
58
|
-
data = {
|
|
59
|
-
"stem_types": str(stem_types), # API expects string representation
|
|
60
|
-
"model_name": model_name
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if audio_file:
|
|
64
|
-
files = self._prepare_file_upload(audio_file, "file")
|
|
65
|
-
|
|
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
|
+
|
|
66
192
|
if url:
|
|
67
193
|
data["url"] = url
|
|
68
|
-
|
|
194
|
+
|
|
69
195
|
if two_stems_mode:
|
|
70
196
|
data["two_stems_mode"] = two_stems_mode
|
|
71
|
-
|
|
197
|
+
|
|
198
|
+
files = self._prepare_file_upload(audio_file, "file") if audio_file else None
|
|
199
|
+
|
|
72
200
|
if self.async_mode:
|
|
73
|
-
return self._async_extract_stems(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if wait_for_completion:
|
|
85
|
-
return self._wait_for_completion(job.id, timeout)
|
|
86
|
-
|
|
87
|
-
return job
|
|
88
|
-
|
|
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
|
+
|
|
89
212
|
async def _async_extract_stems(
|
|
90
213
|
self,
|
|
91
|
-
files: Dict[str, Any],
|
|
92
214
|
data: Dict[str, Any],
|
|
215
|
+
files: Optional[Dict[str, Any]],
|
|
93
216
|
wait_for_completion: bool,
|
|
94
|
-
timeout: int
|
|
95
|
-
) ->
|
|
96
|
-
"""Async version of extract_stems"""
|
|
217
|
+
timeout: int,
|
|
218
|
+
) -> Dict[str, Any]:
|
|
97
219
|
response = await self.client.request(
|
|
98
|
-
"POST",
|
|
99
|
-
"/api/v1/stem-extraction/extract",
|
|
100
|
-
data=data,
|
|
101
|
-
files=files if files else None
|
|
220
|
+
"POST", "/api/v1/stem-extraction/extract", data=data, files=files
|
|
102
221
|
)
|
|
103
|
-
|
|
104
|
-
job = Job.from_dict(response)
|
|
105
|
-
|
|
106
222
|
if wait_for_completion:
|
|
107
|
-
return await self.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"""
|
|
113
|
-
Get stem extraction job status
|
|
114
|
-
|
|
115
|
-
Args:
|
|
116
|
-
job_id: ID of the stem extraction job
|
|
117
|
-
|
|
118
|
-
Returns:
|
|
119
|
-
Job object with current status
|
|
120
|
-
"""
|
|
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."""
|
|
121
228
|
if self.async_mode:
|
|
122
|
-
return self.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return Job.from_dict(response)
|
|
131
|
-
|
|
132
|
-
def list_stem_jobs(
|
|
133
|
-
self,
|
|
134
|
-
skip: int = 0,
|
|
135
|
-
limit: int = 50
|
|
136
|
-
) -> List[Job]:
|
|
137
|
-
"""
|
|
138
|
-
List stem extraction jobs
|
|
139
|
-
|
|
140
|
-
Args:
|
|
141
|
-
skip: Number of jobs to skip
|
|
142
|
-
limit: Maximum number of jobs to return
|
|
143
|
-
|
|
144
|
-
Returns:
|
|
145
|
-
List of stem extraction jobs
|
|
146
|
-
"""
|
|
147
|
-
params = {
|
|
148
|
-
"skip": skip,
|
|
149
|
-
"limit": limit
|
|
150
|
-
}
|
|
151
|
-
|
|
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."""
|
|
152
237
|
if self.async_mode:
|
|
153
|
-
return self.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
async def
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def
|
|
164
|
-
"""
|
|
165
|
-
Delete a stem extraction job
|
|
166
|
-
|
|
167
|
-
Args:
|
|
168
|
-
job_id: ID of the job to delete
|
|
169
|
-
|
|
170
|
-
Returns:
|
|
171
|
-
Deletion confirmation
|
|
172
|
-
"""
|
|
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."""
|
|
173
250
|
if self.async_mode:
|
|
174
|
-
return self.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
async def _async_delete_stem_job(self, job_id: int) -> Dict[str, str]:
|
|
179
|
-
"""Async version of delete_stem_job"""
|
|
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]:
|
|
180
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
|
+
|