95 lines
3.1 KiB
TypeScript
95 lines
3.1 KiB
TypeScript
import {
|
|
AttachmentBuilder,
|
|
ChatInputCommandInteraction,
|
|
EmbedBuilder,
|
|
SlashCommandBuilder,
|
|
} from 'discord.js';
|
|
import 'dotenv/config';
|
|
import { logError } from '../../../logging';
|
|
import { requestTTSResponse } from '../../util';
|
|
import {
|
|
createStatusEmbed,
|
|
getRandomLoadingEmoji,
|
|
getRandomKawaiiPhrase,
|
|
MIKU_COLOR,
|
|
} from '../helpers';
|
|
|
|
const config = {
|
|
ttsSettings: {
|
|
speaker: process.env.TTS_SPEAKER || 'Vivian',
|
|
pitch_change_sem: parseInt(process.env.TTS_PITCH || '0', 10),
|
|
},
|
|
};
|
|
|
|
async function ttsCommand(interaction: ChatInputCommandInteraction) {
|
|
const text = interaction.options.getString('text');
|
|
const speaker = interaction.options.getString('speaker') || config.ttsSettings.speaker;
|
|
const pitch = interaction.options.getInteger('pitch') ?? config.ttsSettings.pitch_change_sem;
|
|
const instruct = interaction.options.getString('instruct');
|
|
|
|
// Pick a random loading emoji and phrase for this generation
|
|
const loadingEmoji = getRandomLoadingEmoji();
|
|
const loadingPhrase = getRandomKawaiiPhrase();
|
|
|
|
// Initial loading embed
|
|
const loadingEmbed = createStatusEmbed(
|
|
loadingEmoji,
|
|
loadingPhrase,
|
|
`Generating audio for: "${text}"`
|
|
);
|
|
await interaction.reply({ embeds: [loadingEmbed] });
|
|
|
|
try {
|
|
const audio = await requestTTSResponse(text, speaker, pitch, instruct);
|
|
const audioBuf = await audio.arrayBuffer();
|
|
const audioFile = new AttachmentBuilder(Buffer.from(audioBuf)).setName('mikuified.wav');
|
|
|
|
// Final embed with the TTS result
|
|
const finalEmbed = new EmbedBuilder()
|
|
.setColor(MIKU_COLOR)
|
|
.setAuthor({ name: 'Miku speaks:' })
|
|
.setDescription(text)
|
|
.setFooter({
|
|
text: `Voice: ${speaker} | Pitch: ${pitch} semitones${instruct ? ` | ${instruct}` : ''}`,
|
|
})
|
|
.setTimestamp();
|
|
|
|
await interaction.editReply({
|
|
embeds: [finalEmbed],
|
|
files: [audioFile],
|
|
});
|
|
} catch (err) {
|
|
const errorEmbed = createStatusEmbed(
|
|
loadingEmoji,
|
|
loadingPhrase,
|
|
`Oops! Something went wrong... 😭\n\`${err}\``
|
|
);
|
|
await interaction.editReply({ embeds: [errorEmbed] });
|
|
logError(`Error while generating TTS: ${err}`);
|
|
}
|
|
}
|
|
|
|
export = {
|
|
data: new SlashCommandBuilder()
|
|
.setName('tts')
|
|
.setDescription("Read text in Miku's voice")
|
|
.addStringOption((opt) => opt.setName('text').setDescription('Text').setRequired(true))
|
|
.addStringOption((opt) =>
|
|
opt.setName('speaker').setDescription('Speaker voice to use').setRequired(false)
|
|
)
|
|
.addIntegerOption((opt) =>
|
|
opt
|
|
.setName('pitch')
|
|
.setDescription('Pitch shift in semitones (default: 0)')
|
|
.setRequired(false)
|
|
)
|
|
.addStringOption((opt) =>
|
|
opt
|
|
.setName('instruct')
|
|
.setDescription('Instruction for how to speak the text')
|
|
.setRequired(false)
|
|
),
|
|
execute: ttsCommand,
|
|
config: config,
|
|
};
|