Skip to content

Commit

Permalink
Merge pull request #7 from jason-kane/beta
Browse files Browse the repository at this point in the history
configure panel; Easier to config ElevenLabs, choose default engine
  • Loading branch information
jason-kane authored May 1, 2024
2 parents ed0c763 + cbfbb81 commit 01d50f4
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 149 deletions.
12 changes: 6 additions & 6 deletions presets.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"Random Any": {
"engine": "Windows TTS",
"engine": "any",
"BaseTTSConfig": {
"voice_name": [
"random",
Expand All @@ -10,7 +10,7 @@
}
},
"Random Female": {
"engine": "Windows TTS",
"engine": "any",
"BaseTTSConfig": {
"voice_name": [
"random",
Expand All @@ -20,7 +20,7 @@
}
},
"Random Male": {
"engine": "Windows TTS",
"engine": "any",
"BaseTTSConfig": {
"voice_name": [
"random",
Expand All @@ -30,7 +30,7 @@
}
},
"Clockwork": {
"engine": "Windows TTS",
"engine": "any",
"BaseTTSConfig": {
"voice_name": [
"random",
Expand Down Expand Up @@ -59,7 +59,7 @@
}
},
"Vahzilok": {
"engine": "Windows TTS",
"engine": "any",
"BaseTTSConfig": {
"voice_name": [
"random",
Expand All @@ -77,7 +77,7 @@
}
},
"Circle of Thorns": {
"engine": "Windows TTS",
"engine": "any",
"BaseTTSConfig": {
"voice_name": [
"random",
Expand Down
5 changes: 2 additions & 3 deletions src/coh_npc_voices/effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import pedalboard
import voicebox
from sqlalchemy import select
import settings

log = logging.getLogger('__name__')

Expand Down Expand Up @@ -954,8 +953,8 @@ def __init__(self, parent, *args, **kwargs):
default=0.5,
from_=-0.1,
to=1,
digits=2,
resolution=0.1
digits=3,
resolution=0.01
).pack(side='top', fill='x', expand=True)

LScale(
Expand Down
101 changes: 76 additions & 25 deletions src/coh_npc_voices/engines.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ def __init__(self, parent, selected_character, *args, **kwargs):

def say(self, message, effects, sink=None, *args, **kwargs):
tts = self.get_tts()
log.info('tts: %s', tts)
log.info(f'{self}.say({message=}, {effects=}, {sink=}, {args=}, {kwargs=}')
log.info(f'Invoking voicebox.SimpleVoicebox({tts=}, {effects=}, {sink=})')
vb = voicebox.SimpleVoicebox(
tts=tts,
effects=effects,
Expand Down Expand Up @@ -299,14 +300,24 @@ def __init__(self, parent, selected_character, *args, **kwargs):

# with defaults
self.language_code = tk.StringVar(value="en-US")
self.voice_name = tk.StringVar(value="en-US-Casual-K")
self.voice_name = tk.StringVar(value="")
# ssml_gender handling is sloppy
self.ssml_gender = tk.StringVar(value="MALE")
self.rate = tk.DoubleVar(value=1.0)
self.pitch = tk.DoubleVar(value=0.0)

self.parameters = set(("language_code", "voice_name", "rate", "pitch"))

self.load_character(self.selected_character.get())
character = self.load_character(self.selected_character.get())

# what is this characters gender (if it has one)?
npc_data = settings.get_npc_data(character.name)
gender = None
if npc_data:
if npc_data["gender"] == "GENDER_MALE":
gender = "male"
elif npc_data["gender"] == "GENDER_FEMALE":
gender = "female"

language_frame = tk.Frame(self)
tk.Label(language_frame, text="Language Code", anchor="e").pack(
Expand All @@ -333,11 +344,15 @@ def __init__(self, parent, selected_character, *args, **kwargs):
voice_frame,
textvariable=self.voice_name,
)
all_voices = self.get_voice_names(language_code=self.language_code.get())
all_voices = self.get_voice_names(
language_code=self.language_code.get(),
gender=gender
)
voice_combo["values"] = all_voices
voice_combo["state"] = "readonly"
voice_combo.pack(side="left", fill="x", expand=True)
voice_frame.pack(side="top", fill="x", expand=True)
self.voice_name.set(value=all_voices[0])

self.voice_name.trace_add("write", self.change_voice_name)

Expand Down Expand Up @@ -414,6 +429,7 @@ def get_language_codes():

@staticmethod
def get_voice_names(language_code="en-US", gender=None):
log.info(f'get_voice_names({language_code=}, {gender=})')
with models.Session(models.engine) as session:
all_voices = list(
session.execute(
Expand All @@ -426,6 +442,14 @@ def get_voice_names(language_code="en-US", gender=None):
if all_voices:
log.info(f"{len(all_voices)} voices found in database")
if gender:
out = []
for result in all_voices:
if result.ssml_gender.upper() == gender.upper():
out.append(result.name)
else:
log.info(f'{gender} != {result.ssml_gender}')
return out

return [
voice.name
for voice in all_voices
Expand Down Expand Up @@ -469,14 +493,14 @@ def get_tts(self):
kwargs = {
"language_code": self.language_code.get(),
"name": self.voice_name.get(),
"ssml_gender": self.ssml_gender.get(),
# "ssml_gender": self.ssml_gender.get(),
}

audio_config = texttospeech.AudioConfig(
speaking_rate=float(self.rate.get()), pitch=float(self.pitch.get())
)

log.debug("texttospeech.VoiceSelectionParams(%s)" % kwargs)
log.info("texttospeech.VoiceSelectionParams(%s)" % kwargs)
voice_params = texttospeech.VoiceSelectionParams(
**kwargs
# texttospeech.SsmlVoiceGender.NEUTRAL
Expand Down Expand Up @@ -521,7 +545,18 @@ def __init__(self, parent, selected_character, *args, **kwargs):
self.voice_name = tk.StringVar(value="")

self.parameters = ("voice_name",)
self.load_character(self.selected_character.get())
raw_name = self.selected_character.get()

character = self.load_character(raw_name)

# what is this characters gender (if it has one)?
npc_data = settings.get_npc_data(character.name)
gender = None
if npc_data:
if npc_data["gender"] == "GENDER_MALE":
gender = "male"
elif npc_data["gender"] == "GENDER_FEMALE":
gender = "female"

voice_frame = tk.Frame(self)
tk.Label(voice_frame, text="Voice", anchor="e").pack(
Expand All @@ -532,12 +567,13 @@ def __init__(self, parent, selected_character, *args, **kwargs):
voice_frame,
textvariable=self.voice_name,
)
all_voices = ElevenLabs.get_voice_names()
all_voices = ElevenLabs.get_voice_names(gender=gender)
log.info(f"Assinging all_voices to {all_voices}")
voice_combo["values"] = all_voices
voice_combo["state"] = "readonly"
voice_combo.pack(side="left", fill="x", expand=True)
voice_frame.pack(side="top", fill="x", expand=True)
self.voice_name.set(value=all_voices[0])

self.voice_name.trace_add("write", self.change_voice_name)

Expand Down Expand Up @@ -578,53 +614,67 @@ def change_voice_name(self, a, b, c):
@staticmethod
def get_voice_names(gender=None):
# cache these to the database, this is crude
return [
# I haven't even listened to all these, this is best guess
# from names. These are the current free-tier voices:
FEMALE = [
"Rachel : 21m00Tcm4TlvDq8ikWAM",
"Sarah : EXAVITQu4vr4xnSDxMaL",
"Emily : LcfcDJNUP1GQjkzn1xUU",
"Elli : MF3mGyEYCl7XYWbV9V6O",
"Dorothy : ThT5KcBeYPX3keUQqHPh",
"Charlotte : XB0fDUnXU5powFXDhCwa",
"Alice : Xb7hH8MSUJpSbSDYk0k2",
"Matilda : XrExE9yKIg1WjnnlVkGX",
"Gigi : jBpfuIE2acCO8z3wKNLl",
"Freya : jsCqWAovK2LkecY7zXl4",
"Grace : oWAxZDx7w5VEj9dCyTzz",
"Lily : pFZP5JQG7iQjIQuC4Bku",
"Serena : pMsXgVXv3BLzUgSXRplE",
"Nicole : piTKgcLEGmPE4e6mEKli",
"Glinda : z9fAnlkpzviPz146aGWa",
"Mimi : zrHiDhphv9ZnVXBqCLjz",
]
MALE = [
"Drew : 29vD33N1CtxCmqQRPOHJ",
"Clyde : 2EiwWnXFnvU5JabPnv8n",
"Paul : 5Q0t7uMcjvnagumLfvZi",
"Domi : AZnzlk1XvdvUeBnXmlld",
"Dave : CYw3kZ02Hs0563khs1Fj",
"Fin : D38z5RcWu1voky8WS1ja",
"Sarah : EXAVITQu4vr4xnSDxMaL",
"Antoni : ErXwobaYiN019PkySvjV",
"Thomas : GBv7mTt0atIp3Br8iCZE",
"Charlie : IKne3meq5aSn9XLyUdCD",
"George : JBFqnCBsd6RMkjVDRZzb",
"Emily : LcfcDJNUP1GQjkzn1xUU",
"Elli : MF3mGyEYCl7XYWbV9V6O",
"Callum : N2lVS1w4EtoT3dr4eOWO",
"Patrick : ODq5zmih8GrVes37Dizd",
"Harry : SOYHLrjzK2X1ezoPC6cr",
"Liam : TX3LPaxmHKxFdv7VOQHJ",
"Dorothy : ThT5KcBeYPX3keUQqHPh",
"Josh : TxGEqnHWrfWFTfGW9XjX",
"Arnold : VR6AewLTigWG4xSOukaG",
"Charlotte : XB0fDUnXU5powFXDhCwa",
"Alice : Xb7hH8MSUJpSbSDYk0k2",
"Matilda : XrExE9yKIg1WjnnlVkGX",
"James : ZQe5CZNOzWyzPSCn5a3c",
"Joseph : Zlb1dXrM653N07WRdFW3",
"Jeremy : bVMeCyTHy58xNoL34h3p",
"Michael : flq6f7yk4E4fJM5XTYuZ",
"Ethan : g5CIjZEefAph4nQFvHAz",
"Chris : iP95p4xoKVk53GoZ742B",
"Gigi : jBpfuIE2acCO8z3wKNLl",
"Freya : jsCqWAovK2LkecY7zXl4",
"Brian : nPczCjzI2devNBz1zQrb",
"Grace : oWAxZDx7w5VEj9dCyTzz",
"Daniel : onwK4e9ZLuTAKqWW03F9",
"Lily : pFZP5JQG7iQjIQuC4Bku",
"Serena : pMsXgVXv3BLzUgSXRplE",
"Adam : pNInz6obpgDQGcFmaJgB",
"Nicole : piTKgcLEGmPE4e6mEKli",
"Bill : pqHfZKP75CvOlQylNhV4",
"Jessie : t0jbNlBVZ17f02VDIeMI",
"Sam : yoZ06aMxZJJ28mfd3POQ",
"Glinda : z9fAnlkpzviPz146aGWa",
"Giovanni : zcAOhNBS3c14rBihAFp1",
"Mimi : zrHiDhphv9ZnVXBqCLjz",
]
]
if gender is None:
return FEMALE + MALE
elif gender.upper() == "FEMALE":
return FEMALE
elif gender.upper() == "MALE":
return MALE
else:
return FEMALE + MALE

#########
client = get_elevenlabs_client()
all_raw_voices = client.voices.get_all()

Expand All @@ -639,6 +689,7 @@ def get_voice_names(gender=None):

else:
log.info("%s?=%s" % (as_gender(voice.labels.get("gender")), gender))

log.info(all_voices)
return all_voices

Expand Down
48 changes: 42 additions & 6 deletions src/coh_npc_voices/models.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import enum
from datetime import datetime
import json
from sqlalchemy import Enum, DateTime, ForeignKey, Integer, String, create_engine, orm, select, TIMESTAMP
from sqlalchemy import DateTime, ForeignKey, Integer, String, create_engine, orm, select
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import Session
import logging
import sys
from typing import Optional
from sqlalchemy.orm import Mapped
from typing_extensions import Annotated
import settings

logging.basicConfig(
Expand Down Expand Up @@ -58,7 +56,7 @@ def category_str2int(instr):
return -1

def get_character(name, category, session=None):
log.info(f'get_character({name=}, {category=}, {session=})')
log.info(f'/-- models.get_character({name=}, {category=}, {session=})')
try:
category=int(category)
except ValueError:
Expand All @@ -75,17 +73,55 @@ def get_character(name, category, session=None):
)
)
else:
log.debug('Using existing session')
log.debug('|- Using existing session')
value = session.scalar(
select(Character).where(
Character.name==name,
Character.category==category
)
)

log.info(f'get_character() returning {value}')
if value is None:
log.info('|- Creating new character in database...')
# this is the first time we've gotten a message from this
# NPC, so they don't have a voice yet. We will default to
# the windows voice because it is free and no voice effects.
with Session(engine) as session:
value = Character(
name=name,
engine=settings.get_config_key(
'DEFAULT_ENGINE', settings.DEFAULT_ENGINE
),
category=category,
)
session.add(value)
session.commit()
session.refresh(value)

log.info(f'\\-- get_character() returning {value}')
return value


def update_character_last_spoke(character, session=None):
if session:
character = session.execute(
select(Character).where(
Character.id == character.id
)
).first()
character.last_up = datetime.datetime.now()
session.commit()
else:
with Session(engine) as session:
character = session.execute(
select(Character).where(
Character.id == character.id
)
).first()
character.last_up = datetime.datetime.now()
session.commit()


class Character(Base):
__tablename__ = "character"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
Expand Down
Loading

0 comments on commit 01d50f4

Please sign in to comment.