This commit is contained in:
sim1222 2023-11-19 01:04:54 +09:00
parent 7d60313cf7
commit 1b0a5d5cb6
16 changed files with 250 additions and 157 deletions

View File

@ -6,7 +6,7 @@ using NAudio.Wave;
namespace Elementary.Audio; namespace Elementary.Audio;
public class AudioConverter public class AudioConverter : IAudioConverter
{ {
public readonly WaveFormat WaveFormat = new(48000, 16, 2); public readonly WaveFormat WaveFormat = new(48000, 16, 2);

View File

@ -63,6 +63,12 @@ public class AudioManager
await JoinChannel(channel); await JoinChannel(channel);
}; };
_audioClient.StreamCreated += async (id, stream) =>
{
_logger.Log(LogLevel.Info, $"Stream created {id}");
await Task.Delay(-1);
};
_audioStream = _audioClient.CreatePCMStream(AudioApplication.Music, 128 * 1024); _audioStream = _audioClient.CreatePCMStream(AudioApplication.Music, 128 * 1024);
_playbackQueue = _services.GetRequiredService<PlaybackQueue>(); _playbackQueue = _services.GetRequiredService<PlaybackQueue>();
@ -126,7 +132,7 @@ public class AudioManager
text = text.Replace("", "ー"); text = text.Replace("", "ー");
float volume = 0.12f; float volume = 0.1f;
Stream? stream = await _sozaiAPI.GetAudioStream(text); Stream? stream = await _sozaiAPI.GetAudioStream(text);
if (stream == null) if (stream == null)

View File

@ -1,80 +1,72 @@
// using ManagedBass; using CSCore;
// using ManagedBass.Mix; using CSCore.Codecs.WAV;
// using System.Collections.Generic;
// namespace Elementary.Audio; using System.IO;
// using WaveFormat = CSCore.WaveFormat;
// public class AudioMixer
// { public class StreamMixer
// private readonly int _mixerStream; {
// private MemoryStream?[] _inputStreams; private List<IWaveSource> sources = new List<IWaveSource>();
// private Stream _outStream; private WaveWriter writer;
// private object _lock = new();
// public void AddInputStream(Stream inputStream)
// public AudioMixer(Stream outStream) {
// { var waveSource = new WaveStream(inputStream, new WaveFormat(48000, 16, 2));
// _outStream = outStream; lock (sources)
// Bass.Init(-1, 44100, DeviceInitFlags.NoSpeakerAssignment, IntPtr.Zero); {
// _mixerStream = BassMix.CreateMixerStream(44100, 2, BassFlags.Float | BassFlags.MixerNonStop); sources.Add(waveSource);
// _inputStreams = new MemoryStream[10]; }
// }
// Bass.ChannelPlay(_mixerStream);
// } public void WriteMixedToOutputStream(Stream outputStream)
// {
// /// <summary> WaveFormat format = new WaveFormat(48000, 16, 2);
// /// Add a stream to the mixer. writer = new WaveWriter(outputStream, format);
// /// wait for the stream to finish playing
// /// </summary> byte[] buffer = new byte[format.BytesPerSecond / 2];
// /// <param name="stream"></param>
// /// <param name="volume"></param> while (sources.Count > 0)
// public void AddStream(Stream stream, float volume = 1.0f) {
// { int read = 0;
// Console.WriteLine("Adding stream to mixer"); lock (sources)
// byte[] pcmBytes; {
// using (MemoryStream ms = new()) foreach (var source in sources)
// { {
// stream.CopyTo(ms); int bytes = source.Read(buffer, 0, buffer.Length);
// pcmBytes = ms.ToArray(); if (bytes == 0)
// } {
// source.Dispose();
// lock (_lock) sources.Remove(source);
// { }
// for (var i = 0; i < _inputStreams.Length; i++) else
// { {
// if (_inputStreams[i] == null) read = bytes;
// { }
// _inputStreams[i] = new MemoryStream(pcmBytes); }
// break; }
// }
// } if (read > 0)
// } {
// writer.Write(buffer, 0, read);
// var channel = Bass.CreateStream(pcmBytes, 0, pcmBytes.Length, BassFlags.Float); }
// Bass.ChannelSetAttribute(channel, ChannelAttribute.Volume, volume); }
// BassMix.MixerAddChannel(_mixerStream, channel, BassFlags.Default);
// writer.Dispose();
// int length = Bass.ChannelGetData(_mixerStream, new byte[4096], 4096); }
// byte[] buffer = new byte[length];
// length = Bass.ChannelGetData(_mixerStream, buffer, length); public void Stop()
// _outStream.Write(buffer, 0, length); {
// } writer?.Dispose();
// writer = null;
// public void Stop()
// { lock (sources)
// Bass.ChannelStop(_mixerStream); {
// } foreach (var source in sources)
// {
// public void Dispose() source.Dispose();
// { }
// lock (_lock)
// { sources.Clear();
// foreach (var stream in _inputStreams) }
// { }
// stream?.Dispose(); }
// }
// }
//
// _outStream.Flush();
// Bass.StreamFree(_mixerStream);
// Bass.Free();
// }
// }

View File

@ -1,10 +1,13 @@
using System.Diagnostics; using System.Diagnostics;
using NAudio.Wave;
namespace Elementary.Audio; namespace Elementary.Audio;
public interface IAudioConverter public interface IAudioConverter
{ {
public Process CreateStreamFromFilePath(string path); WaveFormatConversionStream CreateStreamFromFilePath(string path, float volume = 1.0f);
public Process CreateStreamFromStream(Stream stream); WaveFormatConversionStream CreateStreamFromStream(Stream stream);
WaveFormatConversionStream CreateStreamFromStream(Stream stream, float volume = 1.0f);
} }

View File

@ -0,0 +1,6 @@
namespace Elementary.Audio;
public interface IAudioManager
{
}

View File

@ -0,0 +1,8 @@
namespace Elementary.Audio;
public interface IPlaybackQueue
{
Task Enqueue(PlaybackJob job);
void Flush();
}

View File

@ -0,0 +1,6 @@
namespace Elementary.Audio;
public interface ITtsAPI
{
Task<Stream?> GetAudioStream(string text);
}

View File

@ -1,30 +0,0 @@
using Discord.Audio;
using Discord.WebSocket;
namespace Elementary.Audio;
public class MessageHandler
{
// private IAudioClient _audioClient;
private PlaybackQueue _playbackQueue;
public MessageHandler(PlaybackQueue playbackQueue)
{
_playbackQueue = playbackQueue;
}
public async Task HandleMessage(SocketMessage message)
{
List<String> lines = message.Content.Split("\n").ToList();
foreach (var line in lines)
{
await _playbackQueue.Enqueue(new PlaybackJob()
{
Type = JobType.Text,
Text = line
});
}
}
}

View File

@ -2,6 +2,7 @@
using Discord.Audio; using Discord.Audio;
using NAudio.Wave; using NAudio.Wave;
using NLog; using NLog;
using Exception = System.Exception;
namespace Elementary.Audio; namespace Elementary.Audio;
@ -19,7 +20,7 @@ public class PlaybackJob
public float Volume = 1.0f; public float Volume = 1.0f;
} }
public class PlaybackQueue public class PlaybackQueue : IPlaybackQueue
{ {
// any type of job ConcurrentQueue // any type of job ConcurrentQueue
@ -72,6 +73,8 @@ public class PlaybackQueue
} }
private async void PlayNext() private async void PlayNext()
{
try
{ {
if (_queue.TryDequeue(out var currentStream)) if (_queue.TryDequeue(out var currentStream))
{ {
@ -95,4 +98,11 @@ public class PlaybackQueue
_isPlaying = false; _isPlaying = false;
} }
} }
catch (Exception e)
{
_logger.Error(e);
_isPlaying = false;
PlayNext();
}
}
} }

View File

@ -4,7 +4,7 @@ using NLog;
namespace Elementary.Audio; namespace Elementary.Audio;
public class SozaiAPI public class SozaiAPI : ITtsAPI
{ {
private ILogger _logger; private ILogger _logger;
@ -30,13 +30,13 @@ public class SozaiAPI
public SozaiAPI() public SozaiAPI()
{ {
_client = new HttpClient();
_logger = LogManager.GetCurrentClassLogger(); _logger = LogManager.GetCurrentClassLogger();
_cache = new MemoryCache(new MemoryCacheOptions()); _cache = new MemoryCache(new MemoryCacheOptions());
} }
public async Task Setup(string url) public async Task Setup(string url)
{ {
_client = new HttpClient();
var response = await _client.GetAsync(url); var response = await _client.GetAsync(url);
var stream = await response.Content.ReadAsStreamAsync(); var stream = await response.Content.ReadAsStreamAsync();
Assets = await JsonSerializer.DeserializeAsync<Asset[]>(stream); Assets = await JsonSerializer.DeserializeAsync<Asset[]>(stream);

View File

@ -12,14 +12,18 @@ public class VoicevoxAPI
private ILogger _logger; private ILogger _logger;
private MemoryCache _cache; private MemoryCache _cache;
public VoicevoxAPI()
{
_logger = LogManager.GetCurrentClassLogger();
_cache = new MemoryCache(new MemoryCacheOptions());
}
public async Task Setup(string url) public async Task Setup(string url)
{ {
_client = new HttpClient();
Uri _url = new(url); Uri _url = new(url);
_APIRootUrl = new UriBuilder($"{_url.Scheme}://{_url.Host}:{_url.Port}"); _APIRootUrl = new UriBuilder($"{_url.Scheme}://{_url.Host}:{_url.Port}");
_client = new HttpClient();
_logger = LogManager.GetCurrentClassLogger();
_cache = new MemoryCache(new MemoryCacheOptions());
} }
/// <summary> /// <summary>

View File

@ -1,4 +1,6 @@
using Discord; using System.Diagnostics;
using System.Reflection;
using Discord;
using Discord.Commands; using Discord.Commands;
using Elementary.Audio; using Elementary.Audio;
using Elementary.Dictionary; using Elementary.Dictionary;
@ -119,4 +121,28 @@ public class MessageCommands : ModuleBase<SocketCommandContext>
{ {
await _audioManager.PlayYoutube(url); await _audioManager.PlayYoutube(url);
} }
[Command("memory", RunMode = RunMode.Async)]
[Summary("Show memory usage.")]
public async Task MemoryAsync()
{
var memory = Environment.WorkingSet;
await ReplyAsync($"Memory: {memory / 1024 / 1024} MB");
}
[Command("restart", RunMode = RunMode.Async)]
[Summary("Restart the bot.")]
public async Task RestartAsync()
{
await ReplyAsync("Restarting...");
await LeaveAsync();
var assembly = Assembly.GetEntryAssembly();
var path = assembly.Location;
path = path.Replace(".dll", ".exe");
Console.WriteLine(path);
Process.Start(path);
Environment.Exit(0);
}
} }

View File

@ -14,9 +14,13 @@
<Content Include="..\.dockerignore"> <Content Include="..\.dockerignore">
<Link>.dockerignore</Link> <Link>.dockerignore</Link>
</Content> </Content>
<Content Include="emoji-ja\data\emoji_ja.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CSCore" Version="1.2.1.2" />
<PackageReference Include="Discord.Net" Version="3.11.0"/> <PackageReference Include="Discord.Net" Version="3.11.0"/>
<PackageReference Include="Discord.Net.Core" Version="3.11.0"/> <PackageReference Include="Discord.Net.Core" Version="3.11.0"/>
<PackageReference Include="LiteDB" Version="5.0.17" /> <PackageReference Include="LiteDB" Version="5.0.17" />
@ -57,4 +61,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Remove="Audio\AudioMixer.cs" />
</ItemGroup>
</Project> </Project>

View File

@ -1,11 +1,10 @@
using System.Reflection; using System.Reflection;
using Discord.Audio;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket; using Discord.WebSocket;
using Elementary.Audio; using Elementary.Audio;
using NLog; using NLog;
namespace Elementary.Commands; namespace Elementary.Message;
public class Handler public class Handler
{ {

View File

@ -0,0 +1,52 @@
using Discord.WebSocket;
using Elementary.Audio;
namespace Elementary.Message;
public class MessageHandler
{
// private IAudioClient _audioClient;
private PlaybackQueue _playbackQueue;
public MessageHandler(PlaybackQueue playbackQueue)
{
_playbackQueue = playbackQueue;
}
public async Task HandleMessage(SocketMessage message)
{
// if contains mention, add mentioned user name to lines and replace <id>
List<String> lines = new();
if (message.MentionedUsers.Count > 0)
{
foreach (var mentionedUser in message.MentionedUsers)
{
message.Content.Replace(mentionedUser.Mention, $"@{mentionedUser.GlobalName}").Split('\n').ToList()
.ForEach(lines.Add);
}
}
else
{
if (message.Content.Contains("```"))
{
lines = new List<string> {"コードブロック"};
}
else
{
message.Content.Split('\n').ToList().ForEach(lines.Add);
}
}
foreach (var line in lines)
{
await _playbackQueue.Enqueue(new PlaybackJob()
{
Type = JobType.Text,
Text = line
});
}
}
}

View File

@ -7,6 +7,7 @@ using Discord.WebSocket;
using Elementary.Audio; using Elementary.Audio;
using Elementary.Commands; using Elementary.Commands;
using Elementary.Dictionary; using Elementary.Dictionary;
using Elementary.Message;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NAudio.Wave; using NAudio.Wave;
@ -32,14 +33,13 @@ public class Program
private Mecab _mecab; private Mecab _mecab;
private Ytdlp _ytdlp; private Ytdlp _ytdlp;
public static async Task Main(string[] args) public static void Main(string[] args)
{ {
while (true) while (true)
{ {
try try
{ {
await new Program().Setup(); new Program().Setup().Wait();
await Task.Delay(-1);
} }
catch (Exception e) catch (Exception e)
{ {
@ -55,7 +55,6 @@ public class Program
{ {
GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent
}); });
_commands = new CommandService();
_services = new ServiceCollection() _services = new ServiceCollection()
.AddLogging(builder => .AddLogging(builder =>
{ {
@ -64,22 +63,24 @@ public class Program
builder.AddNLog(configuration.ConfigurationRoot); builder.AddNLog(configuration.ConfigurationRoot);
}) })
.AddSingleton(_client) .AddSingleton(_client)
.AddSingleton(_commands) .AddScoped<CommandService>()
.AddSingleton<Handler>() .AddTransient<Handler>()
.AddSingleton<AudioManager>() .AddScoped<AudioManager>()
.AddSingleton<VoicevoxAPI>() .AddScoped<PlaybackQueue>()
.AddSingleton<SozaiAPI>() .AddScoped<VoicevoxAPI>()
.AddSingleton<PlaybackQueue>() .AddScoped<SozaiAPI>()
.AddTransient<AudioConverter>() .AddTransient<AudioConverter>()
.AddSingleton<MessageHandler>() .AddScoped<MessageHandler>()
.AddSingleton<EmojiDictionary>() .AddSingleton<EmojiDictionary>()
.AddSingleton<DictionaryDB>() .AddSingleton<DictionaryDB>()
.AddSingleton<Mecab>() .AddScoped<Mecab>()
.AddSingleton<Ytdlp>() .AddScoped<Ytdlp>()
.BuildServiceProvider(); .BuildServiceProvider();
_logger = LogManager.GetCurrentClassLogger(); _logger = LogManager.GetCurrentClassLogger();
_commands = _services.GetRequiredService<CommandService>();
_sozaiAPI = _services.GetRequiredService<SozaiAPI>(); _sozaiAPI = _services.GetRequiredService<SozaiAPI>();
_voicevoxAPI = _services.GetRequiredService<VoicevoxAPI>(); _voicevoxAPI = _services.GetRequiredService<VoicevoxAPI>();
_ytdlp = _services.GetRequiredService<Ytdlp>(); _ytdlp = _services.GetRequiredService<Ytdlp>();