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/cli.py
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
AudioPod CLI - Command Line Interface
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import sys
|
|
7
|
-
import json
|
|
8
|
-
import click
|
|
9
|
-
from typing import Optional
|
|
10
|
-
|
|
11
|
-
from .client import Client
|
|
12
|
-
from .exceptions import AudioPodError
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@click.group()
|
|
16
|
-
@click.option('--api-key', envvar='AUDIOPOD_API_KEY', help='AudioPod API key')
|
|
17
|
-
@click.option('--base-url', default='https://api.audiopod.ai', help='API base URL')
|
|
18
|
-
@click.option('--debug', is_flag=True, help='Enable debug logging')
|
|
19
|
-
@click.pass_context
|
|
20
|
-
def cli(ctx, api_key: str, base_url: str, debug: bool):
|
|
21
|
-
"""AudioPod CLI - Professional Audio Processing powered by AI"""
|
|
22
|
-
ctx.ensure_object(dict)
|
|
23
|
-
|
|
24
|
-
if not api_key:
|
|
25
|
-
click.echo("Error: API key is required. Set AUDIOPOD_API_KEY environment variable or use --api-key option.")
|
|
26
|
-
sys.exit(1)
|
|
27
|
-
|
|
28
|
-
try:
|
|
29
|
-
ctx.obj['client'] = Client(
|
|
30
|
-
api_key=api_key,
|
|
31
|
-
base_url=base_url,
|
|
32
|
-
debug=debug
|
|
33
|
-
)
|
|
34
|
-
except AudioPodError as e:
|
|
35
|
-
click.echo(f"Error: {e.message}")
|
|
36
|
-
sys.exit(1)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@cli.command()
|
|
40
|
-
@click.pass_context
|
|
41
|
-
def health(ctx):
|
|
42
|
-
"""Check API health status"""
|
|
43
|
-
try:
|
|
44
|
-
client = ctx.obj['client']
|
|
45
|
-
status = client.check_health()
|
|
46
|
-
click.echo(f"API Status: {status.get('status', 'Unknown')}")
|
|
47
|
-
except AudioPodError as e:
|
|
48
|
-
click.echo(f"Health check failed: {e.message}")
|
|
49
|
-
sys.exit(1)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@cli.group()
|
|
53
|
-
def credits():
|
|
54
|
-
"""Credit management commands"""
|
|
55
|
-
pass
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
@credits.command('balance')
|
|
59
|
-
@click.pass_context
|
|
60
|
-
def credits_balance(ctx):
|
|
61
|
-
"""Get current credit balance"""
|
|
62
|
-
try:
|
|
63
|
-
client = ctx.obj['client']
|
|
64
|
-
credits = client.credits.get_credit_balance()
|
|
65
|
-
|
|
66
|
-
click.echo("Credit Balance:")
|
|
67
|
-
click.echo(f" Subscription Credits: {credits.balance:,}")
|
|
68
|
-
click.echo(f" Pay-as-you-go Credits: {credits.payg_balance:,}")
|
|
69
|
-
click.echo(f" Total Available: {credits.total_available_credits:,}")
|
|
70
|
-
click.echo(f" Total Used: {credits.total_credits_used:,}")
|
|
71
|
-
if credits.next_reset_date:
|
|
72
|
-
click.echo(f" Next Reset: {credits.next_reset_date}")
|
|
73
|
-
|
|
74
|
-
except AudioPodError as e:
|
|
75
|
-
click.echo(f"Failed to get credit balance: {e.message}")
|
|
76
|
-
sys.exit(1)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
@credits.command('usage')
|
|
80
|
-
@click.pass_context
|
|
81
|
-
def credits_usage(ctx):
|
|
82
|
-
"""Get credit usage history"""
|
|
83
|
-
try:
|
|
84
|
-
client = ctx.obj['client']
|
|
85
|
-
usage = client.credits.get_usage_history()
|
|
86
|
-
|
|
87
|
-
if not usage:
|
|
88
|
-
click.echo("No usage history found.")
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
click.echo("Recent Credit Usage:")
|
|
92
|
-
for record in usage[:10]: # Show last 10 records
|
|
93
|
-
click.echo(f" {record['created_at']}: {record['service_type']} - {record['credits_used']} credits")
|
|
94
|
-
|
|
95
|
-
except AudioPodError as e:
|
|
96
|
-
click.echo(f"Failed to get usage history: {e.message}")
|
|
97
|
-
sys.exit(1)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
@cli.group()
|
|
101
|
-
def voice():
|
|
102
|
-
"""Voice cloning and TTS commands"""
|
|
103
|
-
pass
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
@voice.command('clone')
|
|
107
|
-
@click.argument('voice_file', type=click.Path(exists=True))
|
|
108
|
-
@click.argument('text')
|
|
109
|
-
@click.option('--language', '-l', help='Target language code (e.g., en, es)')
|
|
110
|
-
@click.option('--speed', '-s', default=1.0, help='Speech speed (0.5-2.0)')
|
|
111
|
-
@click.option('--wait', is_flag=True, help='Wait for completion')
|
|
112
|
-
@click.option('--output', '-o', help='Output file path')
|
|
113
|
-
@click.pass_context
|
|
114
|
-
def voice_clone(ctx, voice_file: str, text: str, language: Optional[str],
|
|
115
|
-
speed: float, wait: bool, output: Optional[str]):
|
|
116
|
-
"""Clone a voice from audio file"""
|
|
117
|
-
try:
|
|
118
|
-
client = ctx.obj['client']
|
|
119
|
-
|
|
120
|
-
click.echo(f"Cloning voice from {voice_file}...")
|
|
121
|
-
|
|
122
|
-
job = client.voice.clone_voice(
|
|
123
|
-
voice_file=voice_file,
|
|
124
|
-
text=text,
|
|
125
|
-
language=language,
|
|
126
|
-
speed=speed,
|
|
127
|
-
wait_for_completion=wait
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
if wait:
|
|
131
|
-
click.echo(f"Voice cloning completed!")
|
|
132
|
-
if 'output_url' in job:
|
|
133
|
-
click.echo(f"Generated audio URL: {job['output_url']}")
|
|
134
|
-
if output:
|
|
135
|
-
# Here you could add download functionality
|
|
136
|
-
click.echo(f"To download: curl -o {output} '{job['output_url']}'")
|
|
137
|
-
else:
|
|
138
|
-
click.echo(f"Voice cloning job started with ID: {job.id}")
|
|
139
|
-
click.echo(f"Check status with: audiopod voice status {job.id}")
|
|
140
|
-
|
|
141
|
-
except AudioPodError as e:
|
|
142
|
-
click.echo(f"Voice cloning failed: {e.message}")
|
|
143
|
-
sys.exit(1)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
@voice.command('list')
|
|
147
|
-
@click.option('--limit', default=20, help='Maximum number of voices to show')
|
|
148
|
-
@click.pass_context
|
|
149
|
-
def voice_list(ctx, limit: int):
|
|
150
|
-
"""List available voice profiles"""
|
|
151
|
-
try:
|
|
152
|
-
client = ctx.obj['client']
|
|
153
|
-
voices = client.voice.list_voice_profiles(limit=limit)
|
|
154
|
-
|
|
155
|
-
if not voices:
|
|
156
|
-
click.echo("No voice profiles found.")
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
click.echo("Available Voice Profiles:")
|
|
160
|
-
for voice in voices:
|
|
161
|
-
status_icon = "✓" if voice.status == "completed" else "⏳"
|
|
162
|
-
visibility = "Public" if voice.is_public else "Private"
|
|
163
|
-
click.echo(f" {status_icon} {voice.name} (ID: {voice.id}) - {visibility}")
|
|
164
|
-
if voice.description:
|
|
165
|
-
click.echo(f" {voice.description}")
|
|
166
|
-
|
|
167
|
-
except AudioPodError as e:
|
|
168
|
-
click.echo(f"Failed to list voices: {e.message}")
|
|
169
|
-
sys.exit(1)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
@cli.group()
|
|
173
|
-
def music():
|
|
174
|
-
"""Music generation commands"""
|
|
175
|
-
pass
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
@music.command('generate')
|
|
179
|
-
@click.argument('prompt')
|
|
180
|
-
@click.option('--duration', '-d', default=120.0, help='Duration in seconds')
|
|
181
|
-
@click.option('--wait', is_flag=True, help='Wait for completion')
|
|
182
|
-
@click.option('--output', '-o', help='Output file path')
|
|
183
|
-
@click.pass_context
|
|
184
|
-
def music_generate(ctx, prompt: str, duration: float, wait: bool, output: Optional[str]):
|
|
185
|
-
"""Generate music from text prompt"""
|
|
186
|
-
try:
|
|
187
|
-
client = ctx.obj['client']
|
|
188
|
-
|
|
189
|
-
click.echo(f"Generating music: '{prompt}'...")
|
|
190
|
-
|
|
191
|
-
job = client.music.generate_music(
|
|
192
|
-
prompt=prompt,
|
|
193
|
-
duration=duration,
|
|
194
|
-
wait_for_completion=wait
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
if wait:
|
|
198
|
-
click.echo("Music generation completed!")
|
|
199
|
-
if hasattr(job, 'output_url') and job.output_url:
|
|
200
|
-
click.echo(f"Generated music URL: {job.output_url}")
|
|
201
|
-
if output:
|
|
202
|
-
click.echo(f"To download: curl -o {output} '{job.output_url}'")
|
|
203
|
-
else:
|
|
204
|
-
click.echo(f"Music generation job started with ID: {job.id}")
|
|
205
|
-
click.echo(f"Check status with: audiopod music status {job.id}")
|
|
206
|
-
|
|
207
|
-
except AudioPodError as e:
|
|
208
|
-
click.echo(f"Music generation failed: {e.message}")
|
|
209
|
-
sys.exit(1)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
@music.command('list')
|
|
213
|
-
@click.option('--limit', default=20, help='Maximum number of tracks to show')
|
|
214
|
-
@click.pass_context
|
|
215
|
-
def music_list(ctx, limit: int):
|
|
216
|
-
"""List generated music tracks"""
|
|
217
|
-
try:
|
|
218
|
-
client = ctx.obj['client']
|
|
219
|
-
tracks = client.music.list_music_jobs(limit=limit)
|
|
220
|
-
|
|
221
|
-
if not tracks:
|
|
222
|
-
click.echo("No music tracks found.")
|
|
223
|
-
return
|
|
224
|
-
|
|
225
|
-
click.echo("Generated Music Tracks:")
|
|
226
|
-
for track in tracks:
|
|
227
|
-
status_icon = "✓" if track.job.status == "completed" else "⏳"
|
|
228
|
-
prompt = track.job.parameters.get('prompt', 'N/A') if track.job.parameters else 'N/A'
|
|
229
|
-
click.echo(f" {status_icon} Job {track.job.id}: '{prompt[:50]}...'")
|
|
230
|
-
|
|
231
|
-
except AudioPodError as e:
|
|
232
|
-
click.echo(f"Failed to list music tracks: {e.message}")
|
|
233
|
-
sys.exit(1)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
@cli.group()
|
|
237
|
-
def transcription():
|
|
238
|
-
"""Transcription commands"""
|
|
239
|
-
pass
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
@transcription.command('transcribe')
|
|
243
|
-
@click.argument('audio_file', type=click.Path(exists=True))
|
|
244
|
-
@click.option('--language', '-l', help='Language code (auto-detect if not specified)')
|
|
245
|
-
@click.option('--speakers', is_flag=True, help='Enable speaker diarization')
|
|
246
|
-
@click.option('--wait', is_flag=True, help='Wait for completion')
|
|
247
|
-
@click.option('--format', '-f', default='txt', help='Output format (txt, json, srt)')
|
|
248
|
-
@click.pass_context
|
|
249
|
-
def transcription_transcribe(ctx, audio_file: str, language: Optional[str],
|
|
250
|
-
speakers: bool, wait: bool, format: str):
|
|
251
|
-
"""Transcribe audio to text"""
|
|
252
|
-
try:
|
|
253
|
-
client = ctx.obj['client']
|
|
254
|
-
|
|
255
|
-
click.echo(f"Transcribing {audio_file}...")
|
|
256
|
-
|
|
257
|
-
job = client.transcription.transcribe_audio(
|
|
258
|
-
audio_file=audio_file,
|
|
259
|
-
language=language,
|
|
260
|
-
enable_speaker_diarization=speakers,
|
|
261
|
-
wait_for_completion=wait
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
if wait:
|
|
265
|
-
click.echo("Transcription completed!")
|
|
266
|
-
if hasattr(job, 'transcript') and job.transcript:
|
|
267
|
-
click.echo("Transcript:")
|
|
268
|
-
click.echo(job.transcript)
|
|
269
|
-
if speakers and hasattr(job, 'segments') and job.segments:
|
|
270
|
-
click.echo(f"\nFound {len(job.segments)} speaker segments")
|
|
271
|
-
else:
|
|
272
|
-
click.echo(f"Transcription job started with ID: {job.id}")
|
|
273
|
-
|
|
274
|
-
except AudioPodError as e:
|
|
275
|
-
click.echo(f"Transcription failed: {e.message}")
|
|
276
|
-
sys.exit(1)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
def main():
|
|
280
|
-
"""Main CLI entry point"""
|
|
281
|
-
cli()
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if __name__ == '__main__':
|
|
285
|
-
main()
|
audiopod/models.py
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
AudioPod API Client Models
|
|
3
|
-
Data structures for API responses
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
from typing import Optional, Dict, Any, List, Union
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
from enum import Enum
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class JobStatus(str, Enum):
|
|
13
|
-
"""Job processing status"""
|
|
14
|
-
PENDING = "pending"
|
|
15
|
-
PROCESSING = "processing"
|
|
16
|
-
COMPLETED = "completed"
|
|
17
|
-
FAILED = "failed"
|
|
18
|
-
CANCELLED = "cancelled"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class VoiceType(str, Enum):
|
|
22
|
-
"""Voice profile types"""
|
|
23
|
-
CUSTOM = "custom"
|
|
24
|
-
STANDARD = "standard"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class TTSProvider(str, Enum):
|
|
28
|
-
"""Text-to-speech providers"""
|
|
29
|
-
AUDIOPOD_SONIC = "audiopod_sonic"
|
|
30
|
-
OPENAI = "openai"
|
|
31
|
-
GOOGLE_GEMINI = "google_gemini"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@dataclass
|
|
35
|
-
class Job:
|
|
36
|
-
"""Base job information"""
|
|
37
|
-
id: int
|
|
38
|
-
status: JobStatus
|
|
39
|
-
created_at: datetime
|
|
40
|
-
updated_at: Optional[datetime] = None
|
|
41
|
-
completed_at: Optional[datetime] = None
|
|
42
|
-
progress: float = 0.0
|
|
43
|
-
error_message: Optional[str] = None
|
|
44
|
-
parameters: Optional[Dict[str, Any]] = None
|
|
45
|
-
result: Optional[Dict[str, Any]] = None
|
|
46
|
-
|
|
47
|
-
@classmethod
|
|
48
|
-
def from_dict(cls, data: Dict[str, Any]) -> 'Job':
|
|
49
|
-
"""Create Job from API response data"""
|
|
50
|
-
return cls(
|
|
51
|
-
id=data['id'],
|
|
52
|
-
status=JobStatus(data['status']),
|
|
53
|
-
created_at=datetime.fromisoformat(data['created_at'].replace('Z', '+00:00')),
|
|
54
|
-
updated_at=datetime.fromisoformat(data['updated_at'].replace('Z', '+00:00')) if data.get('updated_at') else None,
|
|
55
|
-
completed_at=datetime.fromisoformat(data['completed_at'].replace('Z', '+00:00')) if data.get('completed_at') else None,
|
|
56
|
-
progress=data.get('progress', 0.0),
|
|
57
|
-
error_message=data.get('error_message'),
|
|
58
|
-
parameters=data.get('parameters'),
|
|
59
|
-
result=data.get('result')
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@dataclass
|
|
64
|
-
class VoiceProfile:
|
|
65
|
-
"""Voice profile information"""
|
|
66
|
-
id: int
|
|
67
|
-
uuid: str
|
|
68
|
-
name: str
|
|
69
|
-
display_name: Optional[str]
|
|
70
|
-
description: Optional[str]
|
|
71
|
-
voice_type: VoiceType
|
|
72
|
-
provider: TTSProvider
|
|
73
|
-
is_public: bool
|
|
74
|
-
language_code: Optional[str] = None
|
|
75
|
-
language_name: Optional[str] = None
|
|
76
|
-
gender: Optional[str] = None
|
|
77
|
-
accent: Optional[str] = None
|
|
78
|
-
created_at: Optional[datetime] = None
|
|
79
|
-
status: Optional[JobStatus] = None
|
|
80
|
-
|
|
81
|
-
@classmethod
|
|
82
|
-
def from_dict(cls, data: Dict[str, Any]) -> 'VoiceProfile':
|
|
83
|
-
"""Create VoiceProfile from API response data"""
|
|
84
|
-
return cls(
|
|
85
|
-
id=data['id'],
|
|
86
|
-
uuid=data['uuid'],
|
|
87
|
-
name=data['name'],
|
|
88
|
-
display_name=data.get('display_name'),
|
|
89
|
-
description=data.get('description'),
|
|
90
|
-
voice_type=VoiceType(data['voice_type']),
|
|
91
|
-
provider=TTSProvider(data['provider']),
|
|
92
|
-
is_public=data['is_public'],
|
|
93
|
-
language_code=data.get('language_code'),
|
|
94
|
-
language_name=data.get('language_name'),
|
|
95
|
-
gender=data.get('gender'),
|
|
96
|
-
accent=data.get('accent'),
|
|
97
|
-
created_at=datetime.fromisoformat(data['created_at'].replace('Z', '+00:00')) if data.get('created_at') else None,
|
|
98
|
-
status=JobStatus(data['status']) if data.get('status') else None
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@dataclass
|
|
103
|
-
class TranscriptionResult:
|
|
104
|
-
"""Transcription job result"""
|
|
105
|
-
job: Job
|
|
106
|
-
transcript: Optional[str] = None
|
|
107
|
-
detected_language: Optional[str] = None
|
|
108
|
-
confidence_score: Optional[float] = None
|
|
109
|
-
segments: Optional[List[Dict[str, Any]]] = None
|
|
110
|
-
audio_duration: Optional[float] = None
|
|
111
|
-
|
|
112
|
-
@classmethod
|
|
113
|
-
def from_dict(cls, data: Dict[str, Any]) -> 'TranscriptionResult':
|
|
114
|
-
"""Create TranscriptionResult from API response data"""
|
|
115
|
-
return cls(
|
|
116
|
-
job=Job.from_dict(data),
|
|
117
|
-
transcript=data.get('transcript'),
|
|
118
|
-
detected_language=data.get('detected_language'),
|
|
119
|
-
confidence_score=data.get('confidence_score'),
|
|
120
|
-
segments=data.get('segments'),
|
|
121
|
-
audio_duration=data.get('total_duration')
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
@dataclass
|
|
126
|
-
class MusicGenerationResult:
|
|
127
|
-
"""Music generation job result"""
|
|
128
|
-
job: Job
|
|
129
|
-
output_url: Optional[str] = None
|
|
130
|
-
output_urls: Optional[Dict[str, str]] = None # Format -> URL mapping
|
|
131
|
-
audio_duration: Optional[float] = None
|
|
132
|
-
actual_seeds: Optional[List[int]] = None
|
|
133
|
-
share_token: Optional[str] = None
|
|
134
|
-
share_url: Optional[str] = None
|
|
135
|
-
is_shared: bool = False
|
|
136
|
-
|
|
137
|
-
@classmethod
|
|
138
|
-
def from_dict(cls, data: Dict[str, Any]) -> 'MusicGenerationResult':
|
|
139
|
-
"""Create MusicGenerationResult from API response data"""
|
|
140
|
-
return cls(
|
|
141
|
-
job=Job.from_dict(data),
|
|
142
|
-
output_url=data.get('output_url'),
|
|
143
|
-
output_urls=data.get('output_urls'),
|
|
144
|
-
audio_duration=data.get('audio_duration'),
|
|
145
|
-
actual_seeds=data.get('actual_seeds'),
|
|
146
|
-
share_token=data.get('share_token'),
|
|
147
|
-
share_url=data.get('share_url'),
|
|
148
|
-
is_shared=data.get('is_shared', False)
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
@dataclass
|
|
153
|
-
class TranslationResult:
|
|
154
|
-
"""Speech translation job result"""
|
|
155
|
-
job: Job
|
|
156
|
-
source_language: Optional[str] = None
|
|
157
|
-
target_language: Optional[str] = None
|
|
158
|
-
display_name: Optional[str] = None
|
|
159
|
-
audio_output_path: Optional[str] = None
|
|
160
|
-
video_output_path: Optional[str] = None
|
|
161
|
-
transcript_path: Optional[str] = None
|
|
162
|
-
translated_audio_url: Optional[str] = None
|
|
163
|
-
video_output_url: Optional[str] = None
|
|
164
|
-
transcript_urls: Optional[Dict[str, str]] = None
|
|
165
|
-
is_video: bool = False
|
|
166
|
-
|
|
167
|
-
@classmethod
|
|
168
|
-
def from_dict(cls, data: Dict[str, Any]) -> 'TranslationResult':
|
|
169
|
-
"""Create TranslationResult from API response data"""
|
|
170
|
-
return cls(
|
|
171
|
-
job=Job.from_dict(data),
|
|
172
|
-
source_language=data.get('source_language'),
|
|
173
|
-
target_language=data.get('target_language'),
|
|
174
|
-
display_name=data.get('display_name'),
|
|
175
|
-
audio_output_path=data.get('audio_output_path'),
|
|
176
|
-
video_output_path=data.get('video_output_path'),
|
|
177
|
-
transcript_path=data.get('transcript_path'),
|
|
178
|
-
translated_audio_url=data.get('translated_audio_url'),
|
|
179
|
-
video_output_url=data.get('video_output_url'),
|
|
180
|
-
transcript_urls=data.get('transcript_urls'),
|
|
181
|
-
is_video=data.get('is_video', False)
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
@property
|
|
185
|
-
def audio_output_url(self) -> Optional[str]:
|
|
186
|
-
"""Backward compatibility property - returns translated_audio_url"""
|
|
187
|
-
return self.translated_audio_url
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
@dataclass
|
|
191
|
-
class SpeakerAnalysisResult:
|
|
192
|
-
"""Speaker analysis job result"""
|
|
193
|
-
job: Job
|
|
194
|
-
num_speakers: Optional[int] = None
|
|
195
|
-
speaker_segments: Optional[List[Dict[str, Any]]] = None
|
|
196
|
-
output_paths: Optional[Dict[str, str]] = None
|
|
197
|
-
rttm_path: Optional[str] = None
|
|
198
|
-
|
|
199
|
-
@classmethod
|
|
200
|
-
def from_dict(cls, data: Dict[str, Any]) -> 'SpeakerAnalysisResult':
|
|
201
|
-
"""Create SpeakerAnalysisResult from API response data"""
|
|
202
|
-
return cls(
|
|
203
|
-
job=Job.from_dict(data),
|
|
204
|
-
num_speakers=data.get('num_speakers'),
|
|
205
|
-
speaker_segments=data.get('speaker_segments'),
|
|
206
|
-
output_paths=data.get('output_paths'),
|
|
207
|
-
rttm_path=data.get('rttm_path')
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
@dataclass
|
|
212
|
-
class DenoiseResult:
|
|
213
|
-
"""Audio denoising job result"""
|
|
214
|
-
job: Job
|
|
215
|
-
output_url: Optional[str] = None
|
|
216
|
-
video_output_url: Optional[str] = None
|
|
217
|
-
stats: Optional[Dict[str, Any]] = None
|
|
218
|
-
is_video: bool = False
|
|
219
|
-
|
|
220
|
-
@classmethod
|
|
221
|
-
def from_dict(cls, data: Dict[str, Any]) -> 'DenoiseResult':
|
|
222
|
-
"""Create DenoiseResult from API response data"""
|
|
223
|
-
return cls(
|
|
224
|
-
job=Job.from_dict(data),
|
|
225
|
-
output_url=data.get('output_path'),
|
|
226
|
-
video_output_url=data.get('video_output_path'),
|
|
227
|
-
stats=data.get('stats'),
|
|
228
|
-
is_video=data.get('is_video', False)
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
@dataclass
|
|
233
|
-
class CreditInfo:
|
|
234
|
-
"""User credit information"""
|
|
235
|
-
balance: int
|
|
236
|
-
payg_balance: int
|
|
237
|
-
total_available_credits: int
|
|
238
|
-
next_reset_date: Optional[datetime] = None
|
|
239
|
-
total_credits_used: int = 0
|
|
240
|
-
|
|
241
|
-
@classmethod
|
|
242
|
-
def from_dict(cls, data: Dict[str, Any]) -> 'CreditInfo':
|
|
243
|
-
"""Create CreditInfo from API response data"""
|
|
244
|
-
return cls(
|
|
245
|
-
balance=data['balance'],
|
|
246
|
-
payg_balance=data['payg_balance'],
|
|
247
|
-
total_available_credits=data['total_available_credits'],
|
|
248
|
-
next_reset_date=datetime.fromisoformat(data['next_reset_date'].replace('Z', '+00:00')) if data.get('next_reset_date') else None,
|
|
249
|
-
total_credits_used=data.get('total_credits_used', 0)
|
|
250
|
-
)
|
audiopod/py.typed
DELETED
audiopod/services/karaoke.py
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Karaoke Service - Karaoke video generation
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from typing import Optional, Union
|
|
6
|
-
from .base import BaseService
|
|
7
|
-
from ..models import Job
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class KaraokeService(BaseService):
|
|
11
|
-
"""Service for karaoke video generation"""
|
|
12
|
-
|
|
13
|
-
def generate_karaoke(
|
|
14
|
-
self,
|
|
15
|
-
audio_file: Optional[str] = None,
|
|
16
|
-
youtube_url: Optional[str] = None,
|
|
17
|
-
custom_lyrics: Optional[str] = None,
|
|
18
|
-
video_style: str = "modern",
|
|
19
|
-
wait_for_completion: bool = False,
|
|
20
|
-
timeout: int = 1200
|
|
21
|
-
) -> Job:
|
|
22
|
-
"""Generate karaoke video"""
|
|
23
|
-
if not audio_file and not youtube_url:
|
|
24
|
-
raise ValueError("Either audio_file or youtube_url must be provided")
|
|
25
|
-
|
|
26
|
-
data = {"video_style": video_style}
|
|
27
|
-
files = {}
|
|
28
|
-
|
|
29
|
-
if audio_file:
|
|
30
|
-
files = self._prepare_file_upload(audio_file, "file")
|
|
31
|
-
if youtube_url:
|
|
32
|
-
data["youtube_url"] = youtube_url
|
|
33
|
-
if custom_lyrics:
|
|
34
|
-
data["custom_lyrics"] = custom_lyrics
|
|
35
|
-
|
|
36
|
-
if self.async_mode:
|
|
37
|
-
return self._async_generate_karaoke(files, data, wait_for_completion, timeout)
|
|
38
|
-
else:
|
|
39
|
-
response = self.client.request(
|
|
40
|
-
"POST", "/api/v1/karaoke/generate",
|
|
41
|
-
data=data, files=files if files else None
|
|
42
|
-
)
|
|
43
|
-
job = Job.from_dict(response)
|
|
44
|
-
|
|
45
|
-
if wait_for_completion:
|
|
46
|
-
return self._wait_for_completion(job.id, timeout)
|
|
47
|
-
|
|
48
|
-
return job
|
|
49
|
-
|
|
50
|
-
async def _async_generate_karaoke(self, files, data, wait_for_completion, timeout):
|
|
51
|
-
"""Async version of generate_karaoke"""
|
|
52
|
-
response = await self.client.request(
|
|
53
|
-
"POST", "/api/v1/karaoke/generate",
|
|
54
|
-
data=data, files=files if files else None
|
|
55
|
-
)
|
|
56
|
-
job = Job.from_dict(response)
|
|
57
|
-
|
|
58
|
-
if wait_for_completion:
|
|
59
|
-
return await self._async_wait_for_completion(job.id, timeout)
|
|
60
|
-
|
|
61
|
-
return job
|