Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Sonos.indigoPlugin/Contents/Server Plugin/Actions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@
<List>
<Option value="FILE">File</Option>
<Option value="POLLY">Amazon Polly</Option>
<Option value="ELEVENLABS">ElevenLabs</Option>
<Option value="APPLE">Apple Speech</Option>
<Option value="TTS">Google Text To Speech</Option>
<Option value="IVONA">IVONA Text To Speech</Option>
Expand Down Expand Up @@ -661,6 +662,20 @@
<List class="self" method="getPollyVoices" dynamicReload="yes"/>
</Field>

<!-- ElevenLabs TTS message to read -->
<Field id="ELEVENLABS_setting" type="textfield" visibleBindingId="ttsORfile" visibleBindingValue="ELEVENLABS">
<Label>Message:</Label>
</Field>
<!-- Voice selection dropdown -->
<Field id="ELEVENLABS_voice" type="menu" visibleBindingId="ttsORfile" visibleBindingValue="ELEVENLABS">
<Label>Voice:</Label>
<List class="self" method="getElevenLabsVoices" dynamicReload="yes"/>
</Field>
<!-- ElevenLabs language code input -->
<Field id="ELEVENLABS_language" type="textfield" visibleBindingId="ttsORfile" visibleBindingValue="ELEVENLABS">
<Label>Language code (e.g. "en"):</Label>
</Field>

<Field id="APPLE_setting" type="textfield" visibleBindingId="ttsORfile" visibleBindingValue="APPLE">
<Label>Message:</Label>
</Field>
Expand Down
17 changes: 15 additions & 2 deletions Sonos.indigoPlugin/Contents/Server Plugin/PluginConfig.xml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,19 @@
</Field>
<Field id="simpleSeparator7" type="separator"/>

<!-- ElevenLabs TTS options -->
<Field id="ElevenLabsNote" type="label" fontColor="darkgray" fontSize="small" alignWithControl="false">
<Label>Check this box if you have an account with ElevenLabs and wish to use it for group announcements. [https://elevenlabs.io]</Label>
</Field>
<Field type="checkbox" id="ElevenLabs" tooltip="ElevenLabs account?">
<Label>ElevenLabs:</Label>
</Field>
<!-- ElevenLabs API key - visible only if main option checked -->
<Field type="textfield" id="ElevenLabsAPIKey" tooltip="ElevenLabs API key" visibleBindingId="ElevenLabs" visibleBindingValue="true">
<Label>API key:</Label>
</Field>
<Field id="simpleSeparator8" type="separator"/>

<Field id="MSTranslateNote" type="label" fontColor="darkgray" fontSize="small" alignWithControl="false">
<Label>Check this box if you have registered a user account with Microsoft Translate and wish to use it for group announcements. [https://www.microsoft.com/en-us/translator/getstarted.aspx]</Label>
</Field>
Expand All @@ -159,7 +172,7 @@
<Field type="textfield" id="MSTranslateClientSecret" tooltip="MS Translate Client Secret" visibleBindingId="MSTranslate" visibleBindingValue="true">
<Label>Client Secret:</Label>
</Field>
<Field id="simpleSeparator8" type="separator"/>
<Field id="simpleSeparator9" type="separator"/>

<Field id="UpdaterEmailsEnabledNote" type="label" fontColor="darkgray" fontSize="small" alignWithControl="false">
<Label>Check this box if you want to enable version update notifications. Email sending must also be configured in Indigo's preferences.</Label>
Expand All @@ -173,7 +186,7 @@
<Field id="updaterEmail" type="textfield" visibleBindingId="updaterEmailsEnabled" visibleBindingValue="true">
<Label>Email:</Label>
</Field>
<Field id="simpleSeparator9" type="separator"/>
<Field id="simpleSeparator10" type="separator"/>

<Field id="showDebugInLogNote" type="label" fontColor="darkgray" fontSize="small" alignWithControl="false">
<Label>Check the boxes below for additional information to log to the Indigo Event Log. Basic Debugging must be checked for any other deubgging to function.</Label>
Expand Down
103 changes: 103 additions & 0 deletions Sonos.indigoPlugin/Contents/Server Plugin/Sonos.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,17 @@
IVONAVoices = []
PollyVoices = []
NSVoices = []
ElevenLabsVoices = []


class ElevenLabsVoice:
def __init__(self, voice_id, name, gender):
self.id = voice_id
self.name = name
self.gender = gender

def __repr__(self):
return f"ElevenLabsVoice(id='{self.id}', name='{self.name}', gender='{self.gender}')"


class PA():
Expand Down Expand Up @@ -335,6 +346,8 @@ def __init__(self, plugin, pluginPrefs):
self.MSTranslate = None
self.MSTranslateClientID = None
self.MSTranslateClientSecret = None
self.ElevenLabs = None
self.ElevenLabsAPIKey = None
self.ttsORfile = None
self.deviceList = []
self.ZonePlayers = []
Expand Down Expand Up @@ -897,6 +910,32 @@ def IVONAVoices(self):
except Exception as exception_error:
self.exception_handler(exception_error, True) # Log error and display failing statement

def ElevenLabsVoices(self):
self.logger.debug("Will fetch ElevenLabs voices...")
try:
global ElevenLabsVoices
voice_count = 0
headers = {
"xi-api-key": self.ElevenLabsAPIKey
}
response = requests.get("https://api.elevenlabs.io/v1/voices", headers=headers)
if response.status_code == 200:
for voice in response.json()['voices']:
# If gender is not available, it's unknown
gender = voice.get("labels", {}).get("gender", "unknown")
voice_obj = ElevenLabsVoice(
voice["voice_id"],
voice["name"],
gender
)
ElevenLabsVoices.append(voice_obj)
self.logger.debug(f"ElevenLabs voice: {voice_obj}")
voice_count = voice_count + 1
self.logger.info(f"Loaded ElevenLabs voices... [{voice_count}]")
except Exception as exception_error:
self.exception_handler(exception_error, True) # Log error and display failing statement


def PollyVoices(self):
try:
global PollyVoices
Expand Down Expand Up @@ -2575,6 +2614,42 @@ def actionAnnouncement(self, pluginAction, action):
s_announcement = "announcement.mp3"
tts_delay = 0.5

elif pluginAction.props.get("ttsORfile") == "ELEVENLABS":
# Get the message to read
announcement = self.plugin.substitute(pluginAction.props.get("ELEVENLABS_setting"), validateOnly=False)
# Get the language code (needed for API)
language_code = self.plugin.substitute(pluginAction.props.get("ELEVENLABS_language"), validateOnly=False)
# Find the voice id that the user selected
voice_id: str = pluginAction.props.get("ELEVENLABS_voice")
# POST headers and data
headers = {
"Accept": "audio/mpeg",
"Content-Type": "application/json",
"xi-api-key": self.ElevenLabsAPIKey
}
data = {
"text": announcement,
"language_code": language_code,
"model_id": "eleven_turbo_v2_5",
"voice_settings": {
"stability": 0.5,
"similarity_boost": 0.5
}
}
url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}"
response = requests.post(url, json=data, headers=headers)
if response.status_code == 200:
CHUNK_SIZE = 1024
with open('announcement.mp3', 'wb') as f:
for chunk in response.iter_content(chunk_size=CHUNK_SIZE):
if chunk:
f.write(chunk)
s_announcement = "announcement.mp3"
tts_delay = 0.5
else:
self.logger.error("Unable to synthesize speech at ElevenLabs")


elif pluginAction.props.get("ttsORfile") == "APPLE":
announcement = self.plugin.substitute(pluginAction.props.get("APPLE_setting"), validateOnly=False)
sp = NSSpeechSynthesizer.alloc().initWithVoice_(pluginAction.props.get("APPLE_voice"))
Expand Down Expand Up @@ -3069,6 +3144,20 @@ def closedPrefsConfigUi(self, valuesDict, userCancelled):
except Exception as exception_error:
self.logger.error(f"[{time.asctime()}] Could not retrieve Polly parameters.")


# Setup ElevenLabs option if available
try:
prefs = self.plugin.pluginPrefs
if (self.ElevenLabs != prefs['ElevenLabs'] or
self.ElevenLabsAPIKey != prefs['ElevenLabsAPIKey']):
self.ElevenLabs = prefs['ElevenLabs']
if self.ElevenLabs:
self.ElevenLabsAPIKey = prefs['ElevenLabsAPIKey']
self.ElevenLabsVoices()
except Exception:
self.logger.error(f"[{time.asctime()}] Could not retrieve ElevenLabs parameters.")


try:
if (self.MSTranslate != self.plugin.pluginPrefs['MSTranslate']) or \
(self.MSTranslateClientID != self.plugin.pluginPrefs['MSTranslateClientID']) or \
Expand Down Expand Up @@ -3254,6 +3343,20 @@ def getPollyVoices(self, filter=""):
except Exception as exception_error:
self.exception_handler(exception_error, True) # Log error and display failing statement

def getElevenLabsVoices(self, filter=""):
try:
voice_list = []
for voice in ElevenLabsVoices:
voice_info = (voice.id, voice.name)
voice_list.append(voice_info)
# Sort the displayed voice list by speaker name
voice_list.sort(key=lambda x: x[1])

return voice_list

except Exception as exception_error:
self.exception_handler(exception_error, True)

def getAppleVoices(self, filter=""):
try:
array = []
Expand Down
3 changes: 3 additions & 0 deletions Sonos.indigoPlugin/Contents/Server Plugin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,9 @@ def getIVONAVoices(self, filter="", valuesDict=None, typeId="", targetId=0):
def getPollyVoices(self, filter="", valuesDict=None, typeId="", targetId=0):
return self.Sonos.getPollyVoices()

def getElevenLabsVoices(self, filter="", valuesDict=None, typeId="", targetId=0):
return self.Sonos.getElevenLabsVoices()

def getAppleVoices(self, filter="", valuesDict=None, typeId="", targetId=0):
return self.Sonos.getAppleVoices()

Expand Down