289 lines
9.2 KiB
C#
289 lines
9.2 KiB
C#
using System.Text;
|
||
using System.Text.RegularExpressions;
|
||
using Content.Server.Chat.Systems;
|
||
|
||
namespace Content.Server._White.TTS;
|
||
|
||
// ReSharper disable once InconsistentNaming
|
||
public sealed partial class TTSSystem
|
||
{
|
||
private void OnTransformSpeech(TransformSpeechEvent args)
|
||
{
|
||
if (!_isEnabled)
|
||
return;
|
||
args.Message = args.Message.Replace("+", "");
|
||
}
|
||
|
||
private string Sanitize(string text)
|
||
{
|
||
text = text.Trim();
|
||
text = Regex.Replace(text, @"Ё", "Е");
|
||
text = Regex.Replace(text, @"[^a-zA-Zа-яА-ЯёЁ0-9,\-,+, ,?,!,.]", "");
|
||
text = Regex.Replace(text, @"[a-zA-Z]", ReplaceLat2Cyr, RegexOptions.Multiline | RegexOptions.IgnoreCase);
|
||
text = Regex.Replace(text, @"(?<![a-zA-Zа-яёА-ЯЁ])[a-zA-Zа-яёА-ЯЁ]+?(?![a-zA-Zа-яёА-ЯЁ])", ReplaceMatchedWord, RegexOptions.Multiline | RegexOptions.IgnoreCase);
|
||
text = Regex.Replace(text, @"(?<=[1-90])(\.|,)(?=[1-90])", " целых ");
|
||
text = Regex.Replace(text, @"\d+", ReplaceWord2Num);
|
||
text = text.Trim();
|
||
return text;
|
||
}
|
||
|
||
private string ReplaceLat2Cyr(Match oneChar)
|
||
{
|
||
if (ReverseTranslit.TryGetValue(oneChar.Value.ToLower(), out var replace))
|
||
return replace;
|
||
return oneChar.Value;
|
||
}
|
||
|
||
private string ReplaceMatchedWord(Match word)
|
||
{
|
||
if (WordReplacement.TryGetValue(word.Value.ToLower(), out var replace))
|
||
return replace;
|
||
return word.Value;
|
||
}
|
||
|
||
private string ReplaceWord2Num(Match word)
|
||
{
|
||
if (!long.TryParse(word.Value, out var number))
|
||
return word.Value;
|
||
return NumberConverter.NumberToText(number);
|
||
}
|
||
|
||
private static readonly IReadOnlyDictionary<string, string> WordReplacement =
|
||
new Dictionary<string, string>()
|
||
{
|
||
{"нт", "Эн Тэ"},
|
||
{"смо", "Эс Мэ О"},
|
||
{"гп", "Гэ Пэ"},
|
||
{"рд", "Эр Дэ"},
|
||
{"гсб", "Гэ Эс Бэ"},
|
||
{"гв", "Гэ Вэ"},
|
||
{"нр", "Эн Эр"},
|
||
{"срп", "Эс Эр Пэ"},
|
||
{"цк", "Цэ Каа"},
|
||
{"рнд", "Эр Эн Дэ"},
|
||
{"сб", "Эс Бэ"},
|
||
{"рцд", "Эр Цэ Дэ"},
|
||
{"брпд", "Бэ Эр Пэ Дэ"},
|
||
{"рпд", "Эр Пэ Дэ"},
|
||
{"рпед", "Эр Пед"},
|
||
{"тсф", "Тэ Эс Эф"},
|
||
{"срт", "Эс Эр Тэ"},
|
||
{"обр", "О Бэ Эр"},
|
||
{"кпк", "Кэ Пэ Каа"},
|
||
{"пда", "Пэ Дэ А"},
|
||
{"id", "Ай Ди"},
|
||
{"мщ", "Эм Ще"},
|
||
{"вт", "Вэ Тэ"},
|
||
{"ерп", "Йе Эр Пэ"},
|
||
{"се", "Эс Йе"},
|
||
{"апц", "А Пэ Цэ"},
|
||
{"лкп", "Эл Ка Пэ"},
|
||
{"см", "Эс Эм"},
|
||
{"ека", "Йе Ка"},
|
||
{"ка", "Кэ А"},
|
||
{"бса", "Бэ Эс Аа"},
|
||
{"тк", "Тэ Ка"},
|
||
{"бфл", "Бэ Эф Эл"},
|
||
{"бщ", "Бэ Щэ"},
|
||
{"кк", "Кэ Ка"},
|
||
{"ск", "Эс Ка"},
|
||
{"зк", "Зэ Ка"},
|
||
{"ерт", "Йе Эр Тэ"},
|
||
{"вкд", "Вэ Ка Дэ"},
|
||
{"нтр", "Эн Тэ Эр"},
|
||
{"пнт", "Пэ Эн Тэ"},
|
||
{"авд", "А Вэ Дэ"},
|
||
{"пнв", "Пэ Эн Вэ"},
|
||
{"ссд", "Эс Эс Дэ"},
|
||
{"кпб", "Кэ Пэ Бэ"},
|
||
{"сссп", "Эс Эс Эс Пэ"},
|
||
{"крб", "Ка Эр Бэ"},
|
||
{"бд", "Бэ Дэ"},
|
||
{"сст", "Эс Эс Тэ"},
|
||
{"скс", "Эс Ка Эс"},
|
||
{"икн", "И Ка Эн"},
|
||
{"нсс", "Эн Эс Эс"},
|
||
{"емп", "Йе Эм Пэ"},
|
||
{"бс", "Бэ Эс"},
|
||
{"цкс", "Цэ Ка Эс"},
|
||
{"срд", "Эс Эр Дэ"},
|
||
{"жпс", "Джи Пи Эс"},
|
||
{"gps", "Джи Пи Эс"},
|
||
{"ннксс", "Эн Эн Ка Эс Эс"},
|
||
{"ss", "Эс Эс"},
|
||
{"сс", "Эс Эс"},
|
||
{"тесла", "тэсла"},
|
||
{"трейзен", "трэйзэн"},
|
||
{"нанотрейзен", "нанотрэйзэн"},
|
||
{"рпзд", "Эр Пэ Зэ Дэ"},
|
||
{"кз", "Кэ Зэ"},
|
||
};
|
||
|
||
private static readonly IReadOnlyDictionary<string, string> ReverseTranslit =
|
||
new Dictionary<string, string>()
|
||
{
|
||
{"a", "а"},
|
||
{"b", "б"},
|
||
{"v", "в"},
|
||
{"g", "г"},
|
||
{"d", "д"},
|
||
{"e", "е"},
|
||
{"je", "ё"},
|
||
{"zh", "ж"},
|
||
{"z", "з"},
|
||
{"i", "и"},
|
||
{"y", "й"},
|
||
{"k", "к"},
|
||
{"l", "л"},
|
||
{"m", "м"},
|
||
{"n", "н"},
|
||
{"o", "о"},
|
||
{"p", "п"},
|
||
{"r", "р"},
|
||
{"s", "с"},
|
||
{"t", "т"},
|
||
{"u", "у"},
|
||
{"f", "ф"},
|
||
{"h", "х"},
|
||
{"c", "ц"},
|
||
{"x", "кс"},
|
||
{"ch", "ч"},
|
||
{"sh", "ш"},
|
||
{"jsh", "щ"},
|
||
{"hh", "ъ"},
|
||
{"ih", "ы"},
|
||
{"jh", "ь"},
|
||
{"eh", "э"},
|
||
{"ju", "ю"},
|
||
{"ja", "я"},
|
||
};
|
||
}
|
||
|
||
// Source: https://codelab.ru/s/csharp/digits2phrase
|
||
public static class NumberConverter
|
||
{
|
||
private static readonly string[] Frac20Male =
|
||
{
|
||
"", "один", "два", "три", "четыре", "пять", "шесть",
|
||
"семь", "восемь", "девять", "десять", "одиннадцать",
|
||
"двенадцать", "тринадцать", "четырнадцать", "пятнадцать",
|
||
"шестнадцать", "семнадцать", "восемнадцать", "девятнадцать"
|
||
};
|
||
|
||
private static readonly string[] Frac20Female =
|
||
{
|
||
"", "одна", "две", "три", "четыре", "пять", "шесть",
|
||
"семь", "восемь", "девять", "десять", "одиннадцать",
|
||
"двенадцать", "тринадцать", "четырнадцать", "пятнадцать",
|
||
"шестнадцать", "семнадцать", "восемнадцать", "девятнадцать"
|
||
};
|
||
|
||
private static readonly string[] Hunds =
|
||
{
|
||
"", "сто", "двести", "триста", "четыреста",
|
||
"пятьсот", "шестьсот", "семьсот", "восемьсот", "девятьсот"
|
||
};
|
||
|
||
private static readonly string[] Tens =
|
||
{
|
||
"", "десять", "двадцать", "тридцать", "сорок", "пятьдесят",
|
||
"шестьдесят", "семьдесят", "восемьдесят", "девяносто"
|
||
};
|
||
|
||
public static string NumberToText(long value, bool male = true)
|
||
{
|
||
if (value >= (long)Math.Pow(10, 15))
|
||
return String.Empty;
|
||
|
||
if (value == 0)
|
||
return "ноль";
|
||
|
||
var str = new StringBuilder();
|
||
|
||
if (value < 0)
|
||
{
|
||
str.Append("минус");
|
||
value = -value;
|
||
}
|
||
|
||
value = AppendPeriod(value, 1000000000000, str, "триллион", "триллиона", "триллионов", true);
|
||
value = AppendPeriod(value, 1000000000, str, "миллиард", "миллиарда", "миллиардов", true);
|
||
value = AppendPeriod(value, 1000000, str, "миллион", "миллиона", "миллионов", true);
|
||
value = AppendPeriod(value, 1000, str, "тысяча", "тысячи", "тысяч", false);
|
||
|
||
var hundreds = (int)(value / 100);
|
||
if (hundreds != 0)
|
||
AppendWithSpace(str, Hunds[hundreds]);
|
||
|
||
var less100 = (int)(value % 100);
|
||
var frac20 = male ? Frac20Male : Frac20Female;
|
||
if (less100 < 20)
|
||
AppendWithSpace(str, frac20[less100]);
|
||
else
|
||
{
|
||
var tens = less100 / 10;
|
||
AppendWithSpace(str, Tens[tens]);
|
||
var less10 = less100 % 10;
|
||
if (less10 != 0)
|
||
str.Append(" " + frac20[less100%10]);
|
||
}
|
||
|
||
return str.ToString();
|
||
}
|
||
|
||
private static void AppendWithSpace(StringBuilder stringBuilder, string str)
|
||
{
|
||
if (stringBuilder.Length > 0)
|
||
stringBuilder.Append(" ");
|
||
stringBuilder.Append(str);
|
||
}
|
||
|
||
private static long AppendPeriod(
|
||
long value,
|
||
long power,
|
||
StringBuilder str,
|
||
string declension1,
|
||
string declension2,
|
||
string declension5,
|
||
bool male)
|
||
{
|
||
var thousands = (int)(value / power);
|
||
if (thousands > 0)
|
||
{
|
||
AppendWithSpace(str, NumberToText(thousands, male, declension1, declension2, declension5));
|
||
return value % power;
|
||
}
|
||
return value;
|
||
}
|
||
|
||
private static string NumberToText(
|
||
long value,
|
||
bool male,
|
||
string valueDeclensionFor1,
|
||
string valueDeclensionFor2,
|
||
string valueDeclensionFor5)
|
||
{
|
||
return
|
||
NumberToText(value, male)
|
||
+ " "
|
||
+ GetDeclension((int)(value % 10), valueDeclensionFor1, valueDeclensionFor2, valueDeclensionFor5);
|
||
}
|
||
|
||
private static string GetDeclension(int val, string one, string two, string five)
|
||
{
|
||
var t = (val % 100 > 20) ? val % 10 : val % 20;
|
||
|
||
switch (t)
|
||
{
|
||
case 1:
|
||
return one;
|
||
case 2:
|
||
case 3:
|
||
case 4:
|
||
return two;
|
||
default:
|
||
return five;
|
||
}
|
||
}
|
||
}
|