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/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
@@ -1,2 +0,0 @@
1
- # Marker file for PEP 561
2
- # This package supports type checking
@@ -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