перенос файлов сервера из папки White в _White

This commit is contained in:
Remuchi
2024-01-28 18:18:54 +07:00
parent 21dbccfec9
commit 1e4ad59270
309 changed files with 450 additions and 437 deletions

View File

@@ -0,0 +1,190 @@
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Content.Shared.CCVar;
using Content.Shared.White;
using Prometheus;
using Robust.Shared.Configuration;
namespace Content.Server._White.TTS;
// ReSharper disable once InconsistentNaming
public sealed class TTSManager
{
private static readonly Histogram RequestTimings = Metrics.CreateHistogram(
"tts_req_timings",
"Timings of TTS API requests",
new HistogramConfiguration()
{
LabelNames = new[] {"type"},
Buckets = Histogram.ExponentialBuckets(.1, 1.5, 10),
});
private static readonly Counter WantedCount = Metrics.CreateCounter(
"tts_wanted_count",
"Amount of wanted TTS audio.");
private static readonly Counter ReusedCount = Metrics.CreateCounter(
"tts_reused_count",
"Amount of reused TTS audio from cache.");
private static readonly Gauge CachedCount = Metrics.CreateGauge(
"tts_cached_count",
"Amount of cached TTS audio.");
[Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly HttpClient _httpClient = new();
private ISawmill _sawmill = default!;
private readonly Dictionary<string, byte[]?> _cache = new();
public void Initialize()
{
_sawmill = Logger.GetSawmill("tts");
}
/// <summary>
/// Generates audio with passed text by API
/// </summary>
/// <param name="speaker">Identifier of speaker</param>
/// <param name="text">SSML formatted text</param>
/// <returns>OGG audio bytes</returns>
/// <exception cref="Exception">Throws if url or token CCVar not set or http request failed</exception>
public async Task<byte[]?> ConvertTextToSpeech(string speaker, string text, string pitch, string rate, string? effect = null)
{
var url = _cfg.GetCVar(WhiteCVars.TtsApiUrl);
var maxCacheSize = _cfg.GetCVar(WhiteCVars.TtsMaxCacheSize);
if (string.IsNullOrWhiteSpace(url))
{
throw new Exception("TTS Api url not specified");
}
WantedCount.Inc();
var cacheKey = GenerateCacheKey(speaker, text);
if (_cache.TryGetValue(cacheKey, out var data))
{
ReusedCount.Inc();
_sawmill.Debug($"Use cached sound for '{text}' speech by '{speaker}' speaker");
return data;
}
var body = new GenerateVoiceRequest
{
Text = text,
Speaker = speaker,
Pitch = pitch,
Rate = rate,
Effect = effect
};
var request = CreateRequestLink(url, body);
var reqTime = DateTime.UtcNow;
try
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var response = await _httpClient.GetAsync(request, cts.Token);
if (!response.IsSuccessStatusCode)
{
throw new Exception($"TTS request returned bad status code: {response.StatusCode}");
}
var soundData = await response.Content.ReadAsByteArrayAsync(cts.Token);
if(_cache.Count > maxCacheSize)
{
_cache.Remove(_cache.Last().Key);
}
_cache.Add(cacheKey, soundData);
CachedCount.Inc();
_sawmill.Debug($"Generated new sound for '{text}' speech by '{speaker}' speaker ({soundData.Length} bytes)");
RequestTimings.WithLabels("Success").Observe((DateTime.UtcNow - reqTime).TotalSeconds);
return soundData;
}
catch (TaskCanceledException)
{
RequestTimings.WithLabels("Timeout").Observe((DateTime.UtcNow - reqTime).TotalSeconds);
_sawmill.Warning($"Timeout of request generation new sound for '{text}' speech by '{speaker}' speaker");
return null;
}
catch (Exception e)
{
RequestTimings.WithLabels("Error").Observe((DateTime.UtcNow - reqTime).TotalSeconds);
_sawmill.Warning($"Failed of request generation new sound for '{text}' speech by '{speaker}' speaker\n{e}");
return null;
}
}
private static string CreateRequestLink(string url, GenerateVoiceRequest body)
{
var uriBuilder = new UriBuilder(url);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query["speaker"] = body.Speaker;
query["text"] = body.Text;
query["pitch"] = body.Pitch;
query["rate"] = body.Rate;
query["file"] = "1";
query["ext"] = "ogg";
if (body.Effect != null)
query["effect"] = body.Effect;
uriBuilder.Query = query.ToString();
return uriBuilder.ToString();
}
public void ResetCache()
{
_cache.Clear();
CachedCount.Set(0);
}
private string GenerateCacheKey(string speaker, string text)
{
var key = $"{speaker}/{text}";
byte[] keyData = Encoding.UTF8.GetBytes(key);
var sha256 = System.Security.Cryptography.SHA256.Create();
var bytes = sha256.ComputeHash(keyData);
return Convert.ToHexString(bytes);
}
private record GenerateVoiceRequest
{
[JsonPropertyName("text")]
public string Text { get; set; } = default!;
[JsonPropertyName("speaker")]
public string Speaker { get; set; } = default!;
[JsonPropertyName("pitch")]
public string Pitch { get; set; } = default!;
[JsonPropertyName("rate")]
public string Rate { get; set; } = default!;
[JsonPropertyName("effect")]
public string? Effect { get; set; }
}
private struct GenerateVoiceResponse
{
[JsonPropertyName("results")]
public List<VoiceResult> Results { get; set; }
[JsonPropertyName("original_sha1")]
public string Hash { get; set; }
}
private struct VoiceResult
{
[JsonPropertyName("audio")]
public string Audio { get; set; }
}
}