using GermanApp.Domain.Interfaces;
using Microsoft.Extensions.Logging;
namespace GermanApp.Application.Services;
///
/// Application service for generating audio from text using Coqui TTS.
/// This is part of the Application layer.
///
public class AudioGenerationService
{
private readonly ITtsService _ttsService;
private readonly ILogger _logger;
///
/// Creates a new AudioGenerationService.
///
/// The Coqui TTS service
/// Logger for service operations
public AudioGenerationService(
ITtsService ttsService,
ILogger logger)
{
_ttsService = ttsService;
_logger = logger;
}
///
/// Generates audio from text.
///
/// The text to convert to speech
/// The language code (default: "de" for German)
/// Optional speaker ID
/// Cancellation token
/// Audio data as byte array
public virtual async Task GenerateAudioAsync(
string text,
string language = "de",
string? speaker = null,
CancellationToken cancellationToken = default)
{
_logger.LogInformation(
"Generating audio: TextLength={Length}, Language={Language}, Speaker={Speaker}",
text?.Length ?? 0, language, speaker ?? "default");
try
{
var audio = await _ttsService.GenerateAudioAsync(
text,
speaker,
language,
cancellationToken);
_logger.LogInformation("Audio generated successfully: {ByteCount} bytes", audio.Length);
return audio;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to generate audio");
throw new AiServiceException(
"Failed to generate audio: " + ex.Message,
AiErrorCode.Temporary);
}
}
///
/// Generates audio and saves to a file.
///
/// The text to convert to speech
/// Path to save the audio file
/// The language code (default: "de")
/// Optional speaker ID
/// Cancellation token
/// Path to the generated audio file
public virtual async Task GenerateAudioToFileAsync(
string text,
string outputPath,
string language = "de",
string? speaker = null,
CancellationToken cancellationToken = default)
{
_logger.LogInformation(
"Generating audio to file: TextLength={Length}, Output={Output}",
text?.Length ?? 0, outputPath);
try
{
await _ttsService.GenerateAudioToFileAsync(
text,
outputPath,
speaker,
language,
cancellationToken);
_logger.LogInformation("Audio saved to: {OutputPath}", outputPath);
return outputPath;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to generate audio to file");
throw;
}
}
///
/// Generates audio as a stream.
///
/// The text to convert to speech
/// The language code (default: "de")
/// Optional speaker ID
/// Cancellation token
/// Stream containing audio data
public virtual async Task GenerateAudioStreamAsync(
string text,
string language = "de",
string? speaker = null,
CancellationToken cancellationToken = default)
{
_logger.LogInformation("Generating audio stream: TextLength={Length}", text?.Length ?? 0);
try
{
return await _ttsService.GenerateAudioStreamAsync(
text,
speaker,
language,
cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to generate audio stream");
throw;
}
}
///
/// Generates audio for a vocabulary word.
///
/// The vocabulary word
/// The language code (default: "de")
/// Optional speaker ID
/// Cancellation token
/// Audio data as byte array
public virtual async Task GenerateVocabularyAudioAsync(
string word,
string language = "de",
string? speaker = null,
CancellationToken cancellationToken = default)
{
_logger.LogInformation("Generating vocabulary audio: Word={Word}", word);
// For vocabulary, we might want to add a slight pause or emphasis
// For now, just generate the word directly
return await GenerateAudioAsync(word, language, speaker, cancellationToken);
}
///
/// Generates audio for a complete lesson including vocabulary and example sentences.
///
/// The lesson text to narrate
/// Vocabulary words to include
/// The language code (default: "de")
/// Cancellation token
/// Audio data as byte array
public virtual async Task GenerateLessonAudioAsync(
string lessonText,
IReadOnlyList vocabularyWords,
string language = "de",
CancellationToken cancellationToken = default)
{
_logger.LogInformation(
"Generating lesson audio: TextLength={Length}, VocabularyCount={Count}",
lessonText?.Length ?? 0, vocabularyWords?.Count ?? 0);
// Combine lesson text with vocabulary
var fullText = BuildLessonNarration(lessonText, vocabularyWords);
return await GenerateAudioAsync(fullText, language, null, cancellationToken);
}
///
/// Generates audio for a story.
///
/// The story text to narrate
/// The language code (default: "de")
/// Optional speaker ID
/// Cancellation token
/// Audio data as byte array
public virtual async Task GenerateStoryAudioAsync(
string storyText,
string language = "de",
string? speaker = null,
CancellationToken cancellationToken = default)
{
_logger.LogInformation("Generating story audio: TextLength={Length}", storyText?.Length ?? 0);
// Stories might be long, so we use the TTS service's built-in text splitting
return await GenerateAudioAsync(storyText, language, speaker, cancellationToken);
}
///
/// Generates audio for a quiz question.
///
/// The quiz question text
/// The answer options
/// The language code (default: "de")
/// Cancellation token
/// Audio data as byte array
public virtual async Task GenerateQuizAudioAsync(
string questionText,
IReadOnlyList options,
string language = "de",
CancellationToken cancellationToken = default)
{
_logger.LogInformation(
"Generating quiz audio: QuestionLength={Length}, OptionCount={Count}",
questionText?.Length ?? 0, options?.Count ?? 0);
// Build the full quiz narration
var fullText = BuildQuizNarration(questionText, options);
return await GenerateAudioAsync(fullText, language, null, cancellationToken);
}
///
/// Generates multiple audio files for a batch of texts.
///
/// Dictionary mapping IDs to texts
/// Directory to save audio files
/// The language code (default: "de")
/// Cancellation token
/// Dictionary mapping IDs to output file paths
public virtual async Task> GenerateBatchAudioAsync(
IDictionary texts,
string outputDirectory,
string language = "de",
CancellationToken cancellationToken = default)
{
_logger.LogInformation("Generating batch audio: Count={Count}", texts?.Count ?? 0);
var results = new Dictionary();
// Ensure output directory exists
Directory.CreateDirectory(outputDirectory);
foreach (var kvp in texts)
{
var id = kvp.Key;
var text = kvp.Value;
var outputPath = Path.Combine(outputDirectory, $"{id}.wav");
try
{
await GenerateAudioToFileAsync(text, outputPath, language, null, cancellationToken);
results[id] = outputPath;
_logger.LogInformation("Generated audio for: {Id}", id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to generate audio for: {Id}", id);
results[id] = string.Empty;
}
}
return results;
}
///
/// Gets available voices for the current TTS model.
///
/// Cancellation token
/// List of available speaker IDs
public virtual async Task> GetAvailableVoicesAsync(
CancellationToken cancellationToken = default)
{
try
{
return await _ttsService.GetAvailableSpeakersAsync(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get available voices");
return new List { "default" };
}
}
///
/// Gets information about the current TTS model.
///
/// Model information tuple
public virtual async Task<(string ModelName, string? ModelPath)> GetModelInfoAsync()
{
try
{
return await _ttsService.GetModelInfoAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get model info");
return ("unknown", null);
}
}
///
/// Tests the audio generation service.
///
/// Cancellation token
/// True if service is working, false otherwise
public virtual async Task TestServiceAsync(CancellationToken cancellationToken = default)
{
try
{
// Test with a simple phrase
var audio = await GenerateAudioAsync("Hallo Welt", "de", null, cancellationToken);
return audio != null && audio.Length > 0;
}
catch (Exception ex)
{
_logger.LogError(ex, "Audio generation service test failed");
return false;
}
}
///
/// Builds narration text for a lesson including vocabulary words.
///
private string BuildLessonNarration(string lessonText, IReadOnlyList vocabularyWords)
{
// Start with the lesson text
var narration = lessonText;
// Append vocabulary words with pauses
if (vocabularyWords != null && vocabularyWords.Count > 0)
{
narration += "\n\n";
narration += "Vocabulary words: ";
narration += string.Join(", ", vocabularyWords);
}
return narration;
}
///
/// Builds narration text for a quiz question with options.
///
private string BuildQuizNarration(string questionText, IReadOnlyList options)
{
var narration = questionText;
if (options != null && options.Count > 0)
{
narration += "\n";
for (int i = 0; i < options.Count; i++)
{
narration += $"Option {i + 1}: {options[i]}";
if (i < options.Count - 1)
narration += ". ";
}
}
return narration;
}
}