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);