audiopod 1.2.0__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.
@@ -1,53 +1,134 @@
1
1
  """
2
- Speaker Service - Speaker analysis and diarization
2
+ Speaker Service - Speaker diarization and extraction
3
3
  """
4
4
 
5
- from typing import Optional, Union
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 analysis"""
12
-
13
- def diarize_speakers(
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
- ) -> Union[Job, SpeakerAnalysisResult]:
20
- """Identify and separate speakers in audio"""
21
- files = self._prepare_file_upload(audio_file, "file")
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(files, data, wait_for_completion, timeout)
28
- else:
29
- response = self.client.request(
30
- "POST", "/api/v1/speaker/diarize",
31
- data=data, files=files
32
- )
33
- job = Job.from_dict(response)
34
-
35
- if wait_for_completion:
36
- completed_job = self._wait_for_completion(job.id, timeout)
37
- return SpeakerAnalysisResult.from_dict(completed_job.result or completed_job.__dict__)
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
- completed_job = await self._async_wait_for_completion(job.id, timeout)
51
- return SpeakerAnalysisResult.from_dict(completed_job.result or completed_job.__dict__)
52
-
53
- return job
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,168 @@
1
1
  """
2
- Stem Extraction Service - Audio stem separation operations
2
+ Stem Extraction Service - Audio stem separation
3
3
  """
4
4
 
5
- from typing import List, Optional, Dict, Any, Union
5
+ from typing import List, Optional, Dict, Any
6
6
  from .base import BaseService
7
- from ..models import Job
8
7
  from ..exceptions import ValidationError
9
8
 
10
9
 
11
10
  class StemExtractionService(BaseService):
12
- """Service for audio stem extraction operations"""
13
-
11
+ """
12
+ Service for audio stem separation.
13
+
14
+ Example:
15
+ ```python
16
+ from audiopod import Client
17
+
18
+ client = Client()
19
+
20
+ # Extract all stems
21
+ job = client.stem_extraction.extract_stems(
22
+ audio_file="song.mp3",
23
+ stem_types=["vocals", "drums", "bass", "other"],
24
+ wait_for_completion=True
25
+ )
26
+
27
+ # Download stems
28
+ for stem_name, url in job["download_urls"].items():
29
+ print(f"{stem_name}: {url}")
30
+ ```
31
+ """
32
+
14
33
  def extract_stems(
15
34
  self,
16
35
  audio_file: Optional[str] = None,
17
36
  url: Optional[str] = None,
18
- stem_types: List[str] = None,
37
+ stem_types: Optional[List[str]] = None,
19
38
  model_name: str = "htdemucs",
20
39
  two_stems_mode: Optional[str] = None,
21
40
  wait_for_completion: bool = False,
22
- timeout: int = 900
23
- ) -> Job:
41
+ timeout: int = 900,
42
+ ) -> Dict[str, Any]:
24
43
  """
25
- Extract stems from audio file
26
-
44
+ Extract stems from audio.
45
+
27
46
  Args:
28
- audio_file: Path to audio file to process
29
- url: URL of audio file to process (alternative to audio_file)
30
- stem_types: List of stems to extract (e.g., ['vocals', 'drums', 'bass', 'other'])
31
- model_name: Model to use for separation ('htdemucs' or 'htdemucs_6s')
32
- two_stems_mode: Two-stem mode for vocals/instrumental separation
33
- wait_for_completion: Whether to wait for completion
34
- timeout: Maximum time to wait
35
-
47
+ audio_file: Path to local audio file
48
+ url: URL of audio file (alternative to audio_file)
49
+ stem_types: Stems to extract (e.g., ["vocals", "drums", "bass", "other"])
50
+ model_name: Model to use ("htdemucs" or "htdemucs_6s")
51
+ two_stems_mode: Two-stem mode ("vocals", "drums", or "bass")
52
+ wait_for_completion: Wait for job to complete
53
+ timeout: Max wait time in seconds
54
+
36
55
  Returns:
37
- Job object with stem extraction details
56
+ Job dict with id, status, download_urls (when completed)
38
57
  """
39
58
  if not audio_file and not url:
40
- raise ValidationError("Either audio_file or url must be provided")
41
-
59
+ raise ValidationError("Provide audio_file or url")
60
+
42
61
  if audio_file and url:
43
- raise ValidationError("Provide either audio_file or url, not both")
44
-
45
- # Set default stem types based on model
62
+ raise ValidationError("Provide audio_file or url, not both")
63
+
46
64
  if stem_types is None:
47
- if model_name == "htdemucs_6s":
48
- stem_types = ["vocals", "drums", "bass", "other", "piano", "guitar"]
49
- else:
50
- stem_types = ["vocals", "drums", "bass", "other"]
51
-
52
- # Validate model name
53
- if model_name not in ["htdemucs", "htdemucs_6s"]:
54
- raise ValidationError("Model name must be 'htdemucs' or 'htdemucs_6s'")
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
-
65
+ stem_types = (
66
+ ["vocals", "drums", "bass", "other", "piano", "guitar"]
67
+ if model_name == "htdemucs_6s"
68
+ else ["vocals", "drums", "bass", "other"]
69
+ )
70
+
71
+ data = {"stem_types": str(stem_types), "model_name": model_name}
72
+
66
73
  if url:
67
74
  data["url"] = url
68
-
75
+
69
76
  if two_stems_mode:
70
77
  data["two_stems_mode"] = two_stems_mode
71
-
78
+
79
+ files = self._prepare_file_upload(audio_file, "file") if audio_file else None
80
+
72
81
  if self.async_mode:
73
- return self._async_extract_stems(files, data, wait_for_completion, timeout)
74
- else:
75
- response = self.client.request(
76
- "POST",
77
- "/api/v1/stem-extraction/extract",
78
- data=data,
79
- files=files if files else None
80
- )
81
-
82
- job = Job.from_dict(response)
83
-
84
- if wait_for_completion:
85
- return self._wait_for_completion(job.id, timeout)
86
-
87
- return job
88
-
82
+ return self._async_extract_stems(data, files, wait_for_completion, timeout)
83
+
84
+ response = self.client.request(
85
+ "POST", "/api/v1/stem-extraction/extract", data=data, files=files
86
+ )
87
+
88
+ if wait_for_completion:
89
+ return self._wait_for_stem_job(response["id"], timeout)
90
+
91
+ return response
92
+
89
93
  async def _async_extract_stems(
90
94
  self,
91
- files: Dict[str, Any],
92
95
  data: Dict[str, Any],
96
+ files: Optional[Dict[str, Any]],
93
97
  wait_for_completion: bool,
94
- timeout: int
95
- ) -> Job:
96
- """Async version of extract_stems"""
98
+ timeout: int,
99
+ ) -> Dict[str, Any]:
97
100
  response = await self.client.request(
98
- "POST",
99
- "/api/v1/stem-extraction/extract",
100
- data=data,
101
- files=files if files else None
101
+ "POST", "/api/v1/stem-extraction/extract", data=data, files=files
102
102
  )
103
-
104
- job = Job.from_dict(response)
105
-
106
103
  if wait_for_completion:
107
- return await self._async_wait_for_completion(job.id, timeout)
108
-
109
- return job
110
-
111
- def get_stem_job(self, job_id: int) -> Job:
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
- """
104
+ return await self._async_wait_for_stem_job(response["id"], timeout)
105
+ return response
106
+
107
+ def get_job(self, job_id: int) -> Dict[str, Any]:
108
+ """Get stem extraction job status."""
121
109
  if self.async_mode:
122
- return self._async_get_stem_job(job_id)
123
- else:
124
- response = self.client.request("GET", f"/api/v1/stem-extraction/status/{job_id}")
125
- return Job.from_dict(response)
126
-
127
- async def _async_get_stem_job(self, job_id: int) -> Job:
128
- """Async version of get_stem_job"""
129
- response = await self.client.request("GET", f"/api/v1/stem-extraction/status/{job_id}")
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
-
110
+ return self._async_get_job(job_id)
111
+ return self.client.request("GET", f"/api/v1/stem-extraction/status/{job_id}")
112
+
113
+ async def _async_get_job(self, job_id: int) -> Dict[str, Any]:
114
+ return await self.client.request("GET", f"/api/v1/stem-extraction/status/{job_id}")
115
+
116
+ def list_jobs(self, skip: int = 0, limit: int = 50) -> List[Dict[str, Any]]:
117
+ """List stem extraction jobs."""
152
118
  if self.async_mode:
153
- return self._async_list_stem_jobs(params)
154
- else:
155
- response = self.client.request("GET", "/api/v1/stem-extraction/jobs", params=params)
156
- return [Job.from_dict(job_data) for job_data in response]
157
-
158
- async def _async_list_stem_jobs(self, params: Dict[str, Any]) -> List[Job]:
159
- """Async version of list_stem_jobs"""
160
- response = await self.client.request("GET", "/api/v1/stem-extraction/jobs", params=params)
161
- return [Job.from_dict(job_data) for job_data in response]
162
-
163
- def delete_stem_job(self, job_id: int) -> Dict[str, str]:
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
- """
119
+ return self._async_list_jobs(skip, limit)
120
+ return self.client.request(
121
+ "GET", "/api/v1/stem-extraction/jobs", params={"skip": skip, "limit": limit}
122
+ )
123
+
124
+ async def _async_list_jobs(self, skip: int, limit: int) -> List[Dict[str, Any]]:
125
+ return await self.client.request(
126
+ "GET", "/api/v1/stem-extraction/jobs", params={"skip": skip, "limit": limit}
127
+ )
128
+
129
+ def delete_job(self, job_id: int) -> Dict[str, str]:
130
+ """Delete a stem extraction job."""
173
131
  if self.async_mode:
174
- return self._async_delete_stem_job(job_id)
175
- else:
176
- return self.client.request("DELETE", f"/api/v1/stem-extraction/jobs/{job_id}")
177
-
178
- async def _async_delete_stem_job(self, job_id: int) -> Dict[str, str]:
179
- """Async version of delete_stem_job"""
132
+ return self._async_delete_job(job_id)
133
+ return self.client.request("DELETE", f"/api/v1/stem-extraction/jobs/{job_id}")
134
+
135
+ async def _async_delete_job(self, job_id: int) -> Dict[str, str]:
180
136
  return await self.client.request("DELETE", f"/api/v1/stem-extraction/jobs/{job_id}")
137
+
138
+ def _wait_for_stem_job(self, job_id: int, timeout: int = 900) -> Dict[str, Any]:
139
+ """Wait for stem job completion."""
140
+ import time
141
+
142
+ start = time.time()
143
+ while time.time() - start < timeout:
144
+ job = self.get_job(job_id)
145
+ status = job.get("status", "").upper()
146
+ if status == "COMPLETED":
147
+ return job
148
+ elif status in ["FAILED", "ERROR"]:
149
+ raise Exception(f"Job failed: {job.get('error_message', 'Unknown')}")
150
+ time.sleep(5)
151
+ raise TimeoutError(f"Job {job_id} timed out after {timeout}s")
152
+
153
+ async def _async_wait_for_stem_job(self, job_id: int, timeout: int = 900) -> Dict[str, Any]:
154
+ """Async wait for stem job completion."""
155
+ import asyncio
156
+ import time
157
+
158
+ start = time.time()
159
+ while time.time() - start < timeout:
160
+ job = await self.get_job(job_id)
161
+ status = job.get("status", "").upper()
162
+ if status == "COMPLETED":
163
+ return job
164
+ elif status in ["FAILED", "ERROR"]:
165
+ raise Exception(f"Job failed: {job.get('error_message', 'Unknown')}")
166
+ await asyncio.sleep(5)
167
+ raise TimeoutError(f"Job {job_id} timed out after {timeout}s")
168
+