From 9cd67255703d3afca42aef9dc6cc262d8aee071e Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 5 Jan 2024 10:07:41 +0000 Subject: [PATCH] Added voice selection, username filter and word filter from hermes --- TwitchChatTTS/Hermes/HermesClient.cs | 64 ++++++- TwitchChatTTS/Hermes/TTSUsernameFilter.cs | 5 + TwitchChatTTS/Hermes/TTSVoice.cs | 6 + TwitchChatTTS/Hermes/TTSWordFilter.cs | 22 +++ TwitchChatTTS/Message/MessageHandler.cs | 200 ++++++++++++++++++++ TwitchChatTTS/Message/MessageResult.cs | 5 + TwitchChatTTS/Program.cs | 190 +++---------------- TwitchChatTTS/Speech/AudioPlaybackEngine.cs | 10 +- TwitchChatTTS/Twitch/TwitchApiClient.cs | 4 +- TwitchChatTTS/Web.cs | 13 +- 10 files changed, 339 insertions(+), 180 deletions(-) create mode 100644 TwitchChatTTS/Hermes/TTSUsernameFilter.cs create mode 100644 TwitchChatTTS/Hermes/TTSVoice.cs create mode 100644 TwitchChatTTS/Hermes/TTSWordFilter.cs create mode 100644 TwitchChatTTS/Message/MessageHandler.cs create mode 100644 TwitchChatTTS/Message/MessageResult.cs diff --git a/TwitchChatTTS/Hermes/HermesClient.cs b/TwitchChatTTS/Hermes/HermesClient.cs index 592ac3a..a6761ab 100644 --- a/TwitchChatTTS/Hermes/HermesClient.cs +++ b/TwitchChatTTS/Hermes/HermesClient.cs @@ -1,8 +1,10 @@ using System; +using TwitchChatTTS.Hermes; public class HermesClient { private Account account; private string key; + private WebHelper _web; public string Id { get => account?.id; } public string Username { get => account?.username; } @@ -15,23 +17,73 @@ public class HermesClient { } key = File.ReadAllText(".token")?.Trim(); - WebHelper.AddHeader("x-api-key", key); + _web = new WebHelper(); + _web.AddHeader("x-api-key", key); } public async Task UpdateHermesAccount() { - account = await WebHelper.GetJson("https://hermes.goblincaves.com/api/account"); + ValidateKey(); + account = await _web.GetJson("https://hermes.goblincaves.com/api/account"); } public async Task FetchTwitchBotToken() { - if (string.IsNullOrWhiteSpace(key)) { - throw new InvalidOperationException("Hermes API key not provided."); - } + ValidateKey(); - var token = await WebHelper.GetJson("https://hermes.goblincaves.com/api/token/bot"); + var token = await _web.GetJson("https://hermes.goblincaves.com/api/token/bot"); if (token == null) { throw new Exception("Failed to fetch Twitch API token from Hermes."); } return token; } + + public async Task> FetchTTSUsernameFilters() { + ValidateKey(); + + var filters = await _web.GetJson>("https://hermes.goblincaves.com/api/settings/tts/filter/users"); + if (filters == null) { + throw new Exception("Failed to fetch TTS username filters from Hermes."); + } + + return filters; + } + + public async Task FetchTTSDefaultVoice() { + ValidateKey(); + + var data = await _web.GetJson("https://hermes.goblincaves.com/api/settings/tts/default"); + if (data == null) { + throw new Exception("Failed to fetch TTS default voice from Hermes."); + } + + return data.label; + } + + public async Task> FetchTTSEnabledVoices() { + ValidateKey(); + + var voices = await _web.GetJson>("https://hermes.goblincaves.com/api/settings/tts"); + if (voices == null) { + throw new Exception("Failed to fetch TTS enabled voices from Hermes."); + } + + return voices; + } + + public async Task> FetchTTSWordFilters() { + ValidateKey(); + + var filters = await _web.GetJson>("https://hermes.goblincaves.com/api/settings/tts/filter/words"); + if (filters == null) { + throw new Exception("Failed to fetch TTS word filters from Hermes."); + } + + return filters; + } + + private void ValidateKey() { + if (string.IsNullOrWhiteSpace(key)) { + throw new InvalidOperationException("Hermes API key not provided."); + } + } } \ No newline at end of file diff --git a/TwitchChatTTS/Hermes/TTSUsernameFilter.cs b/TwitchChatTTS/Hermes/TTSUsernameFilter.cs new file mode 100644 index 0000000..af2404a --- /dev/null +++ b/TwitchChatTTS/Hermes/TTSUsernameFilter.cs @@ -0,0 +1,5 @@ +public class TTSUsernameFilter { + public string username { get; set; } + public string tag { get; set; } + public string userId { get; set; } +} \ No newline at end of file diff --git a/TwitchChatTTS/Hermes/TTSVoice.cs b/TwitchChatTTS/Hermes/TTSVoice.cs new file mode 100644 index 0000000..d759b31 --- /dev/null +++ b/TwitchChatTTS/Hermes/TTSVoice.cs @@ -0,0 +1,6 @@ +public class TTSVoice { + public string label { get; set; } + public int value { get; set; } + public string gender { get; set; } + public string language { get; set; } +} \ No newline at end of file diff --git a/TwitchChatTTS/Hermes/TTSWordFilter.cs b/TwitchChatTTS/Hermes/TTSWordFilter.cs new file mode 100644 index 0000000..327b982 --- /dev/null +++ b/TwitchChatTTS/Hermes/TTSWordFilter.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace TwitchChatTTS.Hermes +{ + public class TTSWordFilter + { + public string id { get; set; } + public string search { get; set; } + public string replace { get; set; } + public string userId { get; set; } + + public bool IsRegex { get; set; } + + + public TTSWordFilter() { + IsRegex = true; + } + } +} \ No newline at end of file diff --git a/TwitchChatTTS/Message/MessageHandler.cs b/TwitchChatTTS/Message/MessageHandler.cs new file mode 100644 index 0000000..74d11b0 --- /dev/null +++ b/TwitchChatTTS/Message/MessageHandler.cs @@ -0,0 +1,200 @@ +using System.Text.RegularExpressions; +using TwitchLib.Client.Events; +using TwitchChatTTS.Hermes; + + +public class ChatMessageHandler { + private TTSPlayer Player { get; } + public string DefaultVoice { get; set; } + public IEnumerable EnabledVoices { get; } + public Dictionary UsernameFilters { get; } + public IEnumerable WordFilters { get; } + + private Regex voicesRegex; + private Regex sfxRegex; + + + public ChatMessageHandler(TTSPlayer player, string defaultVoice, IEnumerable enabledVoices, Dictionary usernameFilters, IEnumerable wordFilters) { + Player = player; + DefaultVoice = defaultVoice; + EnabledVoices = enabledVoices; + UsernameFilters = usernameFilters; + WordFilters = wordFilters; + + voicesRegex = GenerateEnabledVoicesRegex(); + sfxRegex = new Regex(@"\(([A-Za-z0-9_-]+)\)"); + } + + + public MessageResult Handle(OnMessageReceivedArgs e) { + var m = e.ChatMessage; + var msg = e.ChatMessage.Message; + + // Skip TTS messages + if ((m.IsVip || m.IsModerator || m.IsBroadcaster) && (msg.ToLower().StartsWith("!skip ") || msg.ToLower() == "!skip")) { + return MessageResult.Skip; + } + + if (UsernameFilters.TryGetValue(m.Username, out TTSUsernameFilter filter) && filter.tag == "blacklisted") { + return MessageResult.Blocked; + } + + // Ensure we can send it via the web. + var alphanumeric = new Regex(@"[^a-zA-Z0-9!@#$%&\^*+\-_(),+':;?.,\[\]\s\\/~`]"); + msg = alphanumeric.Replace(msg, ""); + + // Filter highly repetitive words (like emotes) from the message. + var words = msg.Split(" "); + var wordCounter = new Dictionary(); + string filteredMsg = string.Empty; + foreach (var w in words) { + if (wordCounter.ContainsKey(w)) { + wordCounter[w]++; + } else { + wordCounter.Add(w, 1); + } + + if (wordCounter[w] < 5) { + filteredMsg += w + " "; + } + } + msg = filteredMsg; + + foreach (var wf in WordFilters) { + if (wf.IsRegex) { + try { + var regex = new Regex(wf.search); + msg = regex.Replace(msg, wf.replace); + continue; + } catch (Exception ex) { + wf.IsRegex = false; + } + } + + msg = msg.Replace(wf.search, wf.replace); + } + + int priority = 0; + if (m.IsStaff) { + priority = int.MinValue; + } else if (filter?.tag == "priority") { + priority = int.MinValue + 1; + } else if (m.IsModerator) { + priority = -100; + } else if (m.IsVip) { + priority = -10; + } else if (m.IsPartner) { + priority = -5; + } else if (m.IsHighlighted) { + priority = -1; + } + priority = (int) Math.Round(Math.Min(priority, -m.SubscribedMonthCount * (m.Badges.Any(b => b.Key == "subscriber" && b.Value == "1") ? 1.2 : 1))); + + var matches = voicesRegex.Matches(msg); + int defaultEnd = matches.FirstOrDefault()?.Index ?? msg.Length; + if (defaultEnd > 0) { + HandlePartialMessage(priority, DefaultVoice, msg.Substring(0, defaultEnd).Trim(), e); + } + + foreach (Match match in matches) { + var message = match.Groups[2].ToString(); + if (string.IsNullOrWhiteSpace(message)) { + continue; + } + + var voice = match.Groups[1].ToString(); + voice = voice[0].ToString().ToUpper() + voice.Substring(1).ToLower(); + HandlePartialMessage(priority, voice, message.Trim(), e); + } + + return MessageResult.None; + } + + private void HandlePartialMessage(int priority, string voice, string message, OnMessageReceivedArgs e) { + if (string.IsNullOrWhiteSpace(message)) { + return; + } + + var m = e.ChatMessage; + var parts = sfxRegex.Split(message); + var badgesString = string.Join(", ", e.ChatMessage.Badges.Select(b => b.Key + " = " + b.Value)); + + if (parts.Length == 1) { + Console.WriteLine($"Voice: {voice}; Priority: {priority}; Message: {message}; Month: {m.SubscribedMonthCount}; {badgesString}"); + Player.Add(new TTSMessage() { + Voice = voice, + Message = message, + Moderator = m.IsModerator, + Timestamp = DateTime.UtcNow, + Username = m.Username, + Bits = m.Bits, + Badges = m.Badges, + Priority = priority + }); + return; + } + + var sfxMatches = sfxRegex.Matches(message); + var sfxStart = sfxMatches.FirstOrDefault()?.Index ?? message.Length; + + for (var i = 0; i < sfxMatches.Count; i++) { + var sfxMatch = sfxMatches[i]; + var sfxName = sfxMatch.Groups[1]?.ToString()?.ToLower(); + + if (!File.Exists("sfx/" + sfxName + ".mp3")) { + parts[i * 2 + 2] = parts[i * 2] + " (" + parts[i * 2 + 1] + ")" + parts[i * 2 + 2]; + continue; + } + + if (!string.IsNullOrWhiteSpace(parts[i * 2])) { + Console.WriteLine($"Voice: {voice}; Priority: {priority}; Message: {parts[i * 2]}; Month: {m.SubscribedMonthCount}; {badgesString}"); + Player.Add(new TTSMessage() { + Voice = voice, + Message = parts[i * 2], + Moderator = m.IsModerator, + Timestamp = DateTime.UtcNow, + Username = m.Username, + Bits = m.Bits, + Badges = m.Badges, + Priority = priority + }); + } + + Console.WriteLine($"Voice: {voice}; Priority: {priority}; SFX: {sfxName}; Month: {m.SubscribedMonthCount}; {badgesString}"); + Player.Add(new TTSMessage() { + Voice = voice, + Message = sfxName, + File = $"sfx/{sfxName}.mp3", + Moderator = m.IsModerator, + Timestamp = DateTime.UtcNow, + Username = m.Username, + Bits = m.Bits, + Badges = m.Badges, + Priority = priority + }); + } + + if (!string.IsNullOrWhiteSpace(parts.Last())) { + Console.WriteLine($"Voice: {voice}; Priority: {priority}; Message: {parts.Last()}; Month: {m.SubscribedMonthCount}; {badgesString}"); + Player.Add(new TTSMessage() { + Voice = voice, + Message = parts.Last(), + Moderator = m.IsModerator, + Timestamp = DateTime.UtcNow, + Username = m.Username, + Bits = m.Bits, + Badges = m.Badges, + Priority = priority + }); + } + } + + private Regex GenerateEnabledVoicesRegex() { + if (EnabledVoices == null || EnabledVoices.Count() <= 0) { + return null; + } + + var enabledVoicesString = string.Join("|", EnabledVoices.Select(v => v.label)); + return new Regex($@"\b({enabledVoicesString})\:(.*?)(?=\Z|\b(?:{enabledVoicesString})\:)", RegexOptions.IgnoreCase); + } +} \ No newline at end of file diff --git a/TwitchChatTTS/Message/MessageResult.cs b/TwitchChatTTS/Message/MessageResult.cs new file mode 100644 index 0000000..5103a4b --- /dev/null +++ b/TwitchChatTTS/Message/MessageResult.cs @@ -0,0 +1,5 @@ +public enum MessageResult { + Skip = 1, + Blocked = 2, + None = 0 +} \ No newline at end of file diff --git a/TwitchChatTTS/Program.cs b/TwitchChatTTS/Program.cs index 3233b9e..da06bb3 100644 --- a/TwitchChatTTS/Program.cs +++ b/TwitchChatTTS/Program.cs @@ -37,7 +37,6 @@ HermesClient hermes = new HermesClient(); Console.WriteLine("Fetching Hermes account details..."); await hermes.UpdateHermesAccount(); -Console.WriteLine("ID: " + hermes.Id); Console.WriteLine("Username: " + hermes.Username); Console.WriteLine(); @@ -45,178 +44,42 @@ Console.WriteLine("Fetching Twitch API details from Hermes..."); TwitchApiClient twitchapiclient = new TwitchApiClient(await hermes.FetchTwitchBotToken()); await twitchapiclient.Authorize(); -var sfxRegex = new Regex(@"\(([A-Za-z0-9_-]+)\)"); -var voiceRegex = new Regex(@"\b(Filiz|Astrid|Tatyana|Maxim|Carmen|Ines|Cristiano|Vitoria|Ricardo|Maja|Jan|Jacek|Ewa|Ruben|Lotte|Liv|Seoyeon|Takumi|Mizuki|Giorgio|Carla|Bianca|Karl|Dora|Mathieu|Celine|Chantal|Penelope|Miguel|Mia|Enrique|Conchita|Geraint|Salli|Matthew|Kimberly|Kendra|Justin|Joey|Joanna|Ivy|Raveena|Aditi|Emma|Brian|Amy|Russell|Nicole|Vicki|Marlene|Hans|Naja|Mads|Gwyneth|Zhiyu|Tracy|Danny|Huihui|Yaoyao|Kangkang|HanHan|Zhiwei|Asaf|An|Stefanos|Filip|Ivan|Heidi|Herena|Kalpana|Hemant|Matej|Andika|Rizwan|Lado|Valluvar|Linda|Heather|Sean|Michael|Karsten|Guillaume|Pattara|Jakub|Szabolcs|Hoda|Naayf)\:(.*?)(?=\Z|\b(?:Filiz|Astrid|Tatyana|Maxim|Carmen|Ines|Cristiano|Vitoria|Ricardo|Maja|Jan|Jacek|Ewa|Ruben|Lotte|Liv|Seoyeon|Takumi|Mizuki|Giorgio|Carla|Bianca|Karl|Dora|Mathieu|Celine|Chantal|Penelope|Miguel|Mia|Enrique|Conchita|Geraint|Salli|Matthew|Kimberly|Kendra|Justin|Joey|Joanna|Ivy|Raveena|Aditi|Emma|Brian|Amy|Russell|Nicole|Vicki|Marlene|Hans|Naja|Mads|Gwyneth|Zhiyu|Tracy|Danny|Huihui|Yaoyao|Kangkang|HanHan|Zhiwei|Asaf|An|Stefanos|Filip|Ivan|Heidi|Herena|Kalpana|Hemant|Matej|Andika|Rizwan|Lado|Valluvar|Linda|Heather|Sean|Michael|Karsten|Guillaume|Pattara|Jakub|Szabolcs|Hoda|Naayf)\:)", RegexOptions.IgnoreCase); +Console.WriteLine("Fetching TTS username filters..."); +var usernameFilters = (await hermes.FetchTTSUsernameFilters()) + .ToDictionary(x => x.username, x => x); +Console.WriteLine($"{usernameFilters.Where(f => f.Value.tag == "blacklisted").Count()} username(s) have been blocked."); +Console.WriteLine($"{usernameFilters.Where(f => f.Value.tag == "priority").Count()} user(s) have been prioritized."); + +var enabledVoices = await hermes.FetchTTSEnabledVoices(); +Console.WriteLine($"{enabledVoices.Count()} TTS voices enabled."); + +var wordFilters = await hermes.FetchTTSWordFilters(); +Console.WriteLine($"{wordFilters.Count()} TTS word filters."); + +var defaultVoice = await hermes.FetchTTSDefaultVoice(); +Console.WriteLine("Default Voice: " + defaultVoice); TTSPlayer player = new TTSPlayer(); ISampleProvider playing = null; +var handler = new ChatMessageHandler(player, defaultVoice, enabledVoices, usernameFilters, wordFilters); + var channels = File.Exists(".twitchchannels") ? File.ReadAllLines(".twitchchannels") : new string[] { hermes.Username }; +Console.WriteLine("Twitch channels: " + string.Join(", ", channels)); twitchapiclient.InitializeClient(hermes, channels); twitchapiclient.InitializePublisher(player, redeems); -void HandleMessage(int priority, string voice, string message, OnMessageReceivedArgs e, bool bot) { - var m = e.ChatMessage; - var parts = sfxRegex.Split(message); - var sfxMatches = sfxRegex.Matches(message); - var sfxStart = sfxMatches.FirstOrDefault()?.Index ?? message.Length; - var alphanumeric = new Regex(@"[^a-zA-Z0-9!@#$%&\^*+\-_(),+':;?.,\[\]\s\\/~`]"); - message = alphanumeric.Replace(message, " "); - - if (string.IsNullOrWhiteSpace(message)) { - return; - } - - if (parts.Length == 1) { - Console.WriteLine($"Voice: {voice}; Priority: {priority}; Message: {message}; Month: {m.SubscribedMonthCount}; {string.Join(", ", e.ChatMessage.Badges.Select(b => b.Key + " = " + b.Value))}"); - player.Add(new TTSMessage() { - Voice = voice, - Bot = bot, - Message = message, - Moderator = m.IsModerator, - Timestamp = DateTime.UtcNow, - Username = m.Username, - Bits = m.Bits, - Badges = m.Badges, - Priority = priority - }); - return; - } - - for (var i = 0; i < sfxMatches.Count; i++) { - var sfxMatch = sfxMatches[i]; - var sfxName = sfxMatch.Groups[1]?.ToString()?.ToLower(); - - if (!File.Exists("sfx/" + sfxName + ".mp3")) { - parts[i * 2 + 2] = parts[i * 2] + " (" + parts[i * 2 + 1] + ")" + parts[i * 2 + 2]; - continue; - } - - if (!string.IsNullOrWhiteSpace(parts[i * 2])) { - Console.WriteLine($"Voice: {voice}; Priority: {priority}; Message: {parts[i * 2]}; Month: {m.SubscribedMonthCount}; {string.Join(", ", e.ChatMessage.Badges.Select(b => b.Key + " = " + b.Value))}"); - player.Add(new TTSMessage() { - Voice = voice, - Bot = bot, - Message = parts[i * 2], - Moderator = m.IsModerator, - Timestamp = DateTime.UtcNow, - Username = m.Username, - Bits = m.Bits, - Badges = m.Badges, - Priority = priority - }); - } - - Console.WriteLine($"Voice: {voice}; Priority: {priority}; SFX: {sfxName}; Month: {m.SubscribedMonthCount}; {string.Join(", ", e.ChatMessage.Badges.Select(b => b.Key + " = " + b.Value))}"); - player.Add(new TTSMessage() { - Voice = voice, - Bot = bot, - Message = sfxName, - File = $"sfx/{sfxName}.mp3", - Moderator = m.IsModerator, - Timestamp = DateTime.UtcNow, - Username = m.Username, - Bits = m.Bits, - Badges = m.Badges, - Priority = priority - }); - } - - if (!string.IsNullOrWhiteSpace(parts.Last())) { - Console.WriteLine($"Voice: {voice}; Priority: {priority}; Message: {parts.Last()}; Month: {m.SubscribedMonthCount}; {string.Join(", ", e.ChatMessage.Badges.Select(b => b.Key + " = " + b.Value))}"); - player.Add(new TTSMessage() { - Voice = voice, - Bot = bot, - Message = parts.Last(), - Moderator = m.IsModerator, - Timestamp = DateTime.UtcNow, - Username = m.Username, - Bits = m.Bits, - Badges = m.Badges, - Priority = priority - }); - } -} twitchapiclient.AddOnNewMessageReceived(async Task (object? s, OnMessageReceivedArgs e) => { - var m = e.ChatMessage; - var msg = e.ChatMessage.Message; - if ((m.IsVip || m.IsModerator || m.IsBroadcaster) && (msg.ToLower().StartsWith("!skip ") || msg.ToLower() == "!skip")) { - AudioPlaybackEngine.Instance.RemoveMixerInput(playing); - playing = null; - return; - } + var result = handler.Handle(e); - string[] bots = new string[] { "nightbot", "streamelements", "own3d", "streamlabs", "soundalerts", "pokemoncommunitygame" }; - bool bot = bots.Any(b => b == m.Username); - if (bot || m.IsBroadcaster || msg.StartsWith('!')) { - return; - } - - string[] bad = new string[] { "incel", "simp", "virgin", "faggot", "fagg", "fag", "nigger", "nigga", "nigg", "nig", "whore", "retard", "cock", "fuck", "bastard", "wanker", "bollocks", "motherfucker", "bitch", "bish", "bich", "asshole", "ass", "dick", "dickhead", "frigger", "shit", "slut", "turd", "twat", "nigra", "penis" }; - foreach (var b in bad) { - msg = new Regex($@"\b{b}\b", RegexOptions.IgnoreCase).Replace(msg, ""); - } - - msg = new Regex(@"%").Replace(msg, " percent "); - msg = new Regex(@"https?\:\/\/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)").Replace(msg, ""); - msg = new Regex(@"\bfreeze153").Replace(msg, ""); - - // Filter highly repetitive words (like emotes) from message. - var words = msg.Split(" "); - var wordCounter = new Dictionary(); - string filteredMsg = string.Empty; - foreach (var w in words) { - if (wordCounter.ContainsKey(w)) { - wordCounter[w]++; - } else { - wordCounter.Add(w, 1); - } - - if (wordCounter[w] < 5) { - filteredMsg += w + " "; - } - } - msg = filteredMsg; - - foreach (var w in words) { - if (wordCounter.ContainsKey(w)) { - wordCounter[w]++; - } else { - wordCounter.Add(w, 1); - } - } - - int priority = 0; - if (m.IsStaff) { - priority = int.MinValue; - } else if (m.IsModerator) { - priority = -100; - } else if (m.IsVip) { - priority = -10; - } else if (m.IsPartner) { - priority = -5; - } else if (m.IsHighlighted) { - priority = -1; - } - priority = (int) Math.Round(Math.Min(priority, -m.SubscribedMonthCount * (m.Badges.Any(b => b.Key == "subscriber" && b.Value == "1") ? 1.2 : 1))); - - var matches = voiceRegex.Matches(msg); - int defaultEnd = matches.FirstOrDefault()?.Index ?? msg.Length; - if (defaultEnd > 0) { - HandleMessage(priority, "Brian", msg.Substring(0, defaultEnd), e, bot); - } - - foreach (Match match in matches) { - var message = match.Groups[2].ToString(); - if (string.IsNullOrWhiteSpace(message)) { - continue; - } - - var voice = match.Groups[1].ToString(); - voice = voice[0].ToString().ToUpper() + voice.Substring(1).ToLower(); - - HandleMessage(priority, voice, message, e, bot); + switch (result) { + case MessageResult.Skip: + AudioPlaybackEngine.Instance.RemoveMixerInput(playing); + playing = null; + break; + default: + break; } }); @@ -239,8 +102,9 @@ Task.Run(async () => { var sound = new NetworkWavSound(url); var provider = new CachedWavProvider(sound); var data = AudioPlaybackEngine.Instance.ConvertSound(provider); + var resampled = new WdlResamplingSampleProvider(data, AudioPlaybackEngine.Instance.SampleRate); - m.Audio = data; + m.Audio = resampled; player.Ready(m); } catch (COMException e) { Console.WriteLine(e.GetType().Name + ": " + e.Message + " (HResult: " + e.HResult + ")"); diff --git a/TwitchChatTTS/Speech/AudioPlaybackEngine.cs b/TwitchChatTTS/Speech/AudioPlaybackEngine.cs index eebe34c..de68930 100644 --- a/TwitchChatTTS/Speech/AudioPlaybackEngine.cs +++ b/TwitchChatTTS/Speech/AudioPlaybackEngine.cs @@ -4,16 +4,20 @@ using NAudio.Wave.SampleProviders; public class AudioPlaybackEngine : IDisposable { - public static readonly AudioPlaybackEngine Instance = new AudioPlaybackEngine(22050, 1); + public static readonly AudioPlaybackEngine Instance = new AudioPlaybackEngine(44100, 2); private readonly IWavePlayer outputDevice; private readonly MixingSampleProvider mixer; + public int SampleRate { get; } private AudioPlaybackEngine(int sampleRate = 44100, int channelCount = 2) { + SampleRate = sampleRate; outputDevice = new WaveOutEvent(); + mixer = new MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channelCount)); mixer.ReadFully = true; + outputDevice.Init(mixer); outputDevice.Play(); } @@ -34,7 +38,7 @@ public class AudioPlaybackEngine : IDisposable public void PlaySound(string fileName) { var input = new AudioFileReader(fileName); - AddMixerInput(new AutoDisposeFileReader(input)); + AddMixerInput(new WdlResamplingSampleProvider(ConvertToRightChannelCount(new AutoDisposeFileReader(input)), SampleRate)); } public void PlaySound(NetworkWavSound sound) @@ -63,7 +67,7 @@ public class AudioPlaybackEngine : IDisposable } else { throw new ArgumentException("Unsupported source encoding while adding to mixer."); } - return converted; + return ConvertToRightChannelCount(converted); } public void AddMixerInput(ISampleProvider input) diff --git a/TwitchChatTTS/Twitch/TwitchApiClient.cs b/TwitchChatTTS/Twitch/TwitchApiClient.cs index aefabcf..ac28274 100644 --- a/TwitchChatTTS/Twitch/TwitchApiClient.cs +++ b/TwitchChatTTS/Twitch/TwitchApiClient.cs @@ -11,18 +11,20 @@ public class TwitchApiClient { private TwitchBotToken token; private TwitchClient client; private TwitchPubSub publisher; + private WebHelper web; private bool initialized; public TwitchApiClient(TwitchBotToken token) { client = new TwitchClient(new WebSocketClient()); publisher = new TwitchPubSub(); + web = new WebHelper(); initialized = false; this.token = token; } public async Task Authorize() { - var authorize = await WebHelper.Get("https://hermes.goblincaves.com/api/account/reauthorize"); + var authorize = await web.Get("https://hermes.goblincaves.com/api/account/reauthorize"); var status = (int) authorize.StatusCode; return status == 200 || status == 201; } diff --git a/TwitchChatTTS/Web.cs b/TwitchChatTTS/Web.cs index 8194991..44b8ec1 100644 --- a/TwitchChatTTS/Web.cs +++ b/TwitchChatTTS/Web.cs @@ -3,27 +3,26 @@ using System.Net; using System.Net.Http.Json; -public static class WebHelper { +public class WebHelper { private static HttpClient _client = new HttpClient(); - public static void AddHeader(string key, string? value) { + public void AddHeader(string key, string? value) { _client.DefaultRequestHeaders.Add(key, value); - //ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13; } - public static async Task GetJson(string uri) { + public async Task GetJson(string uri) { return (T) await _client.GetFromJsonAsync(uri, typeof(T)); } - public static async Task Get(string uri) { + public async Task Get(string uri) { return await _client.GetAsync(uri); } - public static async Task Post(string uri, T data) { + public async Task Post(string uri, T data) { return await _client.PostAsJsonAsync(uri, data); } - public static async Task Post(string uri) { + public async Task Post(string uri) { return await _client.PostAsJsonAsync(uri, new object()); } } \ No newline at end of file