using GermanApp.Domain.Interfaces; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace GermanApp.Presentation.Controllers; /// /// API controller for text-to-speech using Coqui TTS. /// This is part of the Presentation layer. /// [ApiController] [Route("api/[controller]")] [Authorize] public class TtsController : ControllerBase { private readonly ITtsService _ttsService; public TtsController(ITtsService ttsService) { _ttsService = ttsService; } /// /// Generates audio from text. /// /// TTS generation request /// Cancellation token /// Audio file as bytes [HttpPost("generate")] [RequestSizeLimit(5_000_000)] // 5MB limit for response public async Task GenerateAudio( [FromBody] TtsGenerationRequest request, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(request.Text)) return BadRequest("Text is required"); try { var audioBytes = await _ttsService.GenerateAudioAsync( request.Text, request.Speaker, request.Language, cancellationToken); // Return audio as WAV or specified format var contentType = GetContentType(request.Format); return File(audioBytes, contentType); } catch (TimeoutException ex) { return StatusCode(504, new { Error = ex.Message }); } catch (Exception ex) { return StatusCode(500, new { Error = ex.Message }); } } /// /// Generates audio and returns a file URL. /// /// TTS generation request with filename /// Cancellation token /// Audio file URL [HttpPost("generate-file")] public async Task GenerateAudioToFile( [FromBody] TtsFileGenerationRequest request, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(request.Text)) return BadRequest("Text is required"); if (string.IsNullOrWhiteSpace(request.Filename)) return BadRequest("Filename is required"); try { var outputPath = Path.Combine("audio", "tts", request.Filename); await _ttsService.GenerateAudioToFileAsync( request.Text, outputPath, request.Speaker, request.Language, cancellationToken); return Ok(new TtsFileResponse( Url: $"/audio/tts/{request.Filename}", Filename: request.Filename)); } catch (Exception ex) { return StatusCode(500, new { Error = ex.Message }); } } /// /// Tests the TTS service. /// /// Health check result [HttpGet("health")] [AllowAnonymous] public async Task HealthCheck(CancellationToken cancellationToken = default) { try { var isHealthy = await _ttsService.TestModelAsync(cancellationToken); return Ok(new { Healthy = isHealthy }); } catch (Exception ex) { return StatusCode(500, new { Error = ex.Message }); } } /// /// Gets information about the current TTS model. /// /// Model information [HttpGet("model-info")] [AllowAnonymous] public async Task GetModelInfo(CancellationToken cancellationToken = default) { try { var (modelName, modelPath) = await _ttsService.GetModelInfoAsync(); return Ok(new { ModelName = modelName, ModelPath = modelPath }); } catch (Exception ex) { return StatusCode(500, new { Error = ex.Message }); } } /// /// Gets list of available voices/speakers. /// /// List of speaker IDs [HttpGet("speakers")] [AllowAnonymous] public async Task GetSpeakers(CancellationToken cancellationToken = default) { try { var speakers = await _ttsService.GetAvailableSpeakersAsync(cancellationToken); return Ok(new { Speakers = speakers }); } catch (Exception ex) { return StatusCode(500, new { Error = ex.Message }); } } /// /// Gets the content type for the specified audio format. /// private string GetContentType(string? format) { format = format?.ToLower() ?? "wav"; return format switch { "wav" => "audio/wav", "mp3" => "audio/mpeg", "ogg" => "audio/ogg", "flac" => "audio/flac", _ => "audio/wav" }; } } /// /// Request DTO for TTS generation. /// public record TtsGenerationRequest( string Text, string? Speaker = null, string Language = "de", string? Format = null); /// /// Request DTO for TTS file generation. /// public record TtsFileGenerationRequest( string Text, string Filename, string? Speaker = null, string Language = "de"); /// /// Response DTO for TTS file generation. /// public record TtsFileResponse( string Url, string Filename);