102 lines
4.7 KiB
Python
102 lines
4.7 KiB
Python
import uuid
|
|
import discord
|
|
import os
|
|
import re
|
|
import asyncio
|
|
import logging
|
|
|
|
class MessageReader:
|
|
# 半角カタカナと数字と全角へのマッピング
|
|
half_width = "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンァィゥェォッャュョー"
|
|
full_width = "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンアイウエオッヤユヨー"
|
|
translation_table = str.maketrans(half_width, full_width)
|
|
|
|
# 濁点・半濁点を含む半角カタカナの変換マッピング
|
|
dakuten_map = {
|
|
'ガ': 'ガ', 'ギ': 'ギ', 'グ': 'グ', 'ゲ': 'ゲ', 'ゴ': 'ゴ',
|
|
'ザ': 'ザ', 'ジ': 'ジ', 'ズ': 'ズ', 'ゼ': 'ゼ', 'ゾ': 'ゾ',
|
|
'ダ': 'ダ', 'ヂ': 'ヂ', 'ヅ': 'ヅ', 'デ': 'デ', 'ド': 'ド',
|
|
'バ': 'バ', 'ビ': 'ビ', 'ブ': 'ブ', 'ベ': 'ベ', 'ボ': 'ボ',
|
|
'パ': 'パ', 'ピ': 'ピ', 'プ': 'プ', 'ペ': 'ペ', 'ポ': 'ポ'
|
|
}
|
|
|
|
def __init__(self, voice_synthesizer, bot):
|
|
self.voice_synthesizer = voice_synthesizer
|
|
self.queue = asyncio.Queue()
|
|
self.is_reading = False
|
|
self.bot = bot
|
|
|
|
async def read_message(self, message, word_dictionary, ffmpeg_executable_path, volume=1.0):
|
|
"""メッセージを読み上げるための処理を開始する"""
|
|
await self.queue.put((message, word_dictionary, ffmpeg_executable_path, volume))
|
|
if not self.is_reading:
|
|
await self._process_queue()
|
|
|
|
async def _process_queue(self):
|
|
"""メッセージキューを処理する"""
|
|
self.is_reading = True
|
|
while not self.queue.empty():
|
|
message, word_dictionary, ffmpeg_executable_path, volume = await self.queue.get()
|
|
try:
|
|
await self._synthesize_and_play(message, word_dictionary, ffmpeg_executable_path, volume)
|
|
except Exception as e:
|
|
logging.error(f"Failed to read message: {e}")
|
|
self.is_reading = False
|
|
|
|
def _convert_to_full_width(self, text):
|
|
"""テキスト内の半角カタカナを全角カタカナに変換する"""
|
|
for half, full in self.dakuten_map.items():
|
|
text = text.replace(half, full)
|
|
text = text.translate(self.translation_table)
|
|
return text
|
|
|
|
async def _synthesize_and_play(self, message, word_dictionary, ffmpeg_executable_path, volume):
|
|
"""メッセージの音声合成を行い、再生する"""
|
|
if not isinstance(message, str):
|
|
if self.bot.is_name_reading:
|
|
message.content = f'{message.author.display_name}さん、{message.content}'
|
|
|
|
for user in message.mentions:
|
|
message.content = message.content.replace(f'<@{user.id}>', user.display_name)
|
|
message.content = message.content.replace(f'<@!{user.id}>', user.display_name)
|
|
|
|
for role in message.role_mentions:
|
|
message.content = message.content.replace(f'<@&{role.id}>', role.name)
|
|
|
|
for channel in message.channel_mentions:
|
|
message.content = message.content.replace(f'<#{channel.id}>', f'#{channel.name}')
|
|
|
|
message.content = re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\\-]+', 'URL省略', message.content)
|
|
|
|
message.content = re.sub(r'<:[a-zA-Z0-9_]+:[0-9]+>', '', message.content)
|
|
|
|
message.content = self._convert_to_full_width(message.content)
|
|
|
|
message.content = message.content.replace('\n', ' ')
|
|
|
|
voice_client = message.guild.voice_client
|
|
play_message = message.content
|
|
id = message.id
|
|
else:
|
|
voice_client = self.bot.independence_chat.voice_client
|
|
play_message = message
|
|
id = str(uuid.uuid4())
|
|
|
|
if voice_client and voice_client.is_connected():
|
|
script = word_dictionary.convert_text(play_message)
|
|
if len(script) > 140:
|
|
script = script[:140]
|
|
|
|
output_path = f"{id}.wav"
|
|
output_path = await self.voice_synthesizer.synthesize(script, output_path=output_path)
|
|
|
|
if not os.path.exists(output_path):
|
|
logging.error(f"Expected audio file not found: {output_path}")
|
|
return
|
|
|
|
audio_source = discord.FFmpegPCMAudio(executable=ffmpeg_executable_path, source=output_path)
|
|
volume_adjusted_source = discord.PCMVolumeTransformer(audio_source, volume=volume)
|
|
voice_client.play(volume_adjusted_source, after=lambda e: os.remove(output_path))
|
|
while voice_client.is_playing():
|
|
await asyncio.sleep(1)
|