File size: 16,587 Bytes
5a65ad6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
"""

Command Line Interface for Speech Translation System



This module provides a user-friendly CLI for the speech translation system.

"""

import click
import logging
import sys
from pathlib import Path
from typing import Optional, List
import json

from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeRemainingColumn
from rich.table import Table
from rich.panel import Panel
from rich import print as rprint

from ..pipeline.main_pipeline import create_speech_translator, SpeechTranslator
from ..config import SUPPORTED_LANGUAGES, WHISPER_MODEL_SIZE, DEFAULT_TRANSLATION_SERVICE, TTS_MODEL


# Initialize rich console
console = Console()


def setup_logging(verbose: bool = False):
    """Setup logging configuration."""
    level = logging.DEBUG if verbose else logging.INFO
    logging.basicConfig(
        level=level,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler('speech_translation.log'),
            logging.StreamHandler()
        ]
    )


@click.group()
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose logging')
@click.pass_context
def cli(ctx, verbose):
    """Speech Translation System with Voice Cloning"""
    ctx.ensure_object(dict)
    ctx.obj['verbose'] = verbose
    setup_logging(verbose)


@cli.command()
@click.argument('input_audio', type=click.Path(exists=True))
@click.argument('voice_sample', type=click.Path(exists=True))
@click.option('--source-lang', '-s', help='Source language code (auto-detect if not specified)')
@click.option('--target-lang', '-t', default='en', help='Target language code (default: en)')
@click.option('--output', '-o', type=click.Path(), help='Output audio file path')
@click.option('--speech-model', default=WHISPER_MODEL_SIZE, 

              help=f'Whisper model size (default: {WHISPER_MODEL_SIZE})')
@click.option('--translation-engine', default=DEFAULT_TRANSLATION_SERVICE,

              type=click.Choice(['google', 'local']),

              help=f'Translation engine (default: {DEFAULT_TRANSLATION_SERVICE})')
@click.option('--tts-model', default=TTS_MODEL, help=f'TTS model (default: {TTS_MODEL})')
@click.option('--device', default='auto', help='Device to use (auto, cpu, cuda)')
@click.pass_context
def translate(ctx, input_audio, voice_sample, source_lang, target_lang, output, 

             speech_model, translation_engine, tts_model, device):
    """Translate audio file with voice cloning."""
    
    try:
        # Validate language codes
        if target_lang not in SUPPORTED_LANGUAGES:
            console.print(f"[red]Error: Unsupported target language '{target_lang}'[/red]")
            console.print("Supported languages:", list(SUPPORTED_LANGUAGES.keys()))
            sys.exit(1)
        
        if source_lang and source_lang not in SUPPORTED_LANGUAGES:
            console.print(f"[red]Error: Unsupported source language '{source_lang}'[/red]")
            sys.exit(1)
        
        # Generate output path if not provided
        if not output:
            input_path = Path(input_audio)
            output = input_path.parent / f"{input_path.stem}_translated_{target_lang}.wav"
        
        console.print(Panel.fit(f"πŸŽ™οΈ  Speech Translation System", style="bold blue"))
        console.print(f"πŸ“ Input: {input_audio}")
        console.print(f"🎯 Voice Sample: {voice_sample}")
        console.print(f"🌍 Translation: {source_lang or 'auto'} β†’ {target_lang}")
        console.print(f"πŸ’Ύ Output: {output}")
        
        # Progress tracking
        progress_messages = []
        def progress_callback(message):
            progress_messages.append(message)
            console.print(f"⏳ {message}")
        
        # Initialize translator
        console.print("\\nπŸš€ Initializing translation system...")
        translator = create_speech_translator(
            speech_model=speech_model,
            translation_engine=translation_engine,
            tts_model=tts_model,
            device=device,
            initialize=False
        )
        
        translator.progress_callback = progress_callback
        translator.initialize()
        
        # Perform translation
        console.print("\\nπŸ”„ Starting translation process...")
        
        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            BarColumn(),
            TimeRemainingColumn(),
            console=console,
        ) as progress:
            
            task = progress.add_task("Translating...", total=100)
            
            result = translator.translate_audio(
                input_audio=input_audio,
                source_lang=source_lang,
                target_lang=target_lang,
                voice_sample=voice_sample,
                output_path=output,
                return_intermediate=True
            )
        
        # Display results
        if result['success']:
            console.print("\\nβœ… [green]Translation completed successfully![/green]")
            
            # Create results table
            table = Table(title="Translation Results")
            table.add_column("Property", style="cyan")
            table.add_column("Value", style="white")
            
            table.add_row("Original Text", result['original_text'][:100] + "..." if len(result['original_text']) > 100 else result['original_text'])
            table.add_row("Translated Text", result['translated_text'][:100] + "..." if len(result['translated_text']) > 100 else result['translated_text'])
            table.add_row("Source Language", result['source_language'])
            table.add_row("Target Language", result['target_language'])
            table.add_row("Processing Time", f"{result['processing_time']:.2f} seconds")
            table.add_row("Audio Duration", f"{result['audio_duration']:.2f} seconds")
            table.add_row("Output File", str(result['output_audio']))
            
            console.print(table)
            
        else:
            console.print(f"\\n❌ [red]Translation failed: {result['error']}[/red]")
            sys.exit(1)
            
    except Exception as e:
        console.print(f"\\nπŸ’₯ [red]Unexpected error: {str(e)}[/red]")
        if ctx.obj['verbose']:
            console.print_exception()
        sys.exit(1)


@cli.command()
@click.argument('text')
@click.argument('voice_sample', type=click.Path(exists=True))
@click.option('--source-lang', '-s', required=True, help='Source language code')
@click.option('--target-lang', '-t', default='en', help='Target language code')
@click.option('--output', '-o', type=click.Path(), help='Output audio file path')
@click.option('--tts-model', default=TTS_MODEL, help=f'TTS model (default: {TTS_MODEL})')
@click.option('--device', default='auto', help='Device to use (auto, cpu, cuda)')
def text_to_speech(text, voice_sample, source_lang, target_lang, output, tts_model, device):
    """Translate text and generate speech with voice cloning."""
    
    try:
        # Validate inputs
        if not output:
            output = f"translated_speech_{target_lang}.wav"
        
        console.print(Panel.fit("πŸ“ Text to Speech Translation", style="bold green"))
        console.print(f"πŸ“ Text: {text}")
        console.print(f"🎯 Voice Sample: {voice_sample}")
        console.print(f"🌍 Translation: {source_lang} β†’ {target_lang}")
        
        # Initialize translator
        translator = create_speech_translator(tts_model=tts_model, device=device)
        
        # Perform translation and speech generation
        result = translator.translate_text_with_voice(
            text=text,
            source_lang=source_lang,
            target_lang=target_lang,
            voice_sample=voice_sample,
            output_path=output
        )
        
        if result['success']:
            console.print("\\nβœ… [green]Text translation completed![/green]")
            console.print(f"🎡 Audio saved to: {result['output_audio']}")
        else:
            console.print(f"\\n❌ [red]Translation failed: {result['error']}[/red]")
            
    except Exception as e:
        console.print(f"\\nπŸ’₯ [red]Error: {str(e)}[/red]")
        sys.exit(1)


@cli.command()
@click.argument('audio_files', nargs=-1, required=True)
@click.argument('voice_sample', type=click.Path(exists=True))
@click.option('--target-lang', '-t', default='en', help='Target language code')
@click.option('--output-dir', '-d', type=click.Path(), help='Output directory')
@click.option('--speech-model', default=WHISPER_MODEL_SIZE, help='Whisper model size')
@click.option('--device', default='auto', help='Device to use')
def batch(audio_files, voice_sample, target_lang, output_dir, speech_model, device):
    """Batch translate multiple audio files."""
    
    try:
        if not output_dir:
            output_dir = Path.cwd() / "translated_batch"
        
        output_dir = Path(output_dir)
        output_dir.mkdir(exist_ok=True)
        
        console.print(Panel.fit("πŸ“¦ Batch Translation", style="bold yellow"))
        console.print(f"πŸ“ Files: {len(audio_files)} audio files")
        console.print(f"🎯 Voice Sample: {voice_sample}")
        console.print(f"🌍 Target Language: {target_lang}")
        console.print(f"πŸ’Ύ Output Directory: {output_dir}")
        
        # Initialize translator
        translator = create_speech_translator(speech_model=speech_model, device=device)
        
        # Perform batch translation
        with Progress(console=console) as progress:
            task = progress.add_task("Processing batch...", total=len(audio_files))
            
            result = translator.batch_translate_audio(
                audio_files=list(audio_files),
                target_lang=target_lang,
                voice_sample=voice_sample,
                output_dir=output_dir
            )
            
            progress.update(task, completed=len(audio_files))
        
        # Display results
        console.print(f"\\nπŸ“Š Batch processing completed!")
        console.print(f"βœ… Successful: {result['successful']}")
        console.print(f"❌ Failed: {result['failed']}")
        
        if result['failed_files']:
            console.print("\\n🚨 Failed files:")
            for failed in result['failed_files']:
                console.print(f"  - {failed['file']}: {failed['error']}")
                
    except Exception as e:
        console.print(f"\\nπŸ’₯ [red]Error: {str(e)}[/red]")
        sys.exit(1)


@cli.command()
@click.argument('speaker_name')
@click.argument('voice_samples', nargs=-1, required=True)
@click.option('--session-dir', type=click.Path(), help='Session directory to save speaker')
def register_speaker(speaker_name, voice_samples, session_dir):
    """Register a speaker voice for reuse."""
    
    try:
        console.print(Panel.fit(f"🎀 Registering Speaker: {speaker_name}", style="bold purple"))
        
        # Initialize voice cloner
        from ..voice_cloning.voice_cloner import create_voice_cloner
        cloner = create_voice_cloner()
        
        # Register speaker
        result = cloner.register_voice(speaker_name, list(voice_samples))
        
        console.print("\\nβœ… [green]Speaker registered successfully![/green]")
        console.print(f"πŸ‘€ Speaker: {result['speaker_name']}")
        console.print(f"🎡 Samples: {result['num_samples']}")
        console.print(f"⏱️  Duration: {result['total_duration']:.1f} seconds")
        
        # Save to session if specified
        if session_dir:
            session_path = Path(session_dir)
            cloner.save_speaker_data(session_path)
            console.print(f"πŸ’Ύ Saved to session: {session_path}")
            
    except Exception as e:
        console.print(f"\\nπŸ’₯ [red]Error: {str(e)}[/red]")
        sys.exit(1)


@cli.command()
def languages():
    """List supported languages."""
    
    console.print(Panel.fit("🌍 Supported Languages", style="bold blue"))
    
    table = Table()
    table.add_column("Code", style="cyan")
    table.add_column("Language", style="white")
    
    for code, name in SUPPORTED_LANGUAGES.items():
        table.add_row(code, name)
    
    console.print(table)


@cli.command()
@click.option('--speech-model', default=WHISPER_MODEL_SIZE, help='Speech model to check')
@click.option('--translation-engine', default=DEFAULT_TRANSLATION_SERVICE, help='Translation engine')
@click.option('--tts-model', default=TTS_MODEL, help='TTS model to check')
@click.option('--device', default='auto', help='Device to use')
def info(speech_model, translation_engine, tts_model, device):
    """Show system information and status."""
    
    try:
        console.print(Panel.fit("ℹ️  System Information", style="bold cyan"))
        
        # Create translator to get system info
        translator = create_speech_translator(
            speech_model=speech_model,
            translation_engine=translation_engine,
            tts_model=tts_model,
            device=device,
            initialize=False
        )
        
        info_data = translator.get_system_info()
        
        # Configuration table
        config_table = Table(title="Configuration")
        config_table.add_column("Component", style="cyan")
        config_table.add_column("Setting", style="white")
        
        for key, value in info_data['configuration'].items():
            config_table.add_row(key.replace('_', ' ').title(), str(value))
        
        console.print(config_table)
        
        # Component status
        status_table = Table(title="Component Status")
        status_table.add_column("Component", style="cyan")
        status_table.add_column("Status", style="white")
        
        for component, loaded in info_data['components_loaded'].items():
            status = "βœ… Loaded" if loaded else "❌ Not Loaded"
            status_table.add_row(component.replace('_', ' ').title(), status)
        
        console.print(status_table)
        
        # Statistics
        if any(info_data['statistics'].values()):
            stats_table = Table(title="Usage Statistics")
            stats_table.add_column("Metric", style="cyan")
            stats_table.add_column("Value", style="white")
            
            for key, value in info_data['statistics'].items():
                stats_table.add_row(key.replace('_', ' ').title(), str(value))
            
            console.print(stats_table)
        
    except Exception as e:
        console.print(f"\\nπŸ’₯ [red]Error getting system info: {str(e)}[/red]")


@cli.command()
@click.argument('session_path', type=click.Path())
def save_session(session_path):
    """Save current session including registered speakers."""
    try:
        # Create a basic translator and save session
        translator = create_speech_translator(initialize=False)
        translator.save_session(session_path)
        console.print(f"πŸ’Ύ Session saved to: {session_path}")
    except Exception as e:
        console.print(f"πŸ’₯ [red]Error saving session: {str(e)}[/red]")


@cli.command()
@click.argument('session_path', type=click.Path(exists=True))
def load_session(session_path):
    """Load previous session."""
    try:
        translator = create_speech_translator(initialize=False)
        translator.load_session(session_path)
        console.print(f"πŸ“‚ Session loaded from: {session_path}")
        
        # Show loaded speakers
        speakers = translator.get_registered_speakers()
        if speakers:
            console.print(f"πŸ‘₯ Registered speakers: {', '.join(speakers)}")
        
    except Exception as e:
        console.print(f"πŸ’₯ [red]Error loading session: {str(e)}[/red]")


def main():
    """Main CLI entry point."""
    try:
        cli()
    except KeyboardInterrupt:
        console.print("\\nπŸ›‘ Operation cancelled by user")
        sys.exit(1)
    except Exception as e:
        console.print(f"\\nπŸ’₯ [red]Unexpected error: {str(e)}[/red]")
        sys.exit(1)


if __name__ == '__main__':
    main()