audiopod 2.1.0__py3-none-any.whl → 2.2.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 +12 -13
- audiopod/client.py +238 -124
- audiopod/config.py +17 -0
- audiopod/exceptions.py +19 -27
- audiopod/services/__init__.py +30 -0
- audiopod/services/base.py +69 -0
- audiopod/services/credits.py +42 -0
- audiopod/services/denoiser.py +136 -0
- audiopod/services/music.py +217 -0
- audiopod/services/speaker.py +134 -0
- audiopod/services/stem_extraction.py +287 -0
- audiopod/services/transcription.py +210 -0
- audiopod/services/translation.py +135 -0
- audiopod/services/video.py +329 -0
- audiopod/services/voice.py +187 -0
- audiopod/services/wallet.py +235 -0
- audiopod-2.2.0.dist-info/METADATA +206 -0
- audiopod-2.2.0.dist-info/RECORD +21 -0
- {audiopod-2.1.0.dist-info → audiopod-2.2.0.dist-info}/WHEEL +1 -1
- audiopod/resources/__init__.py +0 -23
- audiopod/resources/denoiser.py +0 -116
- audiopod/resources/music.py +0 -166
- audiopod/resources/speaker.py +0 -132
- audiopod/resources/stems.py +0 -267
- audiopod/resources/transcription.py +0 -205
- audiopod/resources/voice.py +0 -139
- audiopod/resources/wallet.py +0 -110
- audiopod-2.1.0.dist-info/METADATA +0 -205
- audiopod-2.1.0.dist-info/RECORD +0 -16
- {audiopod-2.1.0.dist-info → audiopod-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {audiopod-2.1.0.dist-info → audiopod-2.2.0.dist-info}/top_level.txt +0 -0
audiopod/resources/music.py
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Music Service - AI Music Generation
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
from typing import Optional, List, Dict, Any, Literal, TYPE_CHECKING
|
|
7
|
-
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from ..client import AudioPod
|
|
10
|
-
|
|
11
|
-
POLL_INTERVAL = 5 # seconds
|
|
12
|
-
DEFAULT_TIMEOUT = 600 # 10 minutes
|
|
13
|
-
|
|
14
|
-
MusicTask = Literal[
|
|
15
|
-
"text2music",
|
|
16
|
-
"prompt2instrumental",
|
|
17
|
-
"lyric2vocals",
|
|
18
|
-
"text2rap",
|
|
19
|
-
"text2samples",
|
|
20
|
-
"audio2audio",
|
|
21
|
-
"songbloom",
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class Music:
|
|
26
|
-
"""AI music generation service."""
|
|
27
|
-
|
|
28
|
-
def __init__(self, client: "AudioPod"):
|
|
29
|
-
self._client = client
|
|
30
|
-
|
|
31
|
-
def create(
|
|
32
|
-
self,
|
|
33
|
-
prompt: str,
|
|
34
|
-
*,
|
|
35
|
-
lyrics: Optional[str] = None,
|
|
36
|
-
duration: int = 30,
|
|
37
|
-
task: Optional[MusicTask] = None,
|
|
38
|
-
genre: Optional[str] = None,
|
|
39
|
-
display_name: Optional[str] = None,
|
|
40
|
-
) -> Dict[str, Any]:
|
|
41
|
-
"""
|
|
42
|
-
Generate music from text.
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
prompt: Text prompt describing the music
|
|
46
|
-
lyrics: Lyrics (for vocal tracks)
|
|
47
|
-
duration: Duration in seconds (default: 30)
|
|
48
|
-
task: Task type (auto-detected based on lyrics if not specified)
|
|
49
|
-
genre: Genre preset
|
|
50
|
-
display_name: Display name for the track
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
Music job object
|
|
54
|
-
|
|
55
|
-
Example:
|
|
56
|
-
>>> job = client.music.create(
|
|
57
|
-
... prompt="Upbeat electronic dance music",
|
|
58
|
-
... duration=30,
|
|
59
|
-
... )
|
|
60
|
-
"""
|
|
61
|
-
if task is None:
|
|
62
|
-
task = "text2music" if lyrics else "prompt2instrumental"
|
|
63
|
-
|
|
64
|
-
endpoint = f"/api/v1/music/{task}"
|
|
65
|
-
|
|
66
|
-
return self._client.post(
|
|
67
|
-
endpoint,
|
|
68
|
-
json_data={
|
|
69
|
-
"prompt": prompt,
|
|
70
|
-
"lyrics": lyrics,
|
|
71
|
-
"audio_duration": duration,
|
|
72
|
-
"display_name": display_name,
|
|
73
|
-
"genre_preset": genre,
|
|
74
|
-
},
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
def instrumental(
|
|
78
|
-
self, prompt: str, duration: int = 30
|
|
79
|
-
) -> Dict[str, Any]:
|
|
80
|
-
"""Generate instrumental music."""
|
|
81
|
-
return self.create(prompt, duration=duration, task="prompt2instrumental")
|
|
82
|
-
|
|
83
|
-
def song(
|
|
84
|
-
self, prompt: str, lyrics: str, duration: int = 60
|
|
85
|
-
) -> Dict[str, Any]:
|
|
86
|
-
"""Generate a song with vocals."""
|
|
87
|
-
return self.create(prompt, lyrics=lyrics, duration=duration, task="text2music")
|
|
88
|
-
|
|
89
|
-
def rap(
|
|
90
|
-
self, prompt: str, lyrics: str, duration: int = 60
|
|
91
|
-
) -> Dict[str, Any]:
|
|
92
|
-
"""Generate rap music."""
|
|
93
|
-
return self.create(prompt, lyrics=lyrics, duration=duration, task="text2rap")
|
|
94
|
-
|
|
95
|
-
def get(self, job_id: int) -> Dict[str, Any]:
|
|
96
|
-
"""Get a music job by ID."""
|
|
97
|
-
return self._client.get(f"/api/v1/music/jobs/{job_id}/status")
|
|
98
|
-
|
|
99
|
-
def list(
|
|
100
|
-
self,
|
|
101
|
-
*,
|
|
102
|
-
skip: int = 0,
|
|
103
|
-
limit: int = 50,
|
|
104
|
-
task: Optional[MusicTask] = None,
|
|
105
|
-
status: Optional[str] = None,
|
|
106
|
-
) -> List[Dict[str, Any]]:
|
|
107
|
-
"""List music jobs."""
|
|
108
|
-
return self._client.get(
|
|
109
|
-
"/api/v1/music/jobs",
|
|
110
|
-
params={"skip": skip, "limit": limit, "task": task, "status": status},
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
def delete(self, job_id: int) -> None:
|
|
114
|
-
"""Delete a music job."""
|
|
115
|
-
self._client.delete(f"/api/v1/music/jobs/{job_id}")
|
|
116
|
-
|
|
117
|
-
def wait_for_completion(
|
|
118
|
-
self, job_id: int, timeout: int = DEFAULT_TIMEOUT
|
|
119
|
-
) -> Dict[str, Any]:
|
|
120
|
-
"""Wait for music generation to complete."""
|
|
121
|
-
start_time = time.time()
|
|
122
|
-
|
|
123
|
-
while time.time() - start_time < timeout:
|
|
124
|
-
job = self.get(job_id)
|
|
125
|
-
status = job.get("status", "").upper()
|
|
126
|
-
|
|
127
|
-
if status == "COMPLETED":
|
|
128
|
-
return job
|
|
129
|
-
if status == "FAILED":
|
|
130
|
-
raise RuntimeError(
|
|
131
|
-
f"Music generation failed: {job.get('error_message', 'Unknown error')}"
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
time.sleep(POLL_INTERVAL)
|
|
135
|
-
|
|
136
|
-
raise TimeoutError(f"Music generation timed out after {timeout}s")
|
|
137
|
-
|
|
138
|
-
def generate(
|
|
139
|
-
self,
|
|
140
|
-
prompt: str,
|
|
141
|
-
*,
|
|
142
|
-
lyrics: Optional[str] = None,
|
|
143
|
-
duration: int = 30,
|
|
144
|
-
task: Optional[MusicTask] = None,
|
|
145
|
-
timeout: int = DEFAULT_TIMEOUT,
|
|
146
|
-
) -> Dict[str, Any]:
|
|
147
|
-
"""
|
|
148
|
-
Generate music and wait for completion.
|
|
149
|
-
|
|
150
|
-
Example:
|
|
151
|
-
>>> result = client.music.generate(
|
|
152
|
-
... prompt="Upbeat electronic dance music",
|
|
153
|
-
... duration=30,
|
|
154
|
-
... )
|
|
155
|
-
>>> print(result["output_url"])
|
|
156
|
-
"""
|
|
157
|
-
job = self.create(prompt, lyrics=lyrics, duration=duration, task=task)
|
|
158
|
-
return self.wait_for_completion(job["id"], timeout=timeout)
|
|
159
|
-
|
|
160
|
-
def get_presets(self) -> Dict[str, Any]:
|
|
161
|
-
"""Get available genre presets."""
|
|
162
|
-
return self._client.get("/api/v1/music/presets")
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
audiopod/resources/speaker.py
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Speaker Service - Speaker Diarization & Separation
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
from typing import Optional, List, Dict, Any, TYPE_CHECKING
|
|
7
|
-
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from ..client import AudioPod
|
|
10
|
-
|
|
11
|
-
POLL_INTERVAL = 3 # seconds
|
|
12
|
-
DEFAULT_TIMEOUT = 600 # 10 minutes
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class Speaker:
|
|
16
|
-
"""Speaker diarization and separation service."""
|
|
17
|
-
|
|
18
|
-
def __init__(self, client: "AudioPod"):
|
|
19
|
-
self._client = client
|
|
20
|
-
|
|
21
|
-
def diarize(
|
|
22
|
-
self,
|
|
23
|
-
*,
|
|
24
|
-
file: Optional[str] = None,
|
|
25
|
-
url: Optional[str] = None,
|
|
26
|
-
num_speakers: Optional[int] = None,
|
|
27
|
-
) -> Dict[str, Any]:
|
|
28
|
-
"""
|
|
29
|
-
Create a speaker diarization job.
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
file: Path to audio file
|
|
33
|
-
url: URL of audio/video
|
|
34
|
-
num_speakers: Number of speakers (hint for better accuracy)
|
|
35
|
-
|
|
36
|
-
Returns:
|
|
37
|
-
Speaker job object
|
|
38
|
-
|
|
39
|
-
Example:
|
|
40
|
-
>>> job = client.speaker.diarize(
|
|
41
|
-
... file="./meeting.mp3",
|
|
42
|
-
... num_speakers=3,
|
|
43
|
-
... )
|
|
44
|
-
"""
|
|
45
|
-
if file:
|
|
46
|
-
return self._client.upload(
|
|
47
|
-
"/api/v1/speaker/diarize",
|
|
48
|
-
file,
|
|
49
|
-
field_name="file",
|
|
50
|
-
additional_fields={"num_speakers": num_speakers},
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
if url:
|
|
54
|
-
return self._client.post(
|
|
55
|
-
"/api/v1/speaker/diarize",
|
|
56
|
-
json_data={"url": url, "num_speakers": num_speakers},
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
raise ValueError("Either file or url must be provided")
|
|
60
|
-
|
|
61
|
-
def get(self, job_id: int) -> Dict[str, Any]:
|
|
62
|
-
"""Get a speaker job by ID."""
|
|
63
|
-
return self._client.get(f"/api/v1/speaker/jobs/{job_id}")
|
|
64
|
-
|
|
65
|
-
def list(
|
|
66
|
-
self,
|
|
67
|
-
*,
|
|
68
|
-
skip: int = 0,
|
|
69
|
-
limit: int = 50,
|
|
70
|
-
status: Optional[str] = None,
|
|
71
|
-
job_type: Optional[str] = None,
|
|
72
|
-
) -> List[Dict[str, Any]]:
|
|
73
|
-
"""List speaker jobs."""
|
|
74
|
-
return self._client.get(
|
|
75
|
-
"/api/v1/speaker/jobs",
|
|
76
|
-
params={
|
|
77
|
-
"skip": skip,
|
|
78
|
-
"limit": limit,
|
|
79
|
-
"status": status,
|
|
80
|
-
"job_type": job_type,
|
|
81
|
-
},
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
def delete(self, job_id: int) -> None:
|
|
85
|
-
"""Delete a speaker job."""
|
|
86
|
-
self._client.delete(f"/api/v1/speaker/jobs/{job_id}")
|
|
87
|
-
|
|
88
|
-
def wait_for_completion(
|
|
89
|
-
self, job_id: int, timeout: int = DEFAULT_TIMEOUT
|
|
90
|
-
) -> Dict[str, Any]:
|
|
91
|
-
"""Wait for speaker job to complete."""
|
|
92
|
-
start_time = time.time()
|
|
93
|
-
|
|
94
|
-
while time.time() - start_time < timeout:
|
|
95
|
-
job = self.get(job_id)
|
|
96
|
-
status = job.get("status", "")
|
|
97
|
-
|
|
98
|
-
if status == "COMPLETED":
|
|
99
|
-
return job
|
|
100
|
-
if status == "FAILED":
|
|
101
|
-
raise RuntimeError(
|
|
102
|
-
f"Speaker processing failed: {job.get('error_message', 'Unknown error')}"
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
time.sleep(POLL_INTERVAL)
|
|
106
|
-
|
|
107
|
-
raise TimeoutError(f"Speaker processing timed out after {timeout}s")
|
|
108
|
-
|
|
109
|
-
def identify(
|
|
110
|
-
self,
|
|
111
|
-
*,
|
|
112
|
-
file: Optional[str] = None,
|
|
113
|
-
url: Optional[str] = None,
|
|
114
|
-
num_speakers: Optional[int] = None,
|
|
115
|
-
timeout: int = DEFAULT_TIMEOUT,
|
|
116
|
-
) -> Dict[str, Any]:
|
|
117
|
-
"""
|
|
118
|
-
Diarize audio and wait for completion.
|
|
119
|
-
|
|
120
|
-
Example:
|
|
121
|
-
>>> result = client.speaker.identify(
|
|
122
|
-
... file="./meeting.mp3",
|
|
123
|
-
... num_speakers=3,
|
|
124
|
-
... )
|
|
125
|
-
>>> print(result["segments"])
|
|
126
|
-
"""
|
|
127
|
-
job = self.diarize(file=file, url=url, num_speakers=num_speakers)
|
|
128
|
-
return self.wait_for_completion(job["id"], timeout=timeout)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
audiopod/resources/stems.py
DELETED
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Stem Extraction Service - Audio Stem Separation
|
|
3
|
-
|
|
4
|
-
Simple mode-based API for separating audio into individual stems.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import time
|
|
8
|
-
from typing import Optional, Dict, Any, Literal, TYPE_CHECKING
|
|
9
|
-
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from ..client import AudioPod
|
|
12
|
-
|
|
13
|
-
POLL_INTERVAL = 3 # seconds
|
|
14
|
-
DEFAULT_TIMEOUT = 600 # 10 minutes
|
|
15
|
-
|
|
16
|
-
# Separation modes
|
|
17
|
-
StemMode = Literal["single", "two", "four", "six", "producer", "studio", "mastering"]
|
|
18
|
-
|
|
19
|
-
# Single stem options
|
|
20
|
-
SingleStem = Literal["vocals", "drums", "bass", "guitar", "piano", "other"]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class StemExtraction:
|
|
24
|
-
"""
|
|
25
|
-
Audio stem separation service.
|
|
26
|
-
|
|
27
|
-
Separate audio into individual components using simple mode selection.
|
|
28
|
-
|
|
29
|
-
Modes:
|
|
30
|
-
- single: Extract one stem (requires stem parameter)
|
|
31
|
-
- two: Vocals + Instrumental
|
|
32
|
-
- four: Vocals, Drums, Bass, Other
|
|
33
|
-
- six: Vocals, Drums, Bass, Guitar, Piano, Other
|
|
34
|
-
- producer: 8 stems with drum kit decomposition
|
|
35
|
-
- studio: 12 stems for professional mixing
|
|
36
|
-
- mastering: 16 stems maximum detail
|
|
37
|
-
|
|
38
|
-
Example:
|
|
39
|
-
>>> client = AudioPod(api_key="ap_...")
|
|
40
|
-
>>>
|
|
41
|
-
>>> # Six-stem separation from YouTube
|
|
42
|
-
>>> job = client.stems.extract(
|
|
43
|
-
... url="https://youtube.com/watch?v=VIDEO_ID",
|
|
44
|
-
... mode="six"
|
|
45
|
-
... )
|
|
46
|
-
>>>
|
|
47
|
-
>>> # Wait for completion
|
|
48
|
-
>>> result = client.stems.wait_for_completion(job["id"])
|
|
49
|
-
>>> print(result["download_urls"])
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
def __init__(self, client: "AudioPod"):
|
|
53
|
-
self._client = client
|
|
54
|
-
|
|
55
|
-
def extract(
|
|
56
|
-
self,
|
|
57
|
-
*,
|
|
58
|
-
file: Optional[str] = None,
|
|
59
|
-
url: Optional[str] = None,
|
|
60
|
-
mode: StemMode = "four",
|
|
61
|
-
stem: Optional[SingleStem] = None,
|
|
62
|
-
) -> Dict[str, Any]:
|
|
63
|
-
"""
|
|
64
|
-
Extract stems from audio using simple mode selection.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
file: Path to local audio file (MP3, WAV, FLAC, M4A, OGG)
|
|
68
|
-
url: URL of audio/video (YouTube, SoundCloud, direct link)
|
|
69
|
-
mode: Separation mode:
|
|
70
|
-
- "single": Extract one stem (specify stem param)
|
|
71
|
-
- "two": Vocals + Instrumental
|
|
72
|
-
- "four": Vocals, Drums, Bass, Other (default)
|
|
73
|
-
- "six": Vocals, Drums, Bass, Guitar, Piano, Other
|
|
74
|
-
- "producer": 8 stems with kick, snare, hihat
|
|
75
|
-
- "studio": 12 stems for professional mixing
|
|
76
|
-
- "mastering": 16 stems maximum detail
|
|
77
|
-
stem: For mode="single", which stem to extract:
|
|
78
|
-
vocals, drums, bass, guitar, piano, other
|
|
79
|
-
|
|
80
|
-
Returns:
|
|
81
|
-
Job object with id, status, task_id
|
|
82
|
-
|
|
83
|
-
Raises:
|
|
84
|
-
ValueError: If neither file nor url provided, or missing stem for single mode
|
|
85
|
-
|
|
86
|
-
Example:
|
|
87
|
-
>>> # From URL with 6 stems
|
|
88
|
-
>>> job = client.stems.extract(
|
|
89
|
-
... url="https://youtube.com/watch?v=VIDEO_ID",
|
|
90
|
-
... mode="six"
|
|
91
|
-
... )
|
|
92
|
-
>>>
|
|
93
|
-
>>> # From file with 4 stems
|
|
94
|
-
>>> job = client.stems.extract(
|
|
95
|
-
... file="./song.mp3",
|
|
96
|
-
... mode="four"
|
|
97
|
-
... )
|
|
98
|
-
>>>
|
|
99
|
-
>>> # Extract only vocals
|
|
100
|
-
>>> job = client.stems.extract(
|
|
101
|
-
... url="https://youtube.com/watch?v=VIDEO_ID",
|
|
102
|
-
... mode="single",
|
|
103
|
-
... stem="vocals"
|
|
104
|
-
... )
|
|
105
|
-
"""
|
|
106
|
-
if not file and not url:
|
|
107
|
-
raise ValueError("Either file or url must be provided")
|
|
108
|
-
|
|
109
|
-
if mode == "single" and not stem:
|
|
110
|
-
raise ValueError(
|
|
111
|
-
"stem parameter required for mode='single'. Options: vocals, drums, bass, guitar, piano, other"
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
# Build form data
|
|
115
|
-
data = {"mode": mode}
|
|
116
|
-
if stem:
|
|
117
|
-
data["stem"] = stem
|
|
118
|
-
|
|
119
|
-
if file:
|
|
120
|
-
return self._client.upload(
|
|
121
|
-
"/api/v1/stem-extraction/api/extract",
|
|
122
|
-
file,
|
|
123
|
-
field_name="file",
|
|
124
|
-
additional_fields=data,
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
if url:
|
|
128
|
-
data["url"] = url
|
|
129
|
-
return self._client.post(
|
|
130
|
-
"/api/v1/stem-extraction/api/extract",
|
|
131
|
-
data=data,
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
raise ValueError("Either file or url must be provided")
|
|
135
|
-
|
|
136
|
-
def status(self, job_id: int) -> Dict[str, Any]:
|
|
137
|
-
"""
|
|
138
|
-
Get the status of a stem extraction job.
|
|
139
|
-
|
|
140
|
-
Args:
|
|
141
|
-
job_id: The job ID returned from extract()
|
|
142
|
-
|
|
143
|
-
Returns:
|
|
144
|
-
Job status with download_urls when completed
|
|
145
|
-
|
|
146
|
-
Example:
|
|
147
|
-
>>> status = client.stems.status(5512)
|
|
148
|
-
>>> if status["status"] == "COMPLETED":
|
|
149
|
-
... print(status["download_urls"])
|
|
150
|
-
"""
|
|
151
|
-
return self._client.get(f"/api/v1/stem-extraction/status/{job_id}")
|
|
152
|
-
|
|
153
|
-
def get(self, job_id: int) -> Dict[str, Any]:
|
|
154
|
-
"""Get a stem extraction job by ID."""
|
|
155
|
-
return self._client.get(f"/api/v1/stem-extraction/jobs/{job_id}")
|
|
156
|
-
|
|
157
|
-
def list(
|
|
158
|
-
self,
|
|
159
|
-
*,
|
|
160
|
-
skip: int = 0,
|
|
161
|
-
limit: int = 50,
|
|
162
|
-
status: Optional[str] = None,
|
|
163
|
-
) -> Dict[str, Any]:
|
|
164
|
-
"""
|
|
165
|
-
List stem extraction jobs.
|
|
166
|
-
|
|
167
|
-
Args:
|
|
168
|
-
skip: Number of jobs to skip (pagination)
|
|
169
|
-
limit: Maximum jobs to return (default: 50)
|
|
170
|
-
status: Filter by status (PENDING, PROCESSING, COMPLETED, FAILED)
|
|
171
|
-
|
|
172
|
-
Returns:
|
|
173
|
-
List of stem extraction jobs
|
|
174
|
-
"""
|
|
175
|
-
return self._client.get(
|
|
176
|
-
"/api/v1/stem-extraction/jobs",
|
|
177
|
-
params={"skip": skip, "limit": limit, "status": status},
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
def delete(self, job_id: int) -> None:
|
|
181
|
-
"""Delete a stem extraction job."""
|
|
182
|
-
self._client.delete(f"/api/v1/stem-extraction/jobs/{job_id}")
|
|
183
|
-
|
|
184
|
-
def wait_for_completion(self, job_id: int, timeout: int = DEFAULT_TIMEOUT) -> Dict[str, Any]:
|
|
185
|
-
"""
|
|
186
|
-
Wait for stem extraction to complete.
|
|
187
|
-
|
|
188
|
-
Args:
|
|
189
|
-
job_id: The job ID to wait for
|
|
190
|
-
timeout: Maximum wait time in seconds (default: 600)
|
|
191
|
-
|
|
192
|
-
Returns:
|
|
193
|
-
Completed job with download_urls
|
|
194
|
-
|
|
195
|
-
Raises:
|
|
196
|
-
RuntimeError: If job fails
|
|
197
|
-
TimeoutError: If timeout exceeded
|
|
198
|
-
|
|
199
|
-
Example:
|
|
200
|
-
>>> job = client.stems.extract(url="...", mode="six")
|
|
201
|
-
>>> result = client.stems.wait_for_completion(job["id"])
|
|
202
|
-
>>> for stem, url in result["download_urls"].items():
|
|
203
|
-
... print(f"{stem}: {url}")
|
|
204
|
-
"""
|
|
205
|
-
start_time = time.time()
|
|
206
|
-
|
|
207
|
-
while time.time() - start_time < timeout:
|
|
208
|
-
job = self.status(job_id)
|
|
209
|
-
job_status = job.get("status", "")
|
|
210
|
-
|
|
211
|
-
if job_status == "COMPLETED":
|
|
212
|
-
return job
|
|
213
|
-
if job_status == "FAILED":
|
|
214
|
-
raise RuntimeError(
|
|
215
|
-
f"Stem extraction failed: {job.get('error_message', 'Unknown error')}"
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
time.sleep(POLL_INTERVAL)
|
|
219
|
-
|
|
220
|
-
raise TimeoutError(f"Stem extraction timed out after {timeout}s")
|
|
221
|
-
|
|
222
|
-
def separate(
|
|
223
|
-
self,
|
|
224
|
-
*,
|
|
225
|
-
file: Optional[str] = None,
|
|
226
|
-
url: Optional[str] = None,
|
|
227
|
-
mode: StemMode = "four",
|
|
228
|
-
stem: Optional[SingleStem] = None,
|
|
229
|
-
timeout: int = DEFAULT_TIMEOUT,
|
|
230
|
-
) -> Dict[str, Any]:
|
|
231
|
-
"""
|
|
232
|
-
Extract stems and wait for completion (convenience method).
|
|
233
|
-
|
|
234
|
-
Args:
|
|
235
|
-
file: Path to local audio file
|
|
236
|
-
url: URL of audio/video
|
|
237
|
-
mode: Separation mode (single, two, four, six, producer, studio, mastering)
|
|
238
|
-
stem: For mode="single", which stem to extract
|
|
239
|
-
timeout: Maximum wait time in seconds
|
|
240
|
-
|
|
241
|
-
Returns:
|
|
242
|
-
Completed job with download_urls
|
|
243
|
-
|
|
244
|
-
Example:
|
|
245
|
-
>>> # One-liner: extract and wait
|
|
246
|
-
>>> result = client.stems.separate(
|
|
247
|
-
... url="https://youtube.com/watch?v=VIDEO_ID",
|
|
248
|
-
... mode="six"
|
|
249
|
-
... )
|
|
250
|
-
>>> print(result["download_urls"]["vocals"])
|
|
251
|
-
"""
|
|
252
|
-
job = self.extract(file=file, url=url, mode=mode, stem=stem)
|
|
253
|
-
return self.wait_for_completion(job["id"], timeout=timeout)
|
|
254
|
-
|
|
255
|
-
def modes(self) -> Dict[str, Any]:
|
|
256
|
-
"""
|
|
257
|
-
Get available separation modes.
|
|
258
|
-
|
|
259
|
-
Returns:
|
|
260
|
-
List of available modes with descriptions
|
|
261
|
-
|
|
262
|
-
Example:
|
|
263
|
-
>>> modes = client.stems.modes()
|
|
264
|
-
>>> for m in modes["modes"]:
|
|
265
|
-
... print(f"{m['mode']}: {m['description']}")
|
|
266
|
-
"""
|
|
267
|
-
return self._client.get("/api/v1/stem-extraction/modes")
|