First commit with progress so far
This commit is contained in:
commit
72be20594d
22
Projects.sln
Normal file
22
Projects.sln
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwitchChatTTS", "TwitchChatTTS\TwitchChatTTS.csproj", "{7A371F54-F9D5-49C9-BE2D-819C60A0D621}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7A371F54-F9D5-49C9-BE2D-819C60A0D621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7A371F54-F9D5-49C9-BE2D-819C60A0D621}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7A371F54-F9D5-49C9-BE2D-819C60A0D621}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7A371F54-F9D5-49C9-BE2D-819C60A0D621}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
2
TwitchChatTTS/.redeems
Normal file
2
TwitchChatTTS/.redeems
Normal file
@ -0,0 +1,2 @@
|
||||
hydration
|
||||
test
|
1
TwitchChatTTS/.token
Normal file
1
TwitchChatTTS/.token
Normal file
@ -0,0 +1 @@
|
||||
1C2920l0uhZfl4uaNSquFRec6cCiHCLS
|
1
TwitchChatTTS/.twitchchannels
Normal file
1
TwitchChatTTS/.twitchchannels
Normal file
@ -0,0 +1 @@
|
||||
freezeflame99
|
9
TwitchChatTTS/Hermes/Account.cs
Normal file
9
TwitchChatTTS/Hermes/Account.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[Serializable]
|
||||
public class Account {
|
||||
[AllowNull]
|
||||
public string id { get; set; }
|
||||
[AllowNull]
|
||||
public string username { get; set; }
|
||||
}
|
37
TwitchChatTTS/Hermes/HermesClient.cs
Normal file
37
TwitchChatTTS/Hermes/HermesClient.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
public class HermesClient {
|
||||
private Account account;
|
||||
private string key;
|
||||
|
||||
public string Id { get => account?.id; }
|
||||
public string Username { get => account?.username; }
|
||||
|
||||
|
||||
public HermesClient() {
|
||||
// Read API Key from file.
|
||||
if (!File.Exists(".token")) {
|
||||
throw new Exception("Ensure you have written your API key in \".token\" file, in the same folder as this application.");
|
||||
}
|
||||
|
||||
key = File.ReadAllText(".token")?.Trim();
|
||||
WebHelper.AddHeader("x-api-key", key);
|
||||
}
|
||||
|
||||
public async Task UpdateHermesAccount() {
|
||||
account = await WebHelper.GetJson<Account>("https://hermes.goblincaves.com/api/account");
|
||||
}
|
||||
|
||||
public async Task<TwitchBotToken> FetchTwitchBotToken() {
|
||||
if (string.IsNullOrWhiteSpace(key)) {
|
||||
throw new InvalidOperationException("Hermes API key not provided.");
|
||||
}
|
||||
|
||||
var token = await WebHelper.GetJson<TwitchBotToken>("https://hermes.goblincaves.com/api/token/bot");
|
||||
if (token == null) {
|
||||
throw new Exception("Failed to fetch Twitch API token from Hermes.");
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
8
TwitchChatTTS/Hermes/TwitchBotToken.cs
Normal file
8
TwitchChatTTS/Hermes/TwitchBotToken.cs
Normal file
@ -0,0 +1,8 @@
|
||||
[Serializable]
|
||||
public class TwitchBotToken {
|
||||
public string client_id { get; set; }
|
||||
public string client_secret { get; set; }
|
||||
public string access_token { get; set; }
|
||||
public string refresh_token { get; set; }
|
||||
public string broadcaster_id { get; set; }
|
||||
}
|
8
TwitchChatTTS/Hermes/TwitchConnection.cs
Normal file
8
TwitchChatTTS/Hermes/TwitchConnection.cs
Normal file
@ -0,0 +1,8 @@
|
||||
[Serializable]
|
||||
public class TwitchConnection {
|
||||
public string id { get; set; }
|
||||
public string secret { get; set; }
|
||||
public string broadcasterId { get; set; }
|
||||
public string username { get; set; }
|
||||
public string userId { get; set; }
|
||||
}
|
282
TwitchChatTTS/Program.cs
Normal file
282
TwitchChatTTS/Program.cs
Normal file
@ -0,0 +1,282 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using NAudio.Wave;
|
||||
using TwitchLib.Api;
|
||||
using TwitchLib.Client;
|
||||
using TwitchLib.Client.Events;
|
||||
using TwitchLib.Client.Models;
|
||||
using TwitchLib.Communication.Clients;
|
||||
using TwitchLib.Communication.Events;
|
||||
using TwitchLib.PubSub;
|
||||
using TwitchLib.PubSub.Events;
|
||||
using NAudio.Wave.SampleProviders;
|
||||
|
||||
/**
|
||||
Future handshake/connection procedure:
|
||||
- GET all tts config data
|
||||
- Continuous connection to server to receive commands from tom & send logs/errors (med priority, though tough task)
|
||||
|
||||
Ideas:
|
||||
- Filter messages by badges, username, ..., etc.
|
||||
- Filter messages by content.
|
||||
- Speed up TTS based on message queue size?
|
||||
- Cut TTS off shortly after raid (based on size of raid)?
|
||||
- Limit duration of TTS
|
||||
- Voice selection for channel and per user.
|
||||
**/
|
||||
|
||||
// dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained true
|
||||
// dotnet publish -r win-x64 -p:PublishSingleFile=true --self-contained true
|
||||
// SE voices: https://api.streamelements.com/kappa/v2/speech?voice=brian&text=hello
|
||||
|
||||
// Read redeems from file.
|
||||
var redeems = File.Exists(".redeems") ? await File.ReadAllLinesAsync(".redeems") : new string[0];
|
||||
|
||||
// Fetch id and username based on api key given.
|
||||
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();
|
||||
|
||||
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);
|
||||
|
||||
TTSPlayer player = new TTSPlayer();
|
||||
ISampleProvider playing = null;
|
||||
|
||||
var channels = File.Exists(".twitchchannels") ? File.ReadAllLines(".twitchchannels") : new string[] { hermes.Username };
|
||||
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;
|
||||
}
|
||||
|
||||
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, int>();
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
AudioPlaybackEngine.Instance.AddOnMixerInputEnded((object? s, SampleProviderEventArgs e) => {
|
||||
if (e.SampleProvider == playing) {
|
||||
playing = null;
|
||||
}
|
||||
});
|
||||
|
||||
Task.Run(async () => {
|
||||
while (true) {
|
||||
try {
|
||||
var m = player.ReceiveBuffer();
|
||||
if (m == null) {
|
||||
await Task.Delay(200);
|
||||
continue;
|
||||
}
|
||||
|
||||
string url = $"https://api.streamelements.com/kappa/v2/speech?voice={m.Voice}&text={m.Message}";
|
||||
var sound = new NetworkWavSound(url);
|
||||
var provider = new CachedWavProvider(sound);
|
||||
var data = AudioPlaybackEngine.Instance.ConvertSound(provider);
|
||||
|
||||
m.Audio = data;
|
||||
player.Ready(m);
|
||||
} catch (COMException e) {
|
||||
Console.WriteLine(e.GetType().Name + ": " + e.Message + " (HResult: " + e.HResult + ")");
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine(e.GetType().Name + ": " + e.Message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Task.Run(async () => {
|
||||
while (true) {
|
||||
try {
|
||||
while (player.IsEmpty() || playing != null) {
|
||||
await Task.Delay(200);
|
||||
}
|
||||
var m = player.ReceiveReady();
|
||||
if (m == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(m.File) && File.Exists(m.File)) {
|
||||
Console.WriteLine("Playing sfx: " + m.File);
|
||||
AudioPlaybackEngine.Instance.PlaySound(m.File);
|
||||
continue;
|
||||
}
|
||||
|
||||
Console.WriteLine("Playing message: " + m.Message);
|
||||
playing = m.Audio;
|
||||
AudioPlaybackEngine.Instance.AddMixerInput(m.Audio);
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine(e.GetType().Name + ": " + e.Message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Console.WriteLine("Twitch API client connecting...");
|
||||
twitchapiclient.Connect();
|
||||
Console.ReadLine();
|
||||
Console.ReadLine();
|
90
TwitchChatTTS/Speech/AudioPlaybackEngine.cs
Normal file
90
TwitchChatTTS/Speech/AudioPlaybackEngine.cs
Normal file
@ -0,0 +1,90 @@
|
||||
using NAudio.Wave;
|
||||
using NAudio.Extras;
|
||||
using NAudio.Wave.SampleProviders;
|
||||
|
||||
public class AudioPlaybackEngine : IDisposable
|
||||
{
|
||||
public static readonly AudioPlaybackEngine Instance = new AudioPlaybackEngine(22050, 1);
|
||||
|
||||
private readonly IWavePlayer outputDevice;
|
||||
private readonly MixingSampleProvider mixer;
|
||||
|
||||
private AudioPlaybackEngine(int sampleRate = 44100, int channelCount = 2)
|
||||
{
|
||||
outputDevice = new WaveOutEvent();
|
||||
mixer = new MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channelCount));
|
||||
mixer.ReadFully = true;
|
||||
outputDevice.Init(mixer);
|
||||
outputDevice.Play();
|
||||
}
|
||||
|
||||
private ISampleProvider ConvertToRightChannelCount(ISampleProvider input)
|
||||
{
|
||||
if (input.WaveFormat.Channels == mixer.WaveFormat.Channels)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
if (input.WaveFormat.Channels == 1 && mixer.WaveFormat.Channels == 2)
|
||||
{
|
||||
return new MonoToStereoSampleProvider(input);
|
||||
}
|
||||
throw new NotImplementedException("Not yet implemented this channel count conversion");
|
||||
}
|
||||
|
||||
public void PlaySound(string fileName)
|
||||
{
|
||||
var input = new AudioFileReader(fileName);
|
||||
AddMixerInput(new AutoDisposeFileReader(input));
|
||||
}
|
||||
|
||||
public void PlaySound(NetworkWavSound sound)
|
||||
{
|
||||
AddMixerInput(new CachedWavProvider(sound));
|
||||
}
|
||||
|
||||
public ISampleProvider ConvertSound(IWaveProvider provider) {
|
||||
ISampleProvider converted = null;
|
||||
if (provider.WaveFormat.Encoding == WaveFormatEncoding.Pcm) {
|
||||
if (provider.WaveFormat.BitsPerSample == 8) {
|
||||
converted = new Pcm8BitToSampleProvider(provider);
|
||||
} else if (provider.WaveFormat.BitsPerSample == 16) {
|
||||
converted = new Pcm16BitToSampleProvider(provider);
|
||||
} else if (provider.WaveFormat.BitsPerSample == 24) {
|
||||
converted = new Pcm24BitToSampleProvider(provider);
|
||||
} else if (provider.WaveFormat.BitsPerSample == 32) {
|
||||
converted = new Pcm32BitToSampleProvider(provider);
|
||||
}
|
||||
} else if (provider.WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat) {
|
||||
if (provider.WaveFormat.BitsPerSample == 64) {
|
||||
converted = new WaveToSampleProvider64(provider);
|
||||
} else {
|
||||
converted = new WaveToSampleProvider(provider);
|
||||
}
|
||||
} else {
|
||||
throw new ArgumentException("Unsupported source encoding while adding to mixer.");
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
public void AddMixerInput(ISampleProvider input)
|
||||
{
|
||||
mixer.AddMixerInput(input);
|
||||
}
|
||||
|
||||
public void AddMixerInput(IWaveProvider input)
|
||||
{
|
||||
mixer.AddMixerInput(input);
|
||||
}
|
||||
|
||||
public void RemoveMixerInput(ISampleProvider sound) {
|
||||
mixer.RemoveMixerInput(sound);
|
||||
}
|
||||
|
||||
public void AddOnMixerInputEnded(EventHandler<SampleProviderEventArgs> e) {
|
||||
mixer.MixerInputEnded += e;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
outputDevice.Dispose();
|
||||
}
|
||||
}
|
45
TwitchChatTTS/Speech/NetworkCachedSound.cs
Normal file
45
TwitchChatTTS/Speech/NetworkCachedSound.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using NAudio.Wave;
|
||||
using System;
|
||||
|
||||
public class NetworkWavSound
|
||||
{
|
||||
public byte[] AudioData { get; private set; }
|
||||
public WaveFormat WaveFormat { get; private set; }
|
||||
|
||||
public NetworkWavSound(string uri)
|
||||
{
|
||||
using (var mfr = new MediaFoundationReader(uri)) {
|
||||
WaveFormat = mfr.WaveFormat;
|
||||
//Console.WriteLine("W: " + WaveFormat.SampleRate + " C: " + WaveFormat.Channels + " B: " + WaveFormat.BitsPerSample + " E: " + WaveFormat.Encoding);
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int read = 0;
|
||||
using (var ms = new MemoryStream()) {
|
||||
while ((read = mfr.Read(buffer, 0, buffer.Length)) > 0)
|
||||
ms.Write(buffer, 0, read);
|
||||
AudioData = ms.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CachedWavProvider : IWaveProvider
|
||||
{
|
||||
private readonly NetworkWavSound sound;
|
||||
private long position;
|
||||
private readonly RawSourceWaveStream stream;
|
||||
|
||||
public WaveFormat WaveFormat { get => sound.WaveFormat; }
|
||||
|
||||
|
||||
public CachedWavProvider(NetworkWavSound cachedSound)
|
||||
{
|
||||
sound = cachedSound;
|
||||
stream = new RawSourceWaveStream(new MemoryStream(sound.AudioData), sound.WaveFormat);
|
||||
}
|
||||
|
||||
public int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return stream.Read(buffer, offset, count);
|
||||
}
|
||||
}
|
76
TwitchChatTTS/Speech/TTSPlayer.cs
Normal file
76
TwitchChatTTS/Speech/TTSPlayer.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using NAudio.Wave;
|
||||
|
||||
public class TTSPlayer {
|
||||
private PriorityQueue<TTSMessage, int> _messages; // ready to play
|
||||
private PriorityQueue<TTSMessage, int> _buffer;
|
||||
private Mutex _mutex;
|
||||
private Mutex _mutex2;
|
||||
|
||||
public TTSPlayer() {
|
||||
_messages = new PriorityQueue<TTSMessage, int>();
|
||||
_buffer = new PriorityQueue<TTSMessage, int>();
|
||||
_mutex = new Mutex();
|
||||
_mutex2 = new Mutex();
|
||||
}
|
||||
|
||||
public void Add(TTSMessage message) {
|
||||
try {
|
||||
_mutex2.WaitOne();
|
||||
_buffer.Enqueue(message, message.Priority);
|
||||
} finally {
|
||||
_mutex2.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
|
||||
public TTSMessage ReceiveReady() {
|
||||
try {
|
||||
_mutex.WaitOne();
|
||||
if (_messages.TryDequeue(out TTSMessage message, out int _)) {
|
||||
return message;
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
_mutex.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
|
||||
public TTSMessage ReceiveBuffer() {
|
||||
try {
|
||||
_mutex2.WaitOne();
|
||||
if (_buffer.TryDequeue(out TTSMessage message, out int _)) {
|
||||
return message;
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
_mutex2.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
|
||||
public void Ready(TTSMessage message) {
|
||||
try {
|
||||
_mutex.WaitOne();
|
||||
_messages.Enqueue(message, message.Priority);
|
||||
} finally {
|
||||
_mutex.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEmpty() {
|
||||
return _messages.Count == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class TTSMessage {
|
||||
public string Voice { get; set; }
|
||||
public string Channel { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Message { get; set; }
|
||||
public string File { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
public bool Moderator { get; set; }
|
||||
public bool Bot { get; set; }
|
||||
public IEnumerable<KeyValuePair<string, string>> Badges { get; set; }
|
||||
public int Bits { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public ISampleProvider Audio { get; set; }
|
||||
}
|
120
TwitchChatTTS/Twitch/TwitchApiClient.cs
Normal file
120
TwitchChatTTS/Twitch/TwitchApiClient.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using TwitchLib.Api;
|
||||
using TwitchLib.Client;
|
||||
using TwitchLib.Client.Events;
|
||||
using TwitchLib.Client.Models;
|
||||
using TwitchLib.Communication.Clients;
|
||||
using TwitchLib.Communication.Events;
|
||||
using TwitchLib.PubSub;
|
||||
using TwitchLib.PubSub.Events;
|
||||
|
||||
public class TwitchApiClient {
|
||||
private TwitchBotToken token;
|
||||
private TwitchClient client;
|
||||
private TwitchPubSub publisher;
|
||||
private bool initialized;
|
||||
|
||||
|
||||
public TwitchApiClient(TwitchBotToken token) {
|
||||
client = new TwitchClient(new WebSocketClient());
|
||||
publisher = new TwitchPubSub();
|
||||
initialized = false;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public async Task<bool> Authorize() {
|
||||
var authorize = await WebHelper.Get("https://hermes.goblincaves.com/api/account/reauthorize");
|
||||
var status = (int) authorize.StatusCode;
|
||||
return status == 200 || status == 201;
|
||||
}
|
||||
|
||||
public async Task Connect() {
|
||||
client.Connect();
|
||||
await publisher.ConnectAsync();
|
||||
}
|
||||
|
||||
public void InitializeClient(HermesClient hermes, IEnumerable<string> channels) {
|
||||
ConnectionCredentials credentials = new ConnectionCredentials(hermes.Username, token.access_token);
|
||||
client.Initialize(credentials, channels.Distinct().ToList());
|
||||
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
|
||||
client.OnJoinedChannel += async Task (object? s, OnJoinedChannelArgs e) => {
|
||||
Console.WriteLine("Joined Channel: " + e.Channel);
|
||||
};
|
||||
|
||||
client.OnConnected += async Task (object? s, OnConnectedArgs e) => {
|
||||
Console.WriteLine("-----------------------------------------------------------");
|
||||
};
|
||||
|
||||
client.OnError += async Task (object? s, OnErrorEventArgs e) => {
|
||||
Console.WriteLine("Log: " + e.Exception.Message + " (" + e.Exception.GetType().Name + ")");
|
||||
};
|
||||
|
||||
client.OnIncorrectLogin += async Task (object? s, OnIncorrectLoginArgs e) => {
|
||||
Console.WriteLine("Incorrect Login: " + e.Exception.Message + " (" + e.Exception.GetType().Name + ")");
|
||||
};
|
||||
|
||||
client.OnConnectionError += async Task (object? s, OnConnectionErrorArgs e) => {
|
||||
Console.WriteLine("Connection Error: " + e.Error.Message + " (" + e.Error.GetType().Name + ")");
|
||||
};
|
||||
|
||||
client.OnError += async Task (object? s, OnErrorEventArgs e) => {
|
||||
Console.WriteLine("Error: " + e.Exception.Message + " (" + e.Exception.GetType().Name + ")");
|
||||
};
|
||||
}
|
||||
|
||||
public void InitializePublisher(TTSPlayer player, IEnumerable<string> redeems) {
|
||||
publisher.OnPubSubServiceConnected += async (s, e) => {
|
||||
publisher.ListenToChannelPoints(token.broadcaster_id);
|
||||
|
||||
await publisher.SendTopicsAsync(token.access_token);
|
||||
Console.WriteLine("Twitch PubSub has been connected.");
|
||||
};
|
||||
|
||||
publisher.OnFollow += (s, e) => {
|
||||
Console.WriteLine("Follow: " + e.DisplayName);
|
||||
};
|
||||
|
||||
publisher.OnChannelPointsRewardRedeemed += (s, e) => {
|
||||
Console.WriteLine($"Channel Point Reward Redeemed: {e.RewardRedeemed.Redemption.Reward.Title} (id: {e.RewardRedeemed.Redemption.Id})");
|
||||
|
||||
if (!redeems.Any(r => r.ToLower() == e.RewardRedeemed.Redemption.Reward.Title.ToLower()))
|
||||
return;
|
||||
|
||||
player.Add(new TTSMessage() {
|
||||
Voice = "Brian",
|
||||
Message = e.RewardRedeemed.Redemption.Reward.Title,
|
||||
File = $"redeems/{e.RewardRedeemed.Redemption.Reward.Title.ToLower()}.mp3",
|
||||
Priority = -50
|
||||
});
|
||||
};
|
||||
|
||||
/*int psConnectionFailures = 0;
|
||||
publisher.OnPubSubServiceError += async (s, e) => {
|
||||
Console.WriteLine("PubSub ran into a service error. Attempting to connect again.");
|
||||
await Task.Delay(Math.Min(3000 + (1 << psConnectionFailures), 120000));
|
||||
var connect = await WebHelper.Get("https://hermes.goblincaves.com/api/account/reauthorize");
|
||||
if ((int) connect.StatusCode == 200 || (int) connect.StatusCode == 201) {
|
||||
psConnectionFailures = 0;
|
||||
} else {
|
||||
psConnectionFailures++;
|
||||
}
|
||||
|
||||
var twitchBotData2 = await WebHelper.GetJson<TwitchBotToken>("https://hermes.goblincaves.com/api/token/bot");
|
||||
if (twitchBotData2 == null) {
|
||||
Console.WriteLine("The API is down. Contact the owner.");
|
||||
return;
|
||||
}
|
||||
twitchBotData.access_token = twitchBotData2.access_token;
|
||||
await pubsub.ConnectAsync();
|
||||
};*/
|
||||
}
|
||||
|
||||
public void AddOnNewMessageReceived(AsyncEventHandler<OnMessageReceivedArgs> handler) {
|
||||
client.OnMessageReceived += handler;
|
||||
}
|
||||
}
|
30
TwitchChatTTS/TwitchChatTTS.csproj
Normal file
30
TwitchChatTTS/TwitchChatTTS.csproj
Normal file
@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||
<PackageReference Include="NAudio" Version="2.2.1" />
|
||||
<PackageReference Include="NAudio.Extras" Version="2.2.1" />
|
||||
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
|
||||
<PackageReference Include="TwitchLib.Api.Core" Version="3.10.0-preview-e47ba7f" />
|
||||
<PackageReference Include="TwitchLib.Api.Core.Enums" Version="3.10.0-preview-e47ba7f" />
|
||||
<PackageReference Include="TwitchLib.Api.Core.Interfaces" Version="3.10.0-preview-e47ba7f" />
|
||||
<PackageReference Include="TwitchLib.Api.Helix" Version="3.10.0-preview-e47ba7f" />
|
||||
<PackageReference Include="TwitchLib.Api.Helix.Models" Version="3.10.0-preview-e47ba7f" />
|
||||
<PackageReference Include="TwitchLib.Client" Version="4.0.0-preview-fd131763416cb9f1a31705ca609566d7e7e7fac8" />
|
||||
<PackageReference Include="TwitchLib.Client.Enums" Version="4.0.0-preview-fd131763416cb9f1a31705ca609566d7e7e7fac8" />
|
||||
<PackageReference Include="TwitchLib.Client.Models" Version="4.0.0-preview-fd131763416cb9f1a31705ca609566d7e7e7fac8" />
|
||||
<PackageReference Include="TwitchLib.Communication" Version="2.0.0" />
|
||||
<PackageReference Include="TwitchLib.EventSub.Core" Version="2.5.1" />
|
||||
<PackageReference Include="TwitchLib.PubSub" Version="4.0.0-preview-f833b1ab1ebef37618dba3fbb1e0a661ff183af5" />
|
||||
<PackageReference Include="NAudio.Core" Version="2.2.1" />
|
||||
<PackageReference Include="TwitchLib.Api" Version="3.10.0-preview-e47ba7f" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
29
TwitchChatTTS/Web.cs
Normal file
29
TwitchChatTTS/Web.cs
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
public static class WebHelper {
|
||||
private static HttpClient _client = new HttpClient();
|
||||
|
||||
public static void AddHeader(string key, string? value) {
|
||||
_client.DefaultRequestHeaders.Add(key, value);
|
||||
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13;
|
||||
}
|
||||
|
||||
public static async Task<T?> GetJson<T>(string uri) {
|
||||
return (T) await _client.GetFromJsonAsync(uri, typeof(T));
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseMessage> Get(string uri) {
|
||||
return await _client.GetAsync(uri);
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseMessage> Post<T>(string uri, T data) {
|
||||
return await _client.PostAsJsonAsync(uri, data);
|
||||
}
|
||||
|
||||
public static async Task<HttpResponseMessage> Post(string uri) {
|
||||
return await _client.PostAsJsonAsync(uri, new object());
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/Microsoft.Extensions.Logging.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/Microsoft.Extensions.Logging.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/Microsoft.Extensions.Options.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/Microsoft.Extensions.Options.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.Asio.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.Asio.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.Core.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.Core.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.Midi.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.Midi.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.Wasapi.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.Wasapi.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.WinMM.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.WinMM.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/NAudio.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/Newtonsoft.Json.Bson.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/Newtonsoft.Json.Bson.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/Newtonsoft.Json.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/Newtonsoft.Json.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/System.Net.Http.Formatting.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/System.Net.Http.Formatting.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/System.Reactive.Core.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/System.Reactive.Core.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/System.Reactive.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/System.Reactive.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/System.Text.Encodings.Web.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/System.Text.Encodings.Web.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/System.Text.Json.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/System.Text.Json.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchChatTTS
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchChatTTS
Normal file
Binary file not shown.
1897
TwitchChatTTS/bin/Debug/net6.0/TwitchChatTTS.deps.json
Normal file
1897
TwitchChatTTS/bin/Debug/net6.0/TwitchChatTTS.deps.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchChatTTS.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchChatTTS.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchChatTTS.pdb
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchChatTTS.pdb
Normal file
Binary file not shown.
@ -0,0 +1,9 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net6.0",
|
||||
"framework": {
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "6.0.0"
|
||||
}
|
||||
}
|
||||
}
|
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Core.Enums.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Core.Enums.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Core.Interfaces.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Core.Interfaces.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Core.Models.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Core.Models.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Core.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Core.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Helix.Models.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Helix.Models.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Helix.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.Helix.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Api.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Client.Enums.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Client.Enums.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Client.Models.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Client.Models.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Client.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Client.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Communication.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.Communication.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.EventSub.Core.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.EventSub.Core.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.EventSub.Websockets.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.EventSub.Websockets.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.PubSub.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/TwitchLib.PubSub.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/Microsoft.CSharp.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/Microsoft.CSharp.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.Asio.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.Asio.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.Core.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.Core.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.Extras.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.Extras.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.Midi.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.Midi.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.Wasapi.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.Wasapi.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.WinMM.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.WinMM.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/NAudio.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/Newtonsoft.Json.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/Newtonsoft.Json.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.AppContext.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.AppContext.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Buffers.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Buffers.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Collections.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Collections.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Console.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Console.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Core.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Core.dll
Normal file
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Data.Common.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Data.Common.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Data.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Data.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Drawing.dll
Normal file
BIN
TwitchChatTTS/bin/Debug/net6.0/linux-x64/System.Drawing.dll
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user