audiopod 1.1.1__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 +10 -64
- audiopod/client.py +143 -172
- audiopod/config.py +4 -50
- audiopod/exceptions.py +16 -71
- audiopod/services/__init__.py +8 -6
- audiopod/services/base.py +51 -195
- audiopod/services/credits.py +26 -30
- audiopod/services/denoiser.py +120 -40
- audiopod/services/music.py +180 -485
- audiopod/services/speaker.py +117 -36
- audiopod/services/stem_extraction.py +130 -142
- audiopod/services/transcription.py +159 -184
- audiopod/services/translation.py +109 -170
- audiopod/services/voice.py +138 -327
- audiopod/services/wallet.py +235 -0
- audiopod-1.4.0.dist-info/METADATA +206 -0
- audiopod-1.4.0.dist-info/RECORD +20 -0
- {audiopod-1.1.1.dist-info → audiopod-1.4.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.1.1.dist-info/METADATA +0 -404
- audiopod-1.1.1.dist-info/RECORD +0 -24
- audiopod-1.1.1.dist-info/entry_points.txt +0 -2
- {audiopod-1.1.1.dist-info → audiopod-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {audiopod-1.1.1.dist-info → audiopod-1.4.0.dist-info}/top_level.txt +0 -0
audiopod/services/music.py
CHANGED
|
@@ -1,522 +1,217 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Music Service - Music generation
|
|
3
|
-
"""
|
|
2
|
+
Music Service - Music generation
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
API Routes:
|
|
5
|
+
- POST /api/v1/music/text2music - Generate music with vocals
|
|
6
|
+
- POST /api/v1/music/prompt2instrumental - Generate instrumental music
|
|
7
|
+
- POST /api/v1/music/lyric2vocals - Generate vocals from lyrics
|
|
8
|
+
- POST /api/v1/music/text2rap - Generate rap music
|
|
9
|
+
- GET /api/v1/music/jobs/{id}/status - Get job status
|
|
10
|
+
- GET /api/v1/music/jobs - List jobs
|
|
11
|
+
- DELETE /api/v1/music/jobs/{id} - Delete job
|
|
12
|
+
- GET /api/v1/music/presets - Get genre presets
|
|
13
|
+
"""
|
|
7
14
|
|
|
15
|
+
from typing import Optional, Dict, Any, List, Literal
|
|
8
16
|
from .base import BaseService
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
MusicTask = Literal["text2music", "prompt2instrumental", "lyric2vocals", "text2rap", "text2samples"]
|
|
11
20
|
|
|
12
21
|
|
|
13
22
|
class MusicService(BaseService):
|
|
14
|
-
"""Service for music generation
|
|
15
|
-
|
|
16
|
-
def
|
|
23
|
+
"""Service for AI music generation."""
|
|
24
|
+
|
|
25
|
+
def generate(
|
|
17
26
|
self,
|
|
18
27
|
prompt: str,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
task: MusicTask = "prompt2instrumental",
|
|
29
|
+
duration: int = 30,
|
|
30
|
+
lyrics: Optional[str] = None,
|
|
31
|
+
genre_preset: Optional[str] = None,
|
|
23
32
|
display_name: Optional[str] = None,
|
|
24
33
|
wait_for_completion: bool = False,
|
|
25
|
-
timeout: int = 600
|
|
26
|
-
) ->
|
|
34
|
+
timeout: int = 600,
|
|
35
|
+
) -> Dict[str, Any]:
|
|
27
36
|
"""
|
|
28
|
-
Generate music from text prompt
|
|
29
|
-
|
|
37
|
+
Generate music from text prompt.
|
|
38
|
+
|
|
30
39
|
Args:
|
|
31
|
-
prompt: Text description of
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
prompt: Text description of desired music
|
|
41
|
+
task: Generation task type:
|
|
42
|
+
- "prompt2instrumental": Instrumental music (no vocals)
|
|
43
|
+
- "text2music": Music with vocals (requires lyrics)
|
|
44
|
+
- "text2rap": Rap music (requires lyrics)
|
|
45
|
+
- "lyric2vocals": Generate vocals from lyrics
|
|
46
|
+
duration: Duration in seconds (default 30, max varies by task)
|
|
47
|
+
lyrics: Lyrics for vocal tasks
|
|
48
|
+
genre_preset: Genre preset name
|
|
49
|
+
display_name: Custom name for the job
|
|
50
|
+
wait_for_completion: Wait for completion
|
|
51
|
+
timeout: Max wait time in seconds
|
|
52
|
+
|
|
40
53
|
Returns:
|
|
41
|
-
Job
|
|
54
|
+
Job dict with audio URL when completed
|
|
42
55
|
"""
|
|
43
|
-
# Validate inputs
|
|
44
|
-
prompt = self._validate_text_input(prompt, max_length=1000)
|
|
45
|
-
if not 10.0 <= duration <= 600.0:
|
|
46
|
-
raise ValidationError("Duration must be between 10 and 600 seconds")
|
|
47
|
-
if not 1.0 <= guidance_scale <= 20.0:
|
|
48
|
-
raise ValidationError("Guidance scale must be between 1.0 and 20.0")
|
|
49
|
-
if not 20 <= num_inference_steps <= 100:
|
|
50
|
-
raise ValidationError("Inference steps must be between 20 and 100")
|
|
51
|
-
if seed is not None and (seed < 0 or seed > 2**32 - 1):
|
|
52
|
-
raise ValidationError("Seed must be between 0 and 2^32 - 1")
|
|
53
|
-
|
|
54
|
-
# Prepare request data - FIXED: Use correct parameter names matching API schema
|
|
55
56
|
data = {
|
|
56
57
|
"prompt": prompt,
|
|
57
|
-
"audio_duration": duration,
|
|
58
|
-
"guidance_scale": guidance_scale,
|
|
59
|
-
"infer_step": num_inference_steps # FIXED: API expects "infer_step" not "num_inference_steps"
|
|
58
|
+
"audio_duration": duration,
|
|
60
59
|
}
|
|
61
|
-
if seed is not None:
|
|
62
|
-
data["manual_seeds"] = [seed] # FIXED: API expects "manual_seeds" list not "seed"
|
|
63
|
-
if display_name:
|
|
64
|
-
data["display_name"] = display_name.strip()
|
|
65
|
-
|
|
66
|
-
# Make request
|
|
67
|
-
if self.async_mode:
|
|
68
|
-
return self._async_generate_music(data, wait_for_completion, timeout)
|
|
69
|
-
else:
|
|
70
|
-
response = self.client.request("POST", "/api/v1/music/text2music", data=data)
|
|
71
|
-
# FIXED: Handle response format correctly - API returns {"job": {...}, "message": "..."}
|
|
72
|
-
job_data = response.get("job", response)
|
|
73
|
-
job = Job.from_dict(job_data)
|
|
74
|
-
|
|
75
|
-
if wait_for_completion:
|
|
76
|
-
completed_job = self._wait_for_completion(job.id, timeout)
|
|
77
|
-
return MusicGenerationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
78
|
-
|
|
79
|
-
return job
|
|
80
|
-
|
|
81
|
-
async def _async_generate_music(
|
|
82
|
-
self,
|
|
83
|
-
data: Dict[str, Any],
|
|
84
|
-
wait_for_completion: bool,
|
|
85
|
-
timeout: int
|
|
86
|
-
) -> Union[Job, MusicGenerationResult]:
|
|
87
|
-
"""Async version of generate_music"""
|
|
88
|
-
response = await self.client.request("POST", "/api/v1/music/text2music", data=data)
|
|
89
|
-
# FIXED: Handle response format correctly
|
|
90
|
-
job_data = response.get("job", response)
|
|
91
|
-
job = Job.from_dict(job_data)
|
|
92
|
-
|
|
93
|
-
if wait_for_completion:
|
|
94
|
-
completed_job = await self._async_wait_for_completion(job.id, timeout)
|
|
95
|
-
return MusicGenerationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
96
|
-
|
|
97
|
-
return job
|
|
98
|
-
|
|
99
|
-
def generate_rap(
|
|
100
|
-
self,
|
|
101
|
-
lyrics: str,
|
|
102
|
-
style: str = "modern",
|
|
103
|
-
tempo: int = 120,
|
|
104
|
-
display_name: Optional[str] = None,
|
|
105
|
-
wait_for_completion: bool = False,
|
|
106
|
-
timeout: int = 600
|
|
107
|
-
) -> Union[Job, MusicGenerationResult]:
|
|
108
|
-
"""
|
|
109
|
-
Generate rap music with lyrics
|
|
110
60
|
|
|
111
|
-
|
|
112
|
-
lyrics
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
display_name: Custom name for the track
|
|
116
|
-
wait_for_completion: Whether to wait for completion
|
|
117
|
-
timeout: Maximum time to wait
|
|
118
|
-
|
|
119
|
-
Returns:
|
|
120
|
-
Job object or generation result
|
|
121
|
-
"""
|
|
122
|
-
# Validate inputs
|
|
123
|
-
lyrics = self._validate_text_input(lyrics, max_length=2000)
|
|
124
|
-
if not 80 <= tempo <= 200:
|
|
125
|
-
raise ValidationError("Tempo must be between 80 and 200 BPM")
|
|
126
|
-
if style not in ["modern", "classic", "trap"]:
|
|
127
|
-
raise ValidationError("Style must be 'modern', 'classic', or 'trap'")
|
|
128
|
-
|
|
129
|
-
# Prepare request data - FIXED: Match API schema for text2rap
|
|
130
|
-
data = {
|
|
131
|
-
"prompt": f"rap music, {style} style", # FIXED: API expects "prompt" field
|
|
132
|
-
"lyrics": lyrics,
|
|
133
|
-
"audio_duration": 120.0, # Default duration
|
|
134
|
-
"guidance_scale": 7.5,
|
|
135
|
-
"infer_step": 50,
|
|
136
|
-
"lora_name_or_path": "ACE-Step/ACE-Step-v1-chinese-rap-LoRA" # Rap-specific LoRA
|
|
137
|
-
}
|
|
61
|
+
if lyrics:
|
|
62
|
+
data["lyrics"] = lyrics
|
|
63
|
+
if genre_preset:
|
|
64
|
+
data["genre_preset"] = genre_preset
|
|
138
65
|
if display_name:
|
|
139
|
-
data["display_name"] = display_name
|
|
140
|
-
|
|
141
|
-
|
|
66
|
+
data["display_name"] = display_name
|
|
67
|
+
|
|
68
|
+
endpoint = f"/api/v1/music/{task}"
|
|
69
|
+
|
|
142
70
|
if self.async_mode:
|
|
143
|
-
return self.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
job_data = response.get("job", response)
|
|
148
|
-
job = Job.from_dict(job_data)
|
|
149
|
-
|
|
150
|
-
if wait_for_completion:
|
|
151
|
-
completed_job = self._wait_for_completion(job.id, timeout)
|
|
152
|
-
return MusicGenerationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
153
|
-
|
|
154
|
-
return job
|
|
155
|
-
|
|
156
|
-
async def _async_generate_rap(
|
|
157
|
-
self,
|
|
158
|
-
data: Dict[str, Any],
|
|
159
|
-
wait_for_completion: bool,
|
|
160
|
-
timeout: int
|
|
161
|
-
) -> Union[Job, MusicGenerationResult]:
|
|
162
|
-
"""Async version of generate_rap"""
|
|
163
|
-
response = await self.client.request("POST", "/api/v1/music/text2rap", data=data)
|
|
164
|
-
# FIXED: Handle response format correctly
|
|
165
|
-
job_data = response.get("job", response)
|
|
166
|
-
job = Job.from_dict(job_data)
|
|
167
|
-
|
|
71
|
+
return self._async_generate(endpoint, data, wait_for_completion, timeout)
|
|
72
|
+
|
|
73
|
+
response = self.client.request("POST", endpoint, json_data=data)
|
|
74
|
+
|
|
168
75
|
if wait_for_completion:
|
|
169
|
-
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
76
|
+
job_id = response.get("id") or response.get("job_id")
|
|
77
|
+
return self._wait_for_music(job_id, timeout)
|
|
78
|
+
return response
|
|
79
|
+
|
|
80
|
+
async def _async_generate(
|
|
81
|
+
self, endpoint: str, data: Dict, wait_for_completion: bool, timeout: int
|
|
82
|
+
) -> Dict[str, Any]:
|
|
83
|
+
response = await self.client.request("POST", endpoint, json_data=data)
|
|
84
|
+
if wait_for_completion:
|
|
85
|
+
job_id = response.get("id") or response.get("job_id")
|
|
86
|
+
return await self._async_wait_for_music(job_id, timeout)
|
|
87
|
+
return response
|
|
88
|
+
|
|
89
|
+
def instrumental(
|
|
175
90
|
self,
|
|
176
91
|
prompt: str,
|
|
177
|
-
duration:
|
|
178
|
-
instruments: Optional[List[str]] = None,
|
|
179
|
-
key: Optional[str] = None,
|
|
180
|
-
tempo: Optional[int] = None,
|
|
181
|
-
display_name: Optional[str] = None,
|
|
92
|
+
duration: int = 30,
|
|
182
93
|
wait_for_completion: bool = False,
|
|
183
|
-
timeout: int = 600
|
|
184
|
-
) ->
|
|
185
|
-
"""
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
wait_for_completion: Whether to wait for completion
|
|
196
|
-
timeout: Maximum time to wait
|
|
197
|
-
|
|
198
|
-
Returns:
|
|
199
|
-
Job object or generation result
|
|
200
|
-
"""
|
|
201
|
-
# Validate inputs
|
|
202
|
-
prompt = self._validate_text_input(prompt, max_length=1000)
|
|
203
|
-
if not 10.0 <= duration <= 600.0:
|
|
204
|
-
raise ValidationError("Duration must be between 10 and 600 seconds")
|
|
205
|
-
if tempo is not None and not 60 <= tempo <= 200:
|
|
206
|
-
raise ValidationError("Tempo must be between 60 and 200 BPM")
|
|
207
|
-
|
|
208
|
-
# Prepare request data - FIXED: Match API schema for prompt2instrumental
|
|
209
|
-
data = {
|
|
210
|
-
"prompt": prompt,
|
|
211
|
-
"audio_duration": duration, # FIXED: API expects "audio_duration"
|
|
212
|
-
"guidance_scale": 7.5,
|
|
213
|
-
"infer_step": 50
|
|
214
|
-
}
|
|
215
|
-
if instruments:
|
|
216
|
-
data["instruments"] = instruments
|
|
217
|
-
if key:
|
|
218
|
-
data["key"] = key
|
|
219
|
-
if tempo:
|
|
220
|
-
data["tempo"] = tempo
|
|
221
|
-
if display_name:
|
|
222
|
-
data["display_name"] = display_name.strip()
|
|
223
|
-
|
|
224
|
-
# Make request
|
|
225
|
-
if self.async_mode:
|
|
226
|
-
return self._async_generate_instrumental(data, wait_for_completion, timeout)
|
|
227
|
-
else:
|
|
228
|
-
response = self.client.request("POST", "/api/v1/music/prompt2instrumental", data=data)
|
|
229
|
-
# FIXED: Handle response format correctly
|
|
230
|
-
job_data = response.get("job", response)
|
|
231
|
-
job = Job.from_dict(job_data)
|
|
232
|
-
|
|
233
|
-
if wait_for_completion:
|
|
234
|
-
completed_job = self._wait_for_completion(job.id, timeout)
|
|
235
|
-
return MusicGenerationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
236
|
-
|
|
237
|
-
return job
|
|
238
|
-
|
|
239
|
-
async def _async_generate_instrumental(
|
|
240
|
-
self,
|
|
241
|
-
data: Dict[str, Any],
|
|
242
|
-
wait_for_completion: bool,
|
|
243
|
-
timeout: int
|
|
244
|
-
) -> Union[Job, MusicGenerationResult]:
|
|
245
|
-
"""Async version of generate_instrumental"""
|
|
246
|
-
response = await self.client.request("POST", "/api/v1/music/prompt2instrumental", data=data)
|
|
247
|
-
# FIXED: Handle response format correctly
|
|
248
|
-
job_data = response.get("job", response)
|
|
249
|
-
job = Job.from_dict(job_data)
|
|
250
|
-
|
|
251
|
-
if wait_for_completion:
|
|
252
|
-
completed_job = await self._async_wait_for_completion(job.id, timeout)
|
|
253
|
-
return MusicGenerationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
254
|
-
|
|
255
|
-
return job
|
|
256
|
-
|
|
257
|
-
def generate_vocals(
|
|
94
|
+
timeout: int = 600,
|
|
95
|
+
) -> Dict[str, Any]:
|
|
96
|
+
"""Generate instrumental music (no vocals)."""
|
|
97
|
+
return self.generate(
|
|
98
|
+
prompt=prompt,
|
|
99
|
+
task="prompt2instrumental",
|
|
100
|
+
duration=duration,
|
|
101
|
+
wait_for_completion=wait_for_completion,
|
|
102
|
+
timeout=timeout,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def song(
|
|
258
106
|
self,
|
|
107
|
+
prompt: str,
|
|
259
108
|
lyrics: str,
|
|
260
|
-
|
|
261
|
-
duration: float = 120.0,
|
|
262
|
-
display_name: Optional[str] = None,
|
|
109
|
+
duration: int = 60,
|
|
263
110
|
wait_for_completion: bool = False,
|
|
264
|
-
timeout: int = 600
|
|
265
|
-
) ->
|
|
266
|
-
"""
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
lyrics
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
Returns:
|
|
278
|
-
Job object or generation result
|
|
279
|
-
"""
|
|
280
|
-
# Validate inputs
|
|
281
|
-
lyrics = self._validate_text_input(lyrics, max_length=10000)
|
|
282
|
-
prompt = self._validate_text_input(prompt, max_length=2000)
|
|
283
|
-
if not 10.0 <= duration <= 600.0:
|
|
284
|
-
raise ValidationError("Duration must be between 10 and 600 seconds")
|
|
285
|
-
|
|
286
|
-
# Prepare request data - Match API schema for lyric2vocals
|
|
287
|
-
data = {
|
|
288
|
-
"prompt": prompt,
|
|
289
|
-
"lyrics": lyrics,
|
|
290
|
-
"audio_duration": duration,
|
|
291
|
-
"guidance_scale": 7.5,
|
|
292
|
-
"infer_step": 50
|
|
293
|
-
}
|
|
294
|
-
if display_name:
|
|
295
|
-
data["display_name"] = display_name.strip()
|
|
296
|
-
|
|
297
|
-
# Make request
|
|
298
|
-
if self.async_mode:
|
|
299
|
-
return self._async_generate_vocals(data, wait_for_completion, timeout)
|
|
300
|
-
else:
|
|
301
|
-
response = self.client.request("POST", "/api/v1/music/lyric2vocals", data=data)
|
|
302
|
-
job_data = response.get("job", response)
|
|
303
|
-
job = Job.from_dict(job_data)
|
|
304
|
-
|
|
305
|
-
if wait_for_completion:
|
|
306
|
-
completed_job = self._wait_for_completion(job.id, timeout)
|
|
307
|
-
return MusicGenerationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
308
|
-
|
|
309
|
-
return job
|
|
310
|
-
|
|
311
|
-
async def _async_generate_vocals(
|
|
312
|
-
self,
|
|
313
|
-
data: Dict[str, Any],
|
|
314
|
-
wait_for_completion: bool,
|
|
315
|
-
timeout: int
|
|
316
|
-
) -> Union[Job, MusicGenerationResult]:
|
|
317
|
-
"""Async version of generate_vocals"""
|
|
318
|
-
response = await self.client.request("POST", "/api/v1/music/lyric2vocals", data=data)
|
|
319
|
-
job_data = response.get("job", response)
|
|
320
|
-
job = Job.from_dict(job_data)
|
|
321
|
-
|
|
322
|
-
if wait_for_completion:
|
|
323
|
-
completed_job = await self._async_wait_for_completion(job.id, timeout)
|
|
324
|
-
return MusicGenerationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
325
|
-
|
|
326
|
-
return job
|
|
327
|
-
|
|
328
|
-
def extend_music(
|
|
111
|
+
timeout: int = 600,
|
|
112
|
+
) -> Dict[str, Any]:
|
|
113
|
+
"""Generate a song with vocals."""
|
|
114
|
+
return self.generate(
|
|
115
|
+
prompt=prompt,
|
|
116
|
+
task="text2music",
|
|
117
|
+
lyrics=lyrics,
|
|
118
|
+
duration=duration,
|
|
119
|
+
wait_for_completion=wait_for_completion,
|
|
120
|
+
timeout=timeout,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def rap(
|
|
329
124
|
self,
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
125
|
+
prompt: str,
|
|
126
|
+
lyrics: str,
|
|
127
|
+
duration: int = 60,
|
|
333
128
|
wait_for_completion: bool = False,
|
|
334
|
-
timeout: int = 600
|
|
335
|
-
) ->
|
|
336
|
-
"""
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
"""
|
|
349
|
-
# Validate inputs
|
|
350
|
-
if not 5.0 <= extend_duration <= 120.0:
|
|
351
|
-
raise ValidationError("Extend duration must be between 5 and 120 seconds")
|
|
352
|
-
|
|
353
|
-
# Prepare request data
|
|
354
|
-
data = {
|
|
355
|
-
"source_job_id": source_job_id,
|
|
356
|
-
"extend_duration": extend_duration
|
|
357
|
-
}
|
|
358
|
-
if display_name:
|
|
359
|
-
data["display_name"] = display_name.strip()
|
|
360
|
-
|
|
361
|
-
# Make request
|
|
129
|
+
timeout: int = 600,
|
|
130
|
+
) -> Dict[str, Any]:
|
|
131
|
+
"""Generate rap music."""
|
|
132
|
+
return self.generate(
|
|
133
|
+
prompt=prompt,
|
|
134
|
+
task="text2rap",
|
|
135
|
+
lyrics=lyrics,
|
|
136
|
+
duration=duration,
|
|
137
|
+
wait_for_completion=wait_for_completion,
|
|
138
|
+
timeout=timeout,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def get_job(self, job_id: int) -> Dict[str, Any]:
|
|
142
|
+
"""Get music generation job status."""
|
|
362
143
|
if self.async_mode:
|
|
363
|
-
return self.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return job
|
|
373
|
-
|
|
374
|
-
async def _async_extend_music(
|
|
375
|
-
self,
|
|
376
|
-
data: Dict[str, Any],
|
|
377
|
-
wait_for_completion: bool,
|
|
378
|
-
timeout: int
|
|
379
|
-
) -> Union[Job, MusicGenerationResult]:
|
|
380
|
-
"""Async version of extend_music"""
|
|
381
|
-
response = await self.client.request("POST", "/api/v1/music/extend", data=data)
|
|
382
|
-
job = Job.from_dict(response)
|
|
383
|
-
|
|
384
|
-
if wait_for_completion:
|
|
385
|
-
completed_job = await self._async_wait_for_completion(job.id, timeout)
|
|
386
|
-
return MusicGenerationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
387
|
-
|
|
388
|
-
return job
|
|
389
|
-
|
|
390
|
-
def list_music_jobs(
|
|
391
|
-
self,
|
|
392
|
-
limit: int = 50,
|
|
393
|
-
offset: int = 0,
|
|
394
|
-
status: Optional[str] = None,
|
|
395
|
-
task: Optional[str] = None,
|
|
396
|
-
liked: Optional[bool] = None
|
|
397
|
-
) -> List[MusicGenerationResult]:
|
|
398
|
-
"""
|
|
399
|
-
List music generation jobs
|
|
400
|
-
|
|
401
|
-
Args:
|
|
402
|
-
limit: Maximum number of results
|
|
403
|
-
offset: Number of results to skip
|
|
404
|
-
status: Filter by job status
|
|
405
|
-
task: Filter by task type
|
|
406
|
-
liked: Filter by liked status
|
|
407
|
-
|
|
408
|
-
Returns:
|
|
409
|
-
List of music generation results
|
|
410
|
-
"""
|
|
411
|
-
params = {
|
|
412
|
-
"limit": limit,
|
|
413
|
-
"skip": offset # FIXED: API uses "skip" parameter for offset
|
|
414
|
-
}
|
|
415
|
-
if status:
|
|
416
|
-
params["status"] = status
|
|
144
|
+
return self._async_get_job(job_id)
|
|
145
|
+
return self.client.request("GET", f"/api/v1/music/jobs/{job_id}/status")
|
|
146
|
+
|
|
147
|
+
async def _async_get_job(self, job_id: int) -> Dict[str, Any]:
|
|
148
|
+
return await self.client.request("GET", f"/api/v1/music/jobs/{job_id}/status")
|
|
149
|
+
|
|
150
|
+
def list_jobs(self, skip: int = 0, limit: int = 50, task: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
151
|
+
"""List music generation jobs."""
|
|
152
|
+
params = {"skip": skip, "limit": limit}
|
|
417
153
|
if task:
|
|
418
154
|
params["task"] = task
|
|
419
|
-
if liked is not None:
|
|
420
|
-
params["liked"] = liked
|
|
421
|
-
|
|
422
|
-
if self.async_mode:
|
|
423
|
-
return self._async_list_music_jobs(params)
|
|
424
|
-
else:
|
|
425
|
-
response = self.client.request("GET", "/api/v1/music/jobs", params=params)
|
|
426
|
-
return [MusicGenerationResult.from_dict(job_data) for job_data in response]
|
|
427
|
-
|
|
428
|
-
async def _async_list_music_jobs(self, params: Dict[str, Any]) -> List[MusicGenerationResult]:
|
|
429
|
-
"""Async version of list_music_jobs"""
|
|
430
|
-
response = await self.client.request("GET", "/api/v1/music/jobs", params=params)
|
|
431
|
-
return [MusicGenerationResult.from_dict(job_data) for job_data in response]
|
|
432
|
-
|
|
433
|
-
def get_music_job(self, job_id: int) -> MusicGenerationResult:
|
|
434
|
-
"""
|
|
435
|
-
Get details of a specific music generation job
|
|
436
|
-
|
|
437
|
-
Args:
|
|
438
|
-
job_id: ID of the music job
|
|
439
|
-
|
|
440
|
-
Returns:
|
|
441
|
-
Music generation result
|
|
442
|
-
"""
|
|
443
|
-
if self.async_mode:
|
|
444
|
-
return self._async_get_music_job(job_id)
|
|
445
|
-
else:
|
|
446
|
-
response = self.client.request("GET", f"/api/v1/music/jobs/{job_id}/status")
|
|
447
|
-
return MusicGenerationResult.from_dict(response)
|
|
448
|
-
|
|
449
|
-
async def _async_get_music_job(self, job_id: int) -> MusicGenerationResult:
|
|
450
|
-
"""Async version of get_music_job"""
|
|
451
|
-
response = await self.client.request("GET", f"/api/v1/music/jobs/{job_id}/status")
|
|
452
|
-
return MusicGenerationResult.from_dict(response)
|
|
453
|
-
|
|
454
|
-
def like_music_track(self, job_id: int) -> Dict[str, Any]:
|
|
455
|
-
"""
|
|
456
|
-
Like a music track
|
|
457
|
-
|
|
458
|
-
Args:
|
|
459
|
-
job_id: ID of the music job
|
|
460
155
|
|
|
461
|
-
Returns:
|
|
462
|
-
Like response
|
|
463
|
-
"""
|
|
464
156
|
if self.async_mode:
|
|
465
|
-
return self.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
def share_music_track(
|
|
474
|
-
self,
|
|
475
|
-
job_id: int,
|
|
476
|
-
platform: Optional[str] = None,
|
|
477
|
-
message: Optional[str] = None
|
|
478
|
-
) -> Dict[str, Any]:
|
|
479
|
-
"""
|
|
480
|
-
Share a music track
|
|
481
|
-
|
|
482
|
-
Args:
|
|
483
|
-
job_id: ID of the music job
|
|
484
|
-
platform: Platform to share on
|
|
485
|
-
message: Optional message to include
|
|
486
|
-
|
|
487
|
-
Returns:
|
|
488
|
-
Share response with shareable URL
|
|
489
|
-
"""
|
|
490
|
-
data = {}
|
|
491
|
-
if platform:
|
|
492
|
-
data["platform"] = platform
|
|
493
|
-
if message:
|
|
494
|
-
data["message"] = message
|
|
495
|
-
|
|
496
|
-
if self.async_mode:
|
|
497
|
-
return self._async_share_music_track(job_id, data)
|
|
498
|
-
else:
|
|
499
|
-
return self.client.request("POST", f"/api/v1/music/jobs/{job_id}/share", data=data)
|
|
500
|
-
|
|
501
|
-
async def _async_share_music_track(self, job_id: int, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
502
|
-
"""Async version of share_music_track"""
|
|
503
|
-
return await self.client.request("POST", f"/api/v1/music/jobs/{job_id}/share", data=data)
|
|
504
|
-
|
|
505
|
-
def delete_music_job(self, job_id: int) -> Dict[str, str]:
|
|
506
|
-
"""
|
|
507
|
-
Delete a music generation job
|
|
508
|
-
|
|
509
|
-
Args:
|
|
510
|
-
job_id: ID of the music job
|
|
511
|
-
|
|
512
|
-
Returns:
|
|
513
|
-
Deletion confirmation
|
|
514
|
-
"""
|
|
157
|
+
return self._async_list_jobs(params)
|
|
158
|
+
return self.client.request("GET", "/api/v1/music/jobs", params=params)
|
|
159
|
+
|
|
160
|
+
async def _async_list_jobs(self, params: Dict) -> List[Dict[str, Any]]:
|
|
161
|
+
return await self.client.request("GET", "/api/v1/music/jobs", params=params)
|
|
162
|
+
|
|
163
|
+
def delete_job(self, job_id: int) -> Dict[str, str]:
|
|
164
|
+
"""Delete a music generation job."""
|
|
515
165
|
if self.async_mode:
|
|
516
|
-
return self.
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
async def _async_delete_music_job(self, job_id: int) -> Dict[str, str]:
|
|
521
|
-
"""Async version of delete_music_job"""
|
|
166
|
+
return self._async_delete_job(job_id)
|
|
167
|
+
return self.client.request("DELETE", f"/api/v1/music/jobs/{job_id}")
|
|
168
|
+
|
|
169
|
+
async def _async_delete_job(self, job_id: int) -> Dict[str, str]:
|
|
522
170
|
return await self.client.request("DELETE", f"/api/v1/music/jobs/{job_id}")
|
|
171
|
+
|
|
172
|
+
def get_presets(self) -> Dict[str, Any]:
|
|
173
|
+
"""Get available genre presets."""
|
|
174
|
+
if self.async_mode:
|
|
175
|
+
return self._async_get_presets()
|
|
176
|
+
return self.client.request("GET", "/api/v1/music/presets")
|
|
177
|
+
|
|
178
|
+
async def _async_get_presets(self) -> Dict[str, Any]:
|
|
179
|
+
return await self.client.request("GET", "/api/v1/music/presets")
|
|
180
|
+
|
|
181
|
+
def _wait_for_music(self, job_id: int, timeout: int) -> Dict[str, Any]:
|
|
182
|
+
"""Wait for music generation job completion."""
|
|
183
|
+
import time
|
|
184
|
+
start_time = time.time()
|
|
185
|
+
|
|
186
|
+
while time.time() - start_time < timeout:
|
|
187
|
+
job = self.get_job(job_id)
|
|
188
|
+
status = job.get("status", "").upper()
|
|
189
|
+
|
|
190
|
+
if status == "COMPLETED":
|
|
191
|
+
return job
|
|
192
|
+
elif status in ("FAILED", "ERROR"):
|
|
193
|
+
raise Exception(f"Music generation failed: {job.get('error_message', 'Unknown error')}")
|
|
194
|
+
|
|
195
|
+
time.sleep(5)
|
|
196
|
+
|
|
197
|
+
raise TimeoutError(f"Music generation {job_id} did not complete within {timeout} seconds")
|
|
198
|
+
|
|
199
|
+
async def _async_wait_for_music(self, job_id: int, timeout: int) -> Dict[str, Any]:
|
|
200
|
+
"""Async wait for music generation job completion."""
|
|
201
|
+
import asyncio
|
|
202
|
+
import time
|
|
203
|
+
start_time = time.time()
|
|
204
|
+
|
|
205
|
+
while time.time() - start_time < timeout:
|
|
206
|
+
job = await self.get_job(job_id)
|
|
207
|
+
status = job.get("status", "").upper()
|
|
208
|
+
|
|
209
|
+
if status == "COMPLETED":
|
|
210
|
+
return job
|
|
211
|
+
elif status in ("FAILED", "ERROR"):
|
|
212
|
+
raise Exception(f"Music generation failed: {job.get('error_message', 'Unknown error')}")
|
|
213
|
+
|
|
214
|
+
await asyncio.sleep(5)
|
|
215
|
+
|
|
216
|
+
raise TimeoutError(f"Music generation {job_id} did not complete within {timeout} seconds")
|
|
217
|
+
|