from __future__ import annotations import os import shutil import subprocess from concurrent.futures import Future from dataclasses import dataclass from anki.sound import AVTag, TTSTag from aqt import mw from aqt.sound import OnDoneCallback, av_player from aqt.tts import TTSProcessPlayer, TTSVoice # Enter the full path here if Anki cannot find the command in the PATH. # Example: AIVIS_CMD = "/usr/local/bin/aivis-jp-tts" AIVIS_CMD = "/home/francesco/bin/aivis-jp-tts" @dataclass class AivisVoice(TTSVoice): pass class AivisPlayer(TTSProcessPlayer): def get_available_voices(self) -> list[TTSVoice]: cmd = AIVIS_CMD if not os.path.isabs(cmd) and shutil.which(cmd) is None: return [] return [AivisVoice(name="AivisJP", lang="ja_JP")] def _play(self, tag: AVTag) -> None: assert isinstance(tag, TTSTag) match = self.voice_for_tag(tag) assert match text = tag.field_text.strip() if not text: return self._tmpfile = self.temp_file_for_tag_and_voice(tag, match.voice) + ".mp3" if os.path.exists(self._tmpfile): return proc = subprocess.run( [AIVIS_CMD, text, self._tmpfile], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) if proc.returncode != 0: raise RuntimeError( f"aivis-jp-tts failed ({proc.returncode})\n" f"stdout:\n{proc.stdout}\n\nstderr:\n{proc.stderr}" ) def _on_done(self, ret: Future, cb: OnDoneCallback) -> None: ret.result() av_player.insert_file(self._tmpfile) cb() def stop(self) -> None: pass av_player.players.append(AivisPlayer(mw.taskman))