147 lines
4.7 KiB
C#
147 lines
4.7 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using DramaLing.Api.Data;
|
|
using DramaLing.Api.Models.Entities;
|
|
using DramaLing.Api.Models.Dtos;
|
|
|
|
namespace DramaLing.Api.Services;
|
|
|
|
public interface IAudioCacheService
|
|
{
|
|
Task<TTSResponse> GetOrCreateAudioAsync(TTSRequest request);
|
|
Task<string> GenerateCacheKeyAsync(string text, string accent, string voice);
|
|
Task UpdateAccessTimeAsync(string cacheKey);
|
|
Task CleanupOldCacheAsync();
|
|
}
|
|
|
|
public class AudioCacheService : IAudioCacheService
|
|
{
|
|
private readonly DramaLingDbContext _context;
|
|
private readonly IAzureSpeechService _speechService;
|
|
private readonly ILogger<AudioCacheService> _logger;
|
|
|
|
public AudioCacheService(
|
|
DramaLingDbContext context,
|
|
IAzureSpeechService speechService,
|
|
ILogger<AudioCacheService> logger)
|
|
{
|
|
_context = context;
|
|
_speechService = speechService;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<TTSResponse> GetOrCreateAudioAsync(TTSRequest request)
|
|
{
|
|
try
|
|
{
|
|
var cacheKey = await GenerateCacheKeyAsync(request.Text, request.Accent, request.Voice);
|
|
|
|
// 檢查快取
|
|
var cachedAudio = await _context.AudioCaches
|
|
.FirstOrDefaultAsync(a => a.TextHash == cacheKey);
|
|
|
|
if (cachedAudio != null)
|
|
{
|
|
// 更新訪問時間
|
|
await UpdateAccessTimeAsync(cacheKey);
|
|
|
|
return new TTSResponse
|
|
{
|
|
AudioUrl = cachedAudio.AudioUrl,
|
|
Duration = cachedAudio.DurationMs.HasValue ? cachedAudio.DurationMs.Value / 1000.0f : 0,
|
|
CacheHit = true
|
|
};
|
|
}
|
|
|
|
// 生成新音頻
|
|
var response = await _speechService.GenerateAudioAsync(request);
|
|
|
|
if (!string.IsNullOrEmpty(response.Error))
|
|
{
|
|
return response;
|
|
}
|
|
|
|
// 存入快取
|
|
var audioCache = new AudioCache
|
|
{
|
|
TextHash = cacheKey,
|
|
TextContent = request.Text,
|
|
Accent = request.Accent,
|
|
VoiceId = request.Voice,
|
|
AudioUrl = response.AudioUrl,
|
|
DurationMs = (int)(response.Duration * 1000),
|
|
CreatedAt = DateTime.UtcNow,
|
|
LastAccessed = DateTime.UtcNow,
|
|
AccessCount = 1
|
|
};
|
|
|
|
_context.AudioCaches.Add(audioCache);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("Created new audio cache entry for text: {Text}", request.Text);
|
|
|
|
return response;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error in GetOrCreateAudioAsync for text: {Text}", request.Text);
|
|
return new TTSResponse
|
|
{
|
|
Error = "Internal error processing audio request"
|
|
};
|
|
}
|
|
}
|
|
|
|
public async Task<string> GenerateCacheKeyAsync(string text, string accent, string voice)
|
|
{
|
|
var combined = $"{text}|{accent}|{voice}";
|
|
using var sha256 = SHA256.Create();
|
|
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(combined));
|
|
return Convert.ToHexString(hash).ToLowerInvariant();
|
|
}
|
|
|
|
public async Task UpdateAccessTimeAsync(string cacheKey)
|
|
{
|
|
try
|
|
{
|
|
var audioCache = await _context.AudioCaches
|
|
.FirstOrDefaultAsync(a => a.TextHash == cacheKey);
|
|
|
|
if (audioCache != null)
|
|
{
|
|
audioCache.LastAccessed = DateTime.UtcNow;
|
|
audioCache.AccessCount++;
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to update access time for cache key: {CacheKey}", cacheKey);
|
|
}
|
|
}
|
|
|
|
public async Task CleanupOldCacheAsync()
|
|
{
|
|
try
|
|
{
|
|
var cutoffDate = DateTime.UtcNow.AddDays(-30);
|
|
|
|
var oldEntries = await _context.AudioCaches
|
|
.Where(a => a.LastAccessed < cutoffDate)
|
|
.ToListAsync();
|
|
|
|
if (oldEntries.Any())
|
|
{
|
|
_context.AudioCaches.RemoveRange(oldEntries);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("Cleaned up {Count} old audio cache entries", oldEntries.Count);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error during audio cache cleanup");
|
|
}
|
|
}
|
|
} |