2024-03-12 14:05:27 -04:00
using System.Runtime.InteropServices ;
using System.Web ;
using Microsoft.Extensions.DependencyInjection ;
using Microsoft.Extensions.Hosting ;
2024-06-16 20:19:31 -04:00
using Serilog ;
2024-03-12 14:05:27 -04:00
using NAudio.Wave.SampleProviders ;
2024-06-24 18:11:36 -04:00
using org.mariuszgromada.math.mxparser ;
2024-07-12 13:36:09 -04:00
using TwitchChatTTS.Hermes.Socket ;
2024-07-16 00:48:55 -04:00
using TwitchChatTTS.Seven.Socket ;
using TwitchChatTTS.Chat.Emotes ;
using CommonSocketLibrary.Abstract ;
using CommonSocketLibrary.Common ;
2024-07-19 12:56:41 -04:00
using TwitchChatTTS.OBS.Socket ;
2024-08-04 19:46:10 -04:00
using TwitchChatTTS.Twitch.Socket.Messages ;
using TwitchChatTTS.Twitch.Socket ;
2024-08-06 15:29:29 -04:00
using TwitchChatTTS.Chat.Commands ;
2024-08-07 15:33:10 -04:00
using System.Text ;
2024-03-12 14:05:27 -04:00
namespace TwitchChatTTS
{
public class TTS : IHostedService
{
2024-08-04 19:46:10 -04:00
public const int MAJOR_VERSION = 4 ;
2024-08-07 19:26:14 -04:00
public const int MINOR_VERSION = 1 ;
2024-06-16 20:19:31 -04:00
2024-07-06 23:42:33 -04:00
private readonly User _user ;
private readonly HermesApiClient _hermesApiClient ;
private readonly SevenApiClient _sevenApiClient ;
2024-08-06 15:29:29 -04:00
private readonly TwitchApiClient _twitchApiClient ;
2024-08-04 19:46:10 -04:00
private readonly HermesSocketClient _hermes ;
2024-07-19 12:56:41 -04:00
private readonly OBSSocketClient _obs ;
private readonly SevenSocketClient _seven ;
2024-08-04 19:46:10 -04:00
private readonly TwitchWebsocketClient _twitch ;
2024-08-06 15:29:29 -04:00
private readonly ICommandFactory _commandFactory ;
private readonly ICommandManager _commandManager ;
2024-07-19 12:56:41 -04:00
private readonly IEmoteDatabase _emotes ;
2024-03-15 08:27:35 -04:00
private readonly TTSPlayer _player ;
2024-08-04 19:46:10 -04:00
private readonly AudioPlaybackEngine _playback ;
2024-08-06 15:29:29 -04:00
private readonly Configuration _configuration ;
2024-06-24 18:11:36 -04:00
private readonly ILogger _logger ;
2024-03-12 14:05:27 -04:00
2024-06-24 18:11:36 -04:00
public TTS (
User user ,
HermesApiClient hermesApiClient ,
SevenApiClient sevenApiClient ,
2024-07-16 00:48:55 -04:00
[FromKeyedServices("hermes")] SocketClient < WebSocketMessage > hermes ,
2024-07-19 12:56:41 -04:00
[FromKeyedServices("obs")] SocketClient < WebSocketMessage > obs ,
[FromKeyedServices("7tv")] SocketClient < WebSocketMessage > seven ,
2024-08-04 19:46:10 -04:00
[FromKeyedServices("twitch")] SocketClient < TwitchWebsocketMessage > twitch ,
2024-08-06 15:29:29 -04:00
ICommandFactory commandFactory ,
ICommandManager commandManager ,
2024-07-19 12:56:41 -04:00
IEmoteDatabase emotes ,
2024-06-24 18:11:36 -04:00
TTSPlayer player ,
2024-08-04 19:46:10 -04:00
AudioPlaybackEngine playback ,
2024-08-06 15:29:29 -04:00
Configuration configuration ,
2024-06-24 18:11:36 -04:00
ILogger logger
)
2024-06-16 20:19:31 -04:00
{
2024-07-06 23:42:33 -04:00
_user = user ;
_hermesApiClient = hermesApiClient ;
_sevenApiClient = sevenApiClient ;
2024-07-16 00:48:55 -04:00
_hermes = ( hermes as HermesSocketClient ) ! ;
2024-07-19 12:56:41 -04:00
_obs = ( obs as OBSSocketClient ) ! ;
_seven = ( seven as SevenSocketClient ) ! ;
2024-08-04 19:46:10 -04:00
_twitch = ( twitch as TwitchWebsocketClient ) ! ;
2024-08-06 15:29:29 -04:00
_commandFactory = commandFactory ;
_commandManager = commandManager ;
2024-07-19 12:56:41 -04:00
_emotes = emotes ;
2024-03-15 08:27:35 -04:00
_configuration = configuration ;
_player = player ;
2024-08-04 19:46:10 -04:00
_playback = playback ;
2024-06-24 18:11:36 -04:00
_logger = logger ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
public async Task StartAsync ( CancellationToken cancellationToken )
{
2024-03-12 14:05:27 -04:00
Console . Title = "TTS - Twitch Chat" ;
2024-08-07 15:33:10 -04:00
Console . OutputEncoding = Encoding . UTF8 ;
2024-07-06 23:42:33 -04:00
License . iConfirmCommercialUse ( "abcdef" ) ;
2024-06-16 20:19:31 -04:00
2024-07-16 00:48:55 -04:00
if ( string . IsNullOrWhiteSpace ( _configuration . Hermes ? . Token ) )
2024-07-12 13:36:09 -04:00
{
2024-07-06 23:42:33 -04:00
_logger . Error ( "Hermes API token not set in the configuration file." ) ;
return ;
}
2024-03-15 08:27:35 -04:00
2024-07-12 13:36:09 -04:00
var hermesVersion = await _hermesApiClient . GetLatestTTSVersion ( ) ;
if ( hermesVersion = = null )
{
_logger . Warning ( "Failed to fetch latest TTS version. Skipping version check." ) ;
}
else if ( hermesVersion . MajorVersion > TTS . MAJOR_VERSION | | hermesVersion . MajorVersion = = TTS . MAJOR_VERSION & & hermesVersion . MinorVersion > TTS . MINOR_VERSION )
2024-06-16 20:19:31 -04:00
{
_logger . Information ( $"A new update for TTS is avaiable! Version {hermesVersion.MajorVersion}.{hermesVersion.MinorVersion} is available at {hermesVersion.Download}" ) ;
var changes = hermesVersion . Changelog . Split ( "\n" ) ;
if ( changes ! = null & & changes . Any ( ) )
_logger . Information ( "Changelogs:\n - " + string . Join ( "\n - " , changes ) + "\n\n" ) ;
await Task . Delay ( 15 * 1000 ) ;
}
2024-03-15 08:27:35 -04:00
2024-07-16 00:48:55 -04:00
await InitializeHermesWebsocket ( ) ;
2024-06-16 20:19:31 -04:00
try
{
2024-08-04 19:46:10 -04:00
var hermesAccount = await _hermesApiClient . FetchHermesAccountDetails ( ) ;
_user . HermesUserId = hermesAccount . Id ;
_user . HermesUsername = hermesAccount . Username ;
_user . TwitchUsername = hermesAccount . Username ;
2024-08-06 15:29:29 -04:00
_user . TwitchUserId = long . Parse ( hermesAccount . BroadcasterId ) ;
}
catch ( ArgumentNullException )
{
_logger . Error ( "Ensure you have your Twitch account linked to TTS." ) ;
await Task . Delay ( TimeSpan . FromSeconds ( 30 ) ) ;
return ;
}
catch ( FormatException )
{
_logger . Error ( "Ensure you have your Twitch account linked to TTS." ) ;
await Task . Delay ( TimeSpan . FromSeconds ( 30 ) ) ;
return ;
2024-06-16 20:19:31 -04:00
}
catch ( Exception ex )
{
2024-07-16 00:48:55 -04:00
_logger . Error ( ex , "Failed to initialize properly. Restart app please." ) ;
2024-08-06 15:29:29 -04:00
await Task . Delay ( TimeSpan . FromSeconds ( 30 ) ) ;
2024-06-16 20:19:31 -04:00
return ;
}
2024-08-07 18:01:04 -04:00
try
{
await _twitch . Connect ( ) ;
}
catch ( Exception e )
{
_logger . Error ( e , "Failed to connect to Twitch websocket server." ) ;
await Task . Delay ( TimeSpan . FromSeconds ( 30 ) ) ;
return ;
}
2024-03-15 08:27:35 -04:00
2024-07-06 23:42:33 -04:00
var emoteSet = await _sevenApiClient . FetchChannelEmoteSet ( _user . TwitchUserId . ToString ( ) ) ;
2024-07-16 00:48:55 -04:00
if ( emoteSet ! = null )
_user . SevenEmoteSetId = emoteSet . Id ;
2024-07-12 13:36:09 -04:00
2024-08-06 15:29:29 -04:00
_commandManager . Update ( _commandFactory ) ;
2024-07-06 23:42:33 -04:00
await InitializeEmotes ( _sevenApiClient , emoteSet ) ;
2024-07-12 13:36:09 -04:00
await InitializeSevenTv ( ) ;
2024-03-12 14:05:27 -04:00
await InitializeObs ( ) ;
2024-08-04 19:46:10 -04:00
_playback . AddOnMixerInputEnded ( ( object? s , SampleProviderEventArgs e ) = >
2024-06-16 20:19:31 -04:00
{
2024-08-04 19:46:10 -04:00
if ( e . SampleProvider = = _player . Playing ? . Audio )
2024-06-16 20:19:31 -04:00
{
_player . Playing = null ;
}
} ) ;
Task . Run ( async ( ) = >
{
while ( true )
{
try
{
if ( cancellationToken . IsCancellationRequested )
{
2024-07-12 13:36:09 -04:00
_logger . Warning ( "TTS Buffer - Cancellation requested." ) ;
2024-06-16 20:19:31 -04:00
return ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
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={HttpUtility.UrlEncode(m.Message)}" ;
var sound = new NetworkWavSound ( url ) ;
var provider = new CachedWavProvider ( sound ) ;
2024-08-04 19:46:10 -04:00
var data = _playback . ConvertSound ( provider ) ;
var resampled = new WdlResamplingSampleProvider ( data , _playback . SampleRate ) ;
2024-06-24 18:11:36 -04:00
_logger . Verbose ( "Fetched TTS audio data." ) ;
2024-06-16 20:19:31 -04:00
m . Audio = resampled ;
_player . Ready ( m ) ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
catch ( COMException e )
{
2024-06-24 18:11:36 -04:00
_logger . Error ( e , "Failed to send request for TTS [HResult: " + e . HResult + "]" ) ;
2024-06-16 20:19:31 -04:00
}
catch ( Exception e )
{
_logger . Error ( e , "Failed to send request for TTS." ) ;
}
}
} ) ;
2024-03-12 14:05:27 -04:00
2024-06-16 20:19:31 -04:00
Task . Run ( async ( ) = >
{
while ( true )
{
try
{
if ( cancellationToken . IsCancellationRequested )
{
2024-07-06 23:42:33 -04:00
_logger . Warning ( "TTS Queue - Cancellation requested." ) ;
2024-06-16 20:19:31 -04:00
return ;
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
while ( _player . IsEmpty ( ) | | _player . Playing ! = null )
{
await Task . Delay ( 200 ) ;
continue ;
}
var m = _player . ReceiveReady ( ) ;
if ( m = = null )
{
continue ;
}
if ( ! string . IsNullOrWhiteSpace ( m . File ) & & File . Exists ( m . File ) )
{
2024-07-06 23:42:33 -04:00
_logger . Debug ( "Playing audio file via TTS: " + m . File ) ;
2024-08-04 19:46:10 -04:00
_playback . PlaySound ( m . File ) ;
2024-06-16 20:19:31 -04:00
continue ;
}
2024-07-06 23:42:33 -04:00
_logger . Debug ( "Playing message via TTS: " + m . Message ) ;
2024-07-12 13:36:09 -04:00
2024-06-16 20:19:31 -04:00
if ( m . Audio ! = null )
2024-07-12 13:36:09 -04:00
{
2024-08-04 19:46:10 -04:00
_player . Playing = m ;
_playback . AddMixerInput ( m . Audio ) ;
2024-07-12 13:36:09 -04:00
}
2024-03-12 14:05:27 -04:00
}
2024-06-16 20:19:31 -04:00
catch ( Exception e )
{
_logger . Error ( e , "Failed to play a TTS audio message" ) ;
}
}
} ) ;
2024-03-12 14:05:27 -04:00
}
public async Task StopAsync ( CancellationToken cancellationToken )
{
if ( cancellationToken . IsCancellationRequested )
2024-06-16 20:19:31 -04:00
_logger . Warning ( "Application has stopped due to cancellation token." ) ;
2024-03-12 14:05:27 -04:00
else
2024-06-16 20:19:31 -04:00
_logger . Warning ( "Application has stopped." ) ;
2024-03-15 08:27:35 -04:00
}
2024-06-16 20:19:31 -04:00
private async Task InitializeHermesWebsocket ( )
{
try
{
2024-07-16 00:48:55 -04:00
_hermes . Initialize ( ) ;
await _hermes . Connect ( ) ;
2024-03-15 08:27:35 -04:00
}
2024-07-16 00:48:55 -04:00
catch ( Exception e )
2024-06-16 20:19:31 -04:00
{
2024-07-16 00:48:55 -04:00
_logger . Error ( e , "Connecting to hermes failed. Skipping hermes websockets." ) ;
2024-03-15 08:27:35 -04:00
}
2024-06-16 20:19:31 -04:00
}
2024-03-15 08:27:35 -04:00
2024-07-12 13:36:09 -04:00
private async Task InitializeSevenTv ( )
2024-06-16 20:19:31 -04:00
{
try
{
2024-07-19 12:56:41 -04:00
_seven . Initialize ( ) ;
await _seven . Connect ( ) ;
2024-06-16 20:19:31 -04:00
}
2024-07-16 00:48:55 -04:00
catch ( Exception e )
2024-06-16 20:19:31 -04:00
{
2024-07-16 00:48:55 -04:00
_logger . Error ( e , "Connecting to 7tv failed. Skipping 7tv websockets." ) ;
2024-03-12 14:05:27 -04:00
}
}
2024-06-16 20:19:31 -04:00
private async Task InitializeObs ( )
{
try
{
2024-07-19 12:56:41 -04:00
_obs . Initialize ( ) ;
await _obs . Connect ( ) ;
2024-06-16 20:19:31 -04:00
}
catch ( Exception )
{
_logger . Warning ( "Connecting to obs failed. Skipping obs websockets." ) ;
2024-03-12 14:05:27 -04:00
}
}
2024-07-16 00:48:55 -04:00
private async Task InitializeEmotes ( SevenApiClient sevenapi , EmoteSet ? channelEmotes )
2024-06-16 20:19:31 -04:00
{
var globalEmotes = await sevenapi . FetchGlobalSevenEmotes ( ) ;
if ( channelEmotes ! = null & & channelEmotes . Emotes . Any ( ) )
{
_logger . Information ( $"Loaded {channelEmotes.Emotes.Count()} 7tv channel emotes." ) ;
foreach ( var entry in channelEmotes . Emotes )
2024-07-19 12:56:41 -04:00
_emotes . Add ( entry . Name , entry . Id ) ;
2024-06-16 20:19:31 -04:00
}
if ( globalEmotes ! = null & & globalEmotes . Any ( ) )
{
_logger . Information ( $"Loaded {globalEmotes.Count()} 7tv global emotes." ) ;
foreach ( var entry in globalEmotes )
2024-07-19 12:56:41 -04:00
_emotes . Add ( entry . Name , entry . Id ) ;
2024-06-16 20:19:31 -04:00
}
2024-03-12 14:05:27 -04:00
}
}
}