πŸ‘¨β€πŸ’» dev shondo

development

fukurou

the supreme coder
ADMIN
Python:
import os
import pygame
import threading

from fishaudio import FishAudio
from fishaudio.utils import save

from DLC.skills_monitor import DiInstaller
from LivinGrimoirePacket.AXPython import DrawRnd
from LivinGrimoirePacket.LivinGrimoire import Skill, Brain
from LivinGrimoirePacket.UniqueSkills import DiCMD

os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "1"

# ╔════════════════════════════════════════════════════════════╗
# β•‘  πŸ“ Add a 'fish_audio' directory at the same level as main.pyβ•‘
# β•‘  🎢 MP3 files will be created inside it automatically.       β•‘
# β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•


class ShorniTTS:
    def __init__(self, api_key: str, reference_id: str):
        pygame.init()
        pygame.mixer.init()
        self.client = FishAudio(api_key=api_key)
        self.reference_id = reference_id
        self.voice = "default"  # label used for filenames

    @staticmethod
    def __clean_filename(txt: str) -> str:
        invalid_chars = ['?', ':', ',', "'", '\n', '"', '!', '.', ';']
        for char in invalid_chars:
            txt = txt.replace(char, "")
        return txt.replace(" ", "_")

    @staticmethod
    def __play_file(filepath: str):
        try:
            sound = pygame.mixer.Sound(filepath)
            sound.play()
            while pygame.mixer.get_busy():
                pygame.time.delay(100)
        except pygame.error:
            if os.path.exists(filepath):
                os.remove(filepath)

    def speak(self, txt: str):
        file_name = self.__clean_filename(txt)
        filepath = f'fish_audio/{self.voice}_{file_name}.mp3'

        if len(txt) > 242:
            my_thread = threading.Thread(target=self.__create_and_play_temp, args=(txt,))
        elif os.path.isfile(filepath):
            my_thread = threading.Thread(target=self.__play_file, args=(filepath,))
        else:
            my_thread = threading.Thread(target=self.__create_and_save_and_play, args=(txt,))
        my_thread.daemon = True
        my_thread.start()

    def __create_and_play_temp(self, txt: str):
        try:
            audio = self.client.tts.convert(text=txt, reference_id=self.reference_id)
            temp_file = f'fish_audio/temp_{hash(txt)}.mp3'
            save(audio, temp_file)
            if os.path.getsize(temp_file) > 0:
                self.__play_file(temp_file)
            os.remove(temp_file)
        except Exception as e:
            print(f"Error playing TTS: {e}")

    def __create_and_save_and_play(self, txt: str):
        try:
            audio = self.client.tts.convert(text=txt, reference_id=self.reference_id)
            file_name = self.__clean_filename(txt)
            filepath = f'fish_audio/{self.voice}_{file_name}.mp3'
            save(audio, filepath)
            if os.path.getsize(filepath) > 0:
                self.__play_file(filepath)
            else:
                os.remove(filepath)
        except Exception as e:
            print(f"Error creating TTS file: {e}")

    def setVoice(self, newVoice: str, newReferenceId: str = None):
        self.voice = newVoice
        if newReferenceId:
            self.reference_id = newReferenceId


class DiTTS_fish(Skill):
    def __init__(self, api_key: str, reference_ids: list[str]):
        super().__init__()
        self.set_skill_type(3)  # continuous skill
        self.set_skill_lobe(2)  # output(hardware) skill
        self.voices: DrawRnd = DrawRnd(*reference_ids)
        # pick one reference_id at random
        ref_id = self.voices.renewableDraw()
        self.speech: ShorniTTS = ShorniTTS(api_key=api_key, reference_id=ref_id)
        self.speech.setVoice("voice")

    def input(self, ear: str, skin: str, eye: str):
        if self._kokoro.toHeart["cmd"] == "change voice":
            new_ref = self.voices.renewableDraw()
            self.speech.setVoice("voice", new_ref)
            self.speech.speak("my voice has been changed")
            return
        if len(ear) == 0:
            return
        self.speech.speak(ear)


class DiTTSInstaller(DiInstaller):
    def __init__(self, brain: Brain, api_key: str, reference_ids: list[str]):
        super().__init__(brain)
        self.skills.append(DiCMD().addModes("change voice"))
        self.skills.append(DiTTS_fish(api_key, reference_ids))

    def input(self, ear: str, skin: str, eye: str):
        self.setSimpleAlg("Installer removed; input is unreachable.")

##########
Replace "your_api_key_here" with your Fish Audio API key.

Provide a list of reference_ids (voice IDs from Fish Audio) when instantiating DiTTSInstaller.

MP3s will now be saved in fish_audio/ instead of sounds/.
 

fukurou

the supreme coder
ADMIN
Python:
import requests
import os

from DLC.skills_monitor import DiInstaller
from LivinGrimoirePacket.AXPython import DrawRnd
from LivinGrimoirePacket.LivinGrimoire import Skill, Brain
from LivinGrimoirePacket.UniqueSkills import DiCMD

os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "1"
import pygame
import threading

from fishaudio import FishAudio
from fishaudio.utils import save


class ShorniTTS:
    def __init__(self):
        pygame.init()
        pygame.mixer.init()

        # FishAudio API
        self.api_key = os.environ.get("FISH_AUDIO_API_KEY", "")
        self.client = FishAudio(api_key=self.api_key)

        # default voice name (directory name)
        self.voice = "pomni"

        # reference_id for pomni voice
        self.reference_id = "991a5b4b1e454556908e106e87771354"

    @staticmethod
    def __clean_filename(txt: str) -> str:
        invalid_chars = ['?', ':', ',', "'", '\n', '"', '!', '*', ';']
        for char in invalid_chars:
            txt = txt.replace(char, "")
        return txt

    @staticmethod
    def __play_file(txt: str, voice: str, filepath: str = None):
        try:
            if filepath is None:
                filepath = f'sounds/{voice}/{txt}.mp3'
            sound = pygame.mixer.Sound(filepath)
            sound.play()
            while pygame.mixer.get_busy():
                pygame.time.delay(100)
        except pygame.error:
            if os.path.exists(filepath):
                os.remove(filepath)

    def speak(self, txt: str):
        file_name = self.__clean_filename(txt)
        filepath = f'sounds/{self.voice}/{file_name}.mp3'

        if len(txt) > 242:
            t = threading.Thread(target=self.__create_and_play_temp, args=(self, txt))
            t.daemon = True
            t.start()
        elif os.path.isfile(filepath):
            t = threading.Thread(target=self.__play_file, args=(file_name, self.voice, filepath))
            t.daemon = True
            t.start()
        else:
            t = threading.Thread(target=self.__create_and_save_and_play, args=(self, txt))
            t.daemon = True
            t.start()

    @staticmethod
    def __create_and_play_temp(tts: 'ShorniTTS', txt: str):
        try:
            audio = tts.client.tts.convert(
                text=txt,
                reference_id=tts.reference_id
            )

            temp_file = f"sounds/temp_{hash(txt)}.mp3"
            save(audio, temp_file)

            if os.path.getsize(temp_file) > 0:
                ShorniTTS.__play_file("", tts.voice, temp_file)

            os.remove(temp_file)

        except Exception as e:
            print(f"FishAudio temp error: {e}")

    @staticmethod
    def __create_and_save_and_play(tts: 'ShorniTTS', txt: str):
        try:
            audio = tts.client.tts.convert(
                text=txt,
                reference_id=tts.reference_id
            )

            file_name = ShorniTTS.__clean_filename(txt)
            filepath = f'sounds/{tts.voice}/{file_name}.mp3'

            save(audio, filepath)

            if os.path.getsize(filepath) > 0:
                ShorniTTS.__play_file(file_name, tts.voice, filepath)
            else:
                os.remove(filepath)

        except Exception as e:
            print(f"FishAudio save error: {e}")

    def setVoice(self, newVoice: str):
        # directory name only; FishAudio uses reference_id, not voice names
        self.voice = newVoice


class DiTTS_fish(Skill):
    def __init__(self):
        super().__init__()
        self.set_skill_type(3)  # continuous skill
        self.set_skill_lobe(2)  # output(hardware) skill

        # voices known at construction time
        voice_names = ("pomni", "mickey", "bonnie", "daphne")

        # --- directory creation ---
        base_dir = "sounds"
        if not os.path.isdir(base_dir):
            os.mkdir(base_dir)

        for v in voice_names:
            vdir = os.path.join(base_dir, v)
            if not os.path.isdir(vdir):
                os.mkdir(vdir)
        # --------------------------

        # init TTS
        self.speech: ShorniTTS = ShorniTTS()

        # voice picker
        self.voices: DrawRnd = DrawRnd(*voice_names)

        # set initial voice (pomni is default)
        self.speech.setVoice("pomni")

    def input(self, ear: str, skin: str, eye: str):
        if self._kokoro.toHeart["cmd"] == "change voice":
            new_voice = self.voices.renewableDraw()
            self.speech.setVoice(new_voice)
            self.speech.speak("my voice has been changed")
            return

        if len(ear) == 0:
            return

        self.speech.speak(ear)


class DiTTSInstaller(DiInstaller):
    def __init__(self, brain: Brain):
        super().__init__(brain)
        self.skills.append(DiCMD().addModes("change voice"))
        self.skills.append(DiTTS_fish())

    def input(self, ear: str, skin: str, eye: str):
        self.setSimpleAlg("Installer removed; input is unreachable.")
 

fukurou

the supreme coder
ADMIN
Python:
import os
import pygame
import threading

from DLC.skills_monitor import DiInstaller
from LivinGrimoirePacket.AXPython import DrawRnd
from LivinGrimoirePacket.LivinGrimoire import Skill, Brain
from LivinGrimoirePacket.UniqueSkills import DiCMD

from fishaudio import FishAudio
from fishaudio.utils import save

os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "1"


# ============================================================
#   FISH AUDIO TTS ENGINE
# ============================================================
class FishAudioTTS:
    def __init__(self):
        pygame.init()
        pygame.mixer.init()

        # FishAudio API
        self.api_key = os.environ.get("FISH_AUDIO_API_KEY", "")
        self.client = FishAudio(api_key=self.api_key)

        # The ONLY source of truth for voices.
        # KEY = directory name / voice name
        # VALUE = FishAudio reference_id
        self.voice_map: dict[str, str] = {
            "pomni": "991a5b4b1e454556908e106e87771354"
        }

        # default voice
        self.voice = "pomni"

    @staticmethod
    def __clean_filename(txt: str) -> str:
        invalid_chars = ['?', ':', ',', "'", '\n', '"', '!', '*', ';']
        for char in invalid_chars:
            txt = txt.replace(char, "")
        return txt

    @staticmethod
    def __play_file(txt: str, voice: str, filepath: str = None):
        try:
            if filepath is None:
                filepath = f'sounds/{voice}/{txt}.mp3'
            sound = pygame.mixer.Sound(filepath)
            sound.play()
            while pygame.mixer.get_busy():
                pygame.time.delay(100)
        except pygame.error:
            if os.path.exists(filepath):
                os.remove(filepath)

    def speak(self, txt: str):
        file_name = self.__clean_filename(txt)
        filepath = f'sounds/{self.voice}/{file_name}.mp3'

        if len(txt) > 242:
            t = threading.Thread(target=self.__create_and_play_temp, args=(self, txt))
            t.daemon = True
            t.start()
        elif os.path.isfile(filepath):
            t = threading.Thread(target=self.__play_file, args=(file_name, self.voice, filepath))
            t.daemon = True
            t.start()
        else:
            t = threading.Thread(target=self.__create_and_save_and_play, args=(self, txt))
            t.daemon = True
            t.start()

    @staticmethod
    def __create_and_play_temp(tts: 'FishAudioTTS', txt: str):
        try:
            ref_id = tts.voice_map[tts.voice]

            audio = tts.client.tts.convert(
                text=txt,
                reference_id=ref_id
            )

            temp_file = f"sounds/temp_{hash(txt)}.mp3"
            save(audio, temp_file)

            if os.path.getsize(temp_file) > 0:
                FishAudioTTS.__play_file("", tts.voice, temp_file)

            os.remove(temp_file)

        except Exception as e:
            print(f"FishAudio temp error: {e}")

    @staticmethod
    def __create_and_save_and_play(tts: 'FishAudioTTS', txt: str):
        try:
            ref_id = tts.voice_map[tts.voice]

            audio = tts.client.tts.convert(
                text=txt,
                reference_id=ref_id
            )

            file_name = FishAudioTTS.__clean_filename(txt)
            filepath = f'sounds/{tts.voice}/{file_name}.mp3'

            save(audio, filepath)

            if os.path.getsize(filepath) > 0:
                FishAudioTTS.__play_file(file_name, tts.voice, filepath)
            else:
                os.remove(filepath)

        except Exception as e:
            print(f"FishAudio save error: {e}")

    def setVoice(self, newVoice: str):
        if newVoice in self.voice_map:
            self.voice = newVoice
        else:
            print(f"Unknown voice '{newVoice}', keeping current voice.")


# ============================================================
#   DI TTS SKILL (FISH AUDIO)
# ============================================================
class DiTTS_FishAudio(Skill):
    def __init__(self):
        super().__init__()
        self.set_skill_type(3)
        self.set_skill_lobe(2)

        # Initialize TTS
        self.speech: FishAudioTTS = FishAudioTTS()

        # Voices come DIRECTLY from the dictionary keys.
        voice_names = list(self.speech.voice_map.keys())

        # directory creation
        base_dir = "sounds"
        if not os.path.isdir(base_dir):
            os.mkdir(base_dir)

        for v in voice_names:
            vdir = os.path.join(base_dir, v)
            if not os.path.isdir(vdir):
                os.mkdir(vdir)

        # voice picker (future‑proof)
        self.voices: DrawRnd = DrawRnd(*voice_names)

        # set initial voice
        self.speech.setVoice("pomni")

    def input(self, ear: str, skin: str, eye: str):
        if self._kokoro.toHeart["cmd"] == "change voice":
            new_voice = self.voices.renewableDraw()
            self.speech.setVoice(new_voice)
            self.speech.speak("my voice has been changed")
            return

        if len(ear) == 0:
            return

        self.speech.speak(ear)


# ============================================================
#   INSTALLER (FISH AUDIO)
# ============================================================
class DiTTS_FishAudioInstaller(DiInstaller):
    def __init__(self, brain: Brain):
        super().__init__(brain)
        self.skills.append(DiCMD().addModes("change voice"))
        self.skills.append(DiTTS_FishAudio())

    def input(self, ear: str, skin: str, eye: str):
        self.setSimpleAlg("Installer removed; input is unreachable.")
 

fukurou

the supreme coder
ADMIN
Python:
import os
import pygame
import threading

from fishaudio import FishAudio
from fishaudio.utils import save

from DLC.skills_monitor import DiInstaller
from LivinGrimoirePacket.AXPython import DrawRnd
from LivinGrimoirePacket.LivinGrimoire import Skill, Brain
from LivinGrimoirePacket.UniqueSkills import DiCMD

os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "1"

# ╔════════════════════════════════════════════════════════════╗
# β•‘  πŸ“ Add a 'fish_audio' directory at the same level as main.pyβ•‘
# β•‘  🎢 MP3 files will be created inside it automatically.       β•‘
# β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•


class FishAudioTTS:   # ← renamed
    def __init__(self, api_key: str, reference_id: str):
        pygame.init()
        pygame.mixer.init()
        self.client = FishAudio(api_key=api_key)
        self.reference_id = reference_id
        self.voice = "default"  # label used for filenames

    @staticmethod
    def __clean_filename(txt: str) -> str:
        invalid_chars = ['?', ':', ',', "'", '\n', '"', '!', '*', ';']
        for char in invalid_chars:
            txt = txt.replace(char, "")
        return txt.replace(" ", "_")

    @staticmethod
    def __play_file(filepath: str):
        try:
            sound = pygame.mixer.Sound(filepath)
            sound.play()
            while pygame.mixer.get_busy():
                pygame.time.delay(100)
        except pygame.error:
            if os.path.exists(filepath):
                os.remove(filepath)

    def speak(self, txt: str):
        file_name = self.__clean_filename(txt)
        filepath = f'fish_audio/{self.voice}_{file_name}.mp3'

        if len(txt) > 242:
            my_thread = threading.Thread(target=self.__create_and_play_temp, args=(txt,))
        elif os.path.isfile(filepath):
            my_thread = threading.Thread(target=self.__play_file, args=(filepath,))
        else:
            my_thread = threading.Thread(target=self.__create_and_save_and_play, args=(txt,))
        my_thread.daemon = True
        my_thread.start()

    def __create_and_play_temp(self, txt: str):
        try:
            audio = self.client.tts.convert(text=txt, reference_id=self.reference_id)
            temp_file = f'fish_audio/temp_{hash(txt)}.mp3'
            save(audio, temp_file)
            if os.path.getsize(temp_file) > 0:
                self.__play_file(temp_file)
            os.remove(temp_file)
        except Exception as e:
            print(f"Error playing TTS: {e}")

    def __create_and_save_and_play(self, txt: str):
        try:
            audio = self.client.tts.convert(text=txt, reference_id=self.reference_id)
            file_name = self.__clean_filename(txt)
            filepath = f'fish_audio/{self.voice}_{file_name}.mp3'
            save(audio, filepath)
            if os.path.getsize(filepath) > 0:
                self.__play_file(filepath)
            else:
                os.remove(filepath)
        except Exception as e:
            print(f"Error creating TTS file: {e}")

    def setVoice(self, newVoice: str, newReferenceId: str = None):
        self.voice = newVoice
        if newReferenceId:
            self.reference_id = newReferenceId


class DiTTS_FishAudio(Skill):   # ← renamed
    def __init__(self, api_key: str, reference_ids: list[str]):
        super().__init__()
        self.set_skill_type(3)  # continuous skill
        self.set_skill_lobe(2)  # output(hardware) skill
        self.voices: DrawRnd = DrawRnd(*reference_ids)
        # pick one reference_id at random
        ref_id = self.voices.renewableDraw()
        self.speech: FishAudioTTS = FishAudioTTS(api_key=api_key, reference_id=ref_id)
        self.speech.setVoice("voice")

    def input(self, ear: str, skin: str, eye: str):
        if self._kokoro.toHeart["cmd"] == "change voice":
            new_ref = self.voices.renewableDraw()
            self.speech.setVoice("voice", new_ref)
            self.speech.speak("my voice has been changed")
            return
        if len(ear) == 0:
            return
        self.speech.speak(ear)


class DiTTS_FishAudioInstaller(DiInstaller):   # ← renamed
    def __init__(self, brain: Brain, api_key: str, reference_ids: list[str]):
        super().__init__(brain)
        self.skills.append(DiCMD().addModes("change voice"))
        self.skills.append(DiTTS_FishAudio(api_key, reference_ids))

    def input(self, ear: str, skin: str, eye: str):
        self.setSimpleAlg("Installer removed; input is unreachable.")
 

fukurou

the supreme coder
ADMIN
Python:
import os
import pygame
import threading

from fishaudio import FishAudio
from fishaudio.utils import save

from DLC.skills_monitor import DiInstaller
from LivinGrimoirePacket.AXPython import DrawRnd
from LivinGrimoirePacket.LivinGrimoire import Skill, Brain
from LivinGrimoirePacket.UniqueSkills import DiCMD

os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "1"


# ============================================================
#   FishAudio TTS (Narakeet β†’ FishAudio, same architecture)
# ============================================================
class FishAudioTTS:
    def __init__(self, api_key: str, reference_id: str):
        pygame.init()
        pygame.mixer.init()

        self.client = FishAudio(api_key=api_key)
        self.reference_id = reference_id

        # voice is now the reference_id (used for directory name)
        self.voice = reference_id

    @staticmethod
    def __clean_filename(txt: str) -> str:
        invalid_chars = ['?', ':', ',', "'", '\n', '"', '!', '*', ';']
        for char in invalid_chars:
            txt = txt.replace(char, "")
        return txt

    @staticmethod
    def __play_file(txt: str, voice: str, filepath: str = None):
        try:
            if filepath is None:
                filepath = f'sounds/{voice}/{txt}.mp3'
            sound = pygame.mixer.Sound(filepath)
            sound.play()
            while pygame.mixer.get_busy():
                pygame.time.delay(100)
        except pygame.error:
            if os.path.exists(filepath):
                os.remove(filepath)

    def speak(self, txt: str):
        file_name = self.__clean_filename(txt)
        filepath = f'sounds/{self.voice}/{file_name}.mp3'

        if len(txt) > 242:
            t = threading.Thread(target=self.__create_and_play_temp, args=(self, txt))
            t.daemon = True
            t.start()
        elif os.path.isfile(filepath):
            t = threading.Thread(target=self.__play_file, args=(file_name, self.voice, filepath))
            t.daemon = True
            t.start()
        else:
            t = threading.Thread(target=self.__create_and_save_and_play, args=(self, txt))
            t.daemon = True
            t.start()

    @staticmethod
    def __create_and_play_temp(tts: 'FishAudioTTS', txt: str):
        try:
            audio = tts.client.tts.convert(
                text=txt,
                reference_id=tts.reference_id
            )

            temp_file = f'sounds/temp_{hash(txt)}.mp3'
            save(audio, temp_file)

            if os.path.getsize(temp_file) > 0:
                FishAudioTTS.__play_file("", tts.voice, temp_file)

            os.remove(temp_file)

        except Exception as e:
            print(f"Error playing TTS: {e}")

    @staticmethod
    def __create_and_save_and_play(tts: 'FishAudioTTS', txt: str):
        try:
            audio = tts.client.tts.convert(
                text=txt,
                reference_id=tts.reference_id
            )

            file_name = FishAudioTTS.__clean_filename(txt)
            filepath = f'sounds/{tts.voice}/{file_name}.mp3'

            save(audio, filepath)

            if os.path.getsize(filepath) > 0:
                FishAudioTTS.__play_file(file_name, tts.voice, filepath)
            else:
                os.remove(filepath)

        except Exception as e:
            print(f"Error creating TTS file: {e}")

    def setVoice(self, new_ref_id: str):
        self.reference_id = new_ref_id
        self.voice = new_ref_id  # directory name


# ============================================================
#   DI TTS Skill (Narakeet β†’ FishAudio)
# ============================================================
class DiTTS_FishAudio(Skill):
    def __init__(self, api_key: str, reference_ids: list[str]):
        super().__init__()
        self.set_skill_type(3)
        self.set_skill_lobe(2)

        # voices are now reference_ids
        self.voices: DrawRnd = DrawRnd(*reference_ids)

        # pick one reference_id
        ref_id = self.voices.renewableDraw()

        # create directory for each ref_id
        base_dir = "sounds"
        if not os.path.isdir(base_dir):
            os.mkdir(base_dir)

        for ref in reference_ids:
            vdir = os.path.join(base_dir, ref)
            if not os.path.isdir(vdir):
                os.mkdir(vdir)

        # init TTS
        self.speech: FishAudioTTS = FishAudioTTS(api_key, ref_id)

    def input(self, ear: str, skin: str, eye: str):
        if self._kokoro.toHeart["cmd"] == "change voice":
            new_ref = self.voices.renewableDraw()
            self.speech.setVoice(new_ref)
            self.speech.speak("my voice has been changed")
            return

        if len(ear) == 0:
            return

        self.speech.speak(ear)


# ============================================================
#   Installer (Narakeet β†’ FishAudio)
# ============================================================
class DiTTS_FishAudioInstaller(DiInstaller):
    def __init__(self, brain: Brain, api_key: str, reference_ids: list[str]):
        super().__init__(brain)
        self.skills.append(DiCMD().addModes("change voice"))
        self.skills.append(DiTTS_FishAudio(api_key, reference_ids))

    def input(self, ear: str, skin: str, eye: str):
        self.setSimpleAlg("Installer removed; input is unreachable.")
 

fukurou

the supreme coder
ADMIN
Python:
import os
import pygame
import threading

from fishaudio import FishAudio
from fishaudio.utils import save

from DLC.skills_monitor import DiInstaller
from LivinGrimoirePacket.AXPython import DrawRnd
from LivinGrimoirePacket.LivinGrimoire import Skill, Brain
from LivinGrimoirePacket.UniqueSkills import DiCMD

os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "1"


# ============================================================
#   FishAudio TTS (clean, no redundancy)
# ============================================================
class FishAudioTTS:
    def __init__(self, api_key: str, reference_id: str):
        pygame.init()
        pygame.mixer.init()

        self.client = FishAudio(api_key=api_key)
        self.reference_id = reference_id  # ONLY identifier

    @staticmethod
    def __clean_filename(txt: str) -> str:
        invalid_chars = ['?', ':', ',', "'", '\n', '"', '!', '*', ';']
        for char in invalid_chars:
            txt = txt.replace(char, "")
        return txt

    @staticmethod
    def __play_file(filepath: str):
        try:
            sound = pygame.mixer.Sound(filepath)
            sound.play()
            while pygame.mixer.get_busy():
                pygame.time.delay(100)
        except pygame.error:
            if os.path.exists(filepath):
                os.remove(filepath)

    def speak(self, txt: str):
        file_name = self.__clean_filename(txt)
        dir_path = f'sounds/{self.reference_id}'
        filepath = f'{dir_path}/{file_name}.mp3'

        if len(txt) > 242:
            t = threading.Thread(target=self.__create_and_play_temp, args=(txt,))
        elif os.path.isfile(filepath):
            t = threading.Thread(target=self.__play_file, args=(filepath,))
        else:
            t = threading.Thread(target=self.__create_and_save_and_play, args=(txt,))
        t.daemon = True
        t.start()

    def __create_and_play_temp(self, txt: str):
        try:
            audio = self.client.tts.convert(text=txt, reference_id=self.reference_id)
            temp_file = f'sounds/temp_{hash(txt)}.mp3'
            save(audio, temp_file)

            if os.path.getsize(temp_file) > 0:
                self.__play_file(temp_file)

            os.remove(temp_file)

        except Exception as e:
            print(f"Error playing TTS: {e}")

    def __create_and_save_and_play(self, txt: str):
        try:
            audio = self.client.tts.convert(text=txt, reference_id=self.reference_id)

            dir_path = f'sounds/{self.reference_id}'
            if not os.path.isdir(dir_path):
                os.mkdir(dir_path)

            file_name = self.__clean_filename(txt)
            filepath = f'{dir_path}/{file_name}.mp3'

            save(audio, filepath)

            if os.path.getsize(filepath) > 0:
                self.__play_file(filepath)
            else:
                os.remove(filepath)

        except Exception as e:
            print(f"Error creating TTS file: {e}")

    def setVoice(self, new_reference_id: str):
        self.reference_id = new_reference_id


# ============================================================
#   DI TTS Skill (FishAudio)
# ============================================================
class DiTTS_FishAudio(Skill):
    def __init__(self, api_key: str, reference_ids: list[str]):
        super().__init__()
        self.set_skill_type(3)
        self.set_skill_lobe(2)

        # reference_ids ONLY
        self.voices: DrawRnd = DrawRnd(*reference_ids)

        # pick one reference_id
        ref_id = self.voices.renewableDraw()

        # create directories
        base_dir = "sounds"
        if not os.path.isdir(base_dir):
            os.mkdir(base_dir)

        for ref in reference_ids:
            vdir = os.path.join(base_dir, ref)
            if not os.path.isdir(vdir):
                os.mkdir(vdir)

        # init TTS
        self.speech: FishAudioTTS = FishAudioTTS(api_key, ref_id)

    def input(self, ear: str, skin: str, eye: str):
        if self._kokoro.toHeart["cmd"] == "change voice":
            new_ref = self.voices.renewableDraw()
            self.speech.setVoice(new_ref)
            self.speech.speak("my voice has been changed")
            return

        if len(ear) == 0:
            return

        self.speech.speak(ear)


# ============================================================
#   Installer
# ============================================================
class DiTTS_FishAudioInstaller(DiInstaller):
    def __init__(self, brain: Brain, api_key: str, reference_ids: list[str]):
        super().__init__(brain)
        self.skills.append(DiCMD().addModes("change voice"))
        self.skills.append(DiTTS_FishAudio(api_key, reference_ids))

    def input(self, ear: str, skin: str, eye: str):
        self.setSimpleAlg("Installer removed; input is unreachable.")
 

fukurou

the supreme coder
ADMIN
Python:
import os
import pygame
import threading

from fishaudio import FishAudio
from fishaudio.utils import save

from DLC.skills_monitor import DiInstaller
from LivinGrimoirePacket.AXPython import DrawRnd
from LivinGrimoirePacket.LivinGrimoire import Skill, Brain
from LivinGrimoirePacket.UniqueSkills import DiCMD

os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "1"


# ============================================================
#   FishAudio TTS (clean, no redundancy)
# ============================================================
class FishAudioTTS:
    def __init__(self, api_key: str, reference_id: str):
        pygame.init()
        pygame.mixer.init()

        self.client = FishAudio(api_key=api_key)
        self.reference_id = reference_id  # ONLY identifier

    @staticmethod
    def __clean_filename(txt: str) -> str:
        invalid_chars = ['?', ':', ',', "'", '\n', '"', '!', '*', ';']
        for char in invalid_chars:
            txt = txt.replace(char, "")
        return txt

    @staticmethod
    def __play_file(filepath: str):
        try:
            sound = pygame.mixer.Sound(filepath)
            sound.play()
            while pygame.mixer.get_busy():
                pygame.time.delay(100)
        except pygame.error:
            if os.path.exists(filepath):
                os.remove(filepath)

    def speak(self, txt: str):
        file_name = self.__clean_filename(txt)
        dir_path = f'sounds/{self.reference_id}'
        filepath = f'{dir_path}/{file_name}.mp3'

        if len(txt) > 242:
            t = threading.Thread(target=self.__create_and_play_temp, args=(txt,))
        elif os.path.isfile(filepath):
            t = threading.Thread(target=self.__play_file, args=(filepath,))
        else:
            t = threading.Thread(target=self.__create_and_save_and_play, args=(txt,))
        t.daemon = True
        t.start()

    def __create_and_play_temp(self, txt: str):
        try:
            audio = self.client.tts.convert(text=txt, reference_id=self.reference_id)
            temp_file = f'sounds/temp_{hash(txt)}.mp3'
            save(audio, temp_file)

            if os.path.getsize(temp_file) > 0:
                self.__play_file(temp_file)

            os.remove(temp_file)

        except Exception as e:
            print(f"Error playing TTS: {e}")

    def __create_and_save_and_play(self, txt: str):
        try:
            audio = self.client.tts.convert(text=txt, reference_id=self.reference_id)

            dir_path = f'sounds/{self.reference_id}'
            if not os.path.isdir(dir_path):
                os.mkdir(dir_path)

            file_name = self.__clean_filename(txt)
            filepath = f'{dir_path}/{file_name}.mp3'

            save(audio, filepath)

            if os.path.getsize(filepath) > 0:
                self.__play_file(filepath)
            else:
                os.remove(filepath)

        except Exception as e:
            print(f"Error creating TTS file: {e}")

    def setVoice(self, new_reference_id: str):
        self.reference_id = new_reference_id


# ============================================================
#   DI TTS Skill (FishAudio)
# ============================================================
class DiTTS_FishAudio(Skill):
    def __init__(self, api_key: str, reference_ids: list[str]):
        super().__init__()
        self.set_skill_type(3)
        self.set_skill_lobe(2)

        # reference_ids ONLY
        self.voices: DrawRnd = DrawRnd(*reference_ids)

        # pick one reference_id
        ref_id = self.voices.renewableDraw()

        # create directories
        base_dir = "sounds"
        if not os.path.isdir(base_dir):
            os.mkdir(base_dir)

        for ref in reference_ids:
            vdir = os.path.join(base_dir, ref)
            if not os.path.isdir(vdir):
                os.mkdir(vdir)

        # init TTS
        self.speech: FishAudioTTS = FishAudioTTS(api_key, ref_id)

    def input(self, ear: str, skin: str, eye: str):
        if self._kokoro.toHeart["cmd"] == "change voice":
            new_ref = self.voices.renewableDraw()
            self.speech.setVoice(new_ref)
            self.speech.speak("my voice has been changed")
            return

        if len(ear) == 0:
            return

        self.speech.speak(ear)


# ============================================================
#   Installer
# ============================================================
class DiTTS_FishAudioInstaller(DiInstaller):
    def __init__(self, brain: Brain, api_key: str, reference_ids: list[str]):
        super().__init__(brain)
        self.skills.append(DiCMD().addModes("change voice"))
        self.skills.append(DiTTS_FishAudio(api_key, reference_ids))

    def input(self, ear: str, skin: str, eye: str):
        self.setSimpleAlg("Installer removed; input is unreachable.")
 
Top