diff --git a/.gitignore b/.gitignore index d9c8e26..c0033c5 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,4 @@ cython_debug/ #.idea/ /phonemize_words.py +/test_eleven_labs.py diff --git a/bot.py b/bot.py index 5d7bbbd..bfae4c1 100644 --- a/bot.py +++ b/bot.py @@ -5,6 +5,7 @@ from typing import Any, Optional import logging from pathlib import Path +from eleven_labs_api import ElevenLabsAPI DEBUG = os.getenv("DEBUG") AUDIO_DIR = os.getenv("AUDIO_DIR", "/audio") @@ -14,6 +15,7 @@ RAW_PREFIX_SHORT = "!b" SPACE_PREFIX = RAW_PREFIX + " " BOT_TOKEN = os.getenv("BOT_TOKEN") +ELEVEN_LABS_TOKEN = os.getenv("ELEVEN_LABS_TOKEN") BOT_DESCRIPTION = ("A bot to generate alternate names for Benedict Cumberbatch.\n" "\n" f"Usage: {RAW_PREFIX_SHORT} [command] [arguments]\n" @@ -34,18 +36,22 @@ def add_indented_commands(self, _commands, *, heading, max_size=None): max_size = super().get_max_size(_commands) + self.indent return super().add_indented_commands(_commands, heading=heading, max_size=max_size) -defaultHelpCommand = CustomHelp( - show_parameter_descriptions=False, - no_category="Commands", - command_attrs={ - "aliases": ["?"], - "help": "Get general help information or help about a specific command" - } +bot = commands.Bot( + command_prefix=commands.when_mentioned_or(SPACE_PREFIX, "!b "), + description=BOT_DESCRIPTION, + intents=intents, + help_command=CustomHelp( + show_parameter_descriptions=False, + no_category="Commands", + command_attrs={ + "aliases": ["?"], + "help": "Get general help information or help about a specific command" + } + ) ) -bot = commands.Bot(command_prefix=commands.when_mentioned_or(SPACE_PREFIX, "!b "), description=BOT_DESCRIPTION, - intents=intents, - help_command=defaultHelpCommand) + name_api = Generator() +eleven_labs_api = ElevenLabsAPI(ELEVEN_LABS_TOKEN) g_last_name = "Benedict Cumberbatch" g_last_phone = "benedict cumberbatch" @@ -62,8 +68,12 @@ async def _speak(ctx: commands.Context) -> Optional[Any]: if not vc or not vc.is_connected(): return await ctx.reply("I am not connected to a voice channel.", mention_author=False) if not vc.is_playing(): - name_api.vocalize(g_last_phone) - audio_source = Path(AUDIO_DIR) / "output.wav" + _count = eleven_labs_api.get_remaining_character_count() + if _count < 20: + name_api.vocalize(g_last_phone) + audio_source = Path(AUDIO_DIR) / "output.wav" + else: + audio_source = eleven_labs_api.get_spoken_name(g_last_name, AUDIO_DIR) return vc.play(discord.FFmpegPCMAudio(executable=FFMPEG_EXEC, source=str(audio_source))) else: return await ctx.reply("Audio is already playing.", mention_author=False) @@ -78,7 +88,6 @@ async def _gen(ctx: commands.Context): await _speak(ctx) - @bot.event async def on_ready(): print(f'Logged in as {bot.user.name} - {bot.user.id}') @@ -176,6 +185,14 @@ async def autospeak(ctx: commands.Context, subcmd: str = "on"): return await ctx.reply(f"Autospeak off") +# Hidden Commands +@bot.command(name="count", + hidden=True) +async def count(ctx: commands.Context): + cnt = eleven_labs_api.get_remaining_character_count() + return await ctx.reply(f"{cnt} characters") + + def run(token=BOT_TOKEN): if DEBUG: print(f"Using token: {token}") diff --git a/eleven_labs_api.py b/eleven_labs_api.py new file mode 100644 index 0000000..aab038b --- /dev/null +++ b/eleven_labs_api.py @@ -0,0 +1,51 @@ +from elevenlabs.client import ElevenLabs +from elevenlabs.types import VoiceSettings +from dataclasses import dataclass +from pathlib import Path +from typing import Union + +MODEL_ID = "eleven_turbo_v2_5" +OUTPUT_FORMAT = "mp3_44100_128" +MAX_CHARACTERS = 10000 + +class ElevenLabsAPI: + + @dataclass + class VoiceIDs: + Clyde = "wyWA56cQNU2KqUW4eCsI" + Charles = "zNsotODqUhvbJ5wMG7Ei" + + def __init__(self, token: str): + self.client = ElevenLabs(api_key=token) + self.character_count = 0 + self.update_character_count() + + def get_spoken_name(self, name: str, audio_dir: Union[Path, str], + voice_id: str = VoiceIDs.Charles, speed: float = 1.0, + regen: bool = False) -> Path: + name_id = name.replace(" ", "_") + file = Path(audio_dir) / f"{name_id}.mp3" + if file.exists() and not regen: + return file + + voice_settings = VoiceSettings(speed=speed) + audio_stream = self.client.text_to_speech.convert( + text=name, + voice_id=voice_id, + model_id=MODEL_ID, + output_format=OUTPUT_FORMAT, + voice_settings=voice_settings + ) + with open(file, "wb") as f: + for chunk in audio_stream: + f.write(chunk) + + self.update_character_count() + return file + + def update_character_count(self): + subscription = self.client.user.subscription.get() + self.character_count = subscription.character_count + + def get_remaining_character_count(self): + return MAX_CHARACTERS - self.character_count \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index cdc18e9..76988ff 100644 Binary files a/requirements.txt and b/requirements.txt differ