Skip to content

Commit

Permalink
Allow speech-synthesis to be hosted externally
Browse files Browse the repository at this point in the history
  • Loading branch information
dormant-user committed Dec 15, 2024
1 parent c8ca400 commit 11ce3cf
Show file tree
Hide file tree
Showing 13 changed files with 115 additions and 224 deletions.
12 changes: 8 additions & 4 deletions docs/genindex.html
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ <h2 id="C">C</h2>
<li><a href="index.html#jarvis.modules.models.classes.BackgroundTask.check_empty_string">check_empty_string() (jarvis.modules.models.classes.BackgroundTask class method)</a>
</li>
<li><a href="index.html#jarvis.modules.audio.speech_synthesis.check_existing">check_existing() (in module jarvis.modules.audio.speech_synthesis)</a>
</li>
<li><a href="index.html#jarvis.modules.audio.speech_synthesis.check_external">check_external() (in module jarvis.modules.audio.speech_synthesis)</a>
</li>
<li><a href="index.html#jarvis.modules.models.classes.BackgroundTask.check_hours_format">check_hours_format() (jarvis.modules.models.classes.BackgroundTask class method)</a>
</li>
Expand Down Expand Up @@ -1038,7 +1040,7 @@ <h2 id="H">H</h2>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="index.html#jarvis.executors.static_responses.hi">hi() (in module jarvis.executors.static_responses)</a>
</li>
<li><a href="index.html#jarvis.modules.models.enums.SSQuality.High_Quality">High_Quality (jarvis.modules.models.enums.SSQuality attribute)</a>
<li><a href="index.html#jarvis.modules.models.enums.SSQuality.High">High (jarvis.modules.models.enums.SSQuality attribute)</a>
</li>
<li><a href="index.html#jarvis.modules.models.classes.EnvConfig.home">home (jarvis.modules.models.classes.EnvConfig attribute)</a>
</li>
Expand Down Expand Up @@ -2121,7 +2123,7 @@ <h2 id="L">L</h2>
</li>
<li><a href="index.html#jarvis.modules.models.classes.Settings.logical_cores">logical_cores (jarvis.modules.models.classes.Settings attribute)</a>
</li>
<li><a href="index.html#jarvis.modules.models.enums.SSQuality.Low_Quality">Low_Quality (jarvis.modules.models.enums.SSQuality attribute)</a>
<li><a href="index.html#jarvis.modules.models.enums.SSQuality.Low">Low (jarvis.modules.models.enums.SSQuality attribute)</a>
</li>
<li><a href="index.html#jarvis.executors.lights_squire.lumen">lumen() (in module jarvis.executors.lights_squire)</a>
</li>
Expand Down Expand Up @@ -2159,7 +2161,7 @@ <h2 id="M">M</h2>
<li><a href="index.html#jarvis.modules.dictionary.dictionary.meaning">(in module jarvis.modules.dictionary.dictionary)</a>
</li>
</ul></li>
<li><a href="index.html#jarvis.modules.models.enums.SSQuality.Medium_Quality">Medium_Quality (jarvis.modules.models.enums.SSQuality attribute)</a>
<li><a href="index.html#jarvis.modules.models.enums.SSQuality.Medium">Medium (jarvis.modules.models.enums.SSQuality attribute)</a>
</li>
<li><a href="index.html#jarvis.modules.meetings.ics_meetings.meetings">meetings() (in module jarvis.modules.meetings.ics_meetings)</a>
</li>
Expand Down Expand Up @@ -3005,10 +3007,12 @@ <h2 id="S">S</h2>
<li><a href="index.html#jarvis.executors.volume.speaker_volume">speaker_volume() (in module jarvis.executors.volume)</a>
</li>
<li><a href="index.html#jarvis.modules.models.classes.EnvConfig.speech_rate">speech_rate (jarvis.modules.models.classes.EnvConfig attribute)</a>
</li>
<li><a href="index.html#jarvis.api.routers.speech_synthesis.speech_synthesis">speech_synthesis() (in module jarvis.api.routers.speech_synthesis)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="index.html#jarvis.api.routers.speech_synthesis.speech_synthesis">speech_synthesis() (in module jarvis.api.routers.speech_synthesis)</a>
<li><a href="index.html#jarvis.modules.models.classes.EnvConfig.speech_synthesis_api">speech_synthesis_api (jarvis.modules.models.classes.EnvConfig attribute)</a>
</li>
<li><a href="index.html#jarvis.modules.audio.speech_synthesis.speech_synthesis_api">speech_synthesis_api() (in module jarvis.modules.audio.speech_synthesis)</a>
</li>
Expand Down
139 changes: 31 additions & 108 deletions docs/index.html

Large diffs are not rendered by default.

Binary file modified docs/objects.inv
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/searchindex.js

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions jarvis/api/routers/speech_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from http import HTTPStatus
from json import JSONDecodeError
from threading import Thread
from urllib.parse import urljoin

import requests
from fastapi import APIRouter
Expand All @@ -28,11 +29,13 @@ async def speech_synthesis_voices():
- 200: If call to speech synthesis endpoint was successful.
- 500: If call to speech synthesis fails.
"""
if models.env.speech_synthesis_api:
url = urljoin(str(models.env.speech_synthesis_api), "/api/voices")
else:
# noinspection HttpUrlsUsage
url = f"http://{models.env.speech_synthesis_host}:{models.env.speech_synthesis_port}/api/voices"
try:
response = requests.get(
url=f"http://{models.env.speech_synthesis_host}:{models.env.speech_synthesis_port}/api/voices",
timeout=3,
)
response = requests.get(url=url, timeout=3)
except EgressErrors as error:
logger.error(error)
raise APIResponse(
Expand Down
4 changes: 2 additions & 2 deletions jarvis/executors/car.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import time
from datetime import datetime
from datetime import UTC, datetime

# noinspection PyProtectedMember
from multiprocessing.context import TimeoutError as ThreadTimeoutError
Expand Down Expand Up @@ -378,7 +378,7 @@ def report(
str:
Response to the user.
"""
default_dt_string = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S+0000")
default_dt_string = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%S+0000")
report_time = convert_dt_report(
dt_string=status_data.get("lastUpdatedTime", default_dt_string)
)
Expand Down
90 changes: 15 additions & 75 deletions jarvis/executors/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ def get_contacts() -> Dict[str, Dict[str, str]] | DefaultDict[str, Dict[str, str


def get_frequent() -> Dict[str, int]:
"""Support getting frequently used keywords' mapping file."""
"""Retrieve frequently used keywords' mapping file."""
return _loader(models.fileio.frequent, default={})


def put_frequent(data: Dict[str, int]) -> None:
"""Support writing frequently used keywords' mapping file.
"""Writes frequently used keywords into a mapping file.
Args:
data: Takes the mapping dictionary as an argument.
Expand All @@ -96,12 +96,7 @@ def get_location() -> DefaultDict[str, Dict | float | bool]:


def get_secure_send() -> Dict[str, Dict[str, Any]]:
"""Get existing secure string information from the mapping file.
Returns:
Dict[str, Dict[str, Any]]:
Dictionary of secure send data.
"""
"""Get existing secure-send information from the mapping file."""
return _loader(models.fileio.secure_send, default={})


Expand Down Expand Up @@ -136,22 +131,12 @@ def put_secure_send(data: Dict[str, Dict[str, Any]]) -> None:


def get_custom_conditions() -> Dict[str, Dict[str, str]]:
"""Custom conditions to map specific keywords to one or many functions.
Returns:
Dict[str, Dict[str, str]]:
A unique key value pair, of custom phrase as key and an embedded dict of function name and phrase.
"""
"""Custom conditions to map specific keywords to one or many functions."""
return _loader(models.fileio.conditions)


def get_restrictions() -> List[str]:
"""Function level restrictions to restrict certain keywords via offline communicator.
Returns:
List[str]:
A list of function names that has to be restricted.
"""
"""Function level restrictions to restrict certain keywords via offline communicator."""
return _loader(models.fileio.restrictions, default=[])


Expand All @@ -165,12 +150,7 @@ def put_restrictions(restrictions: List[str]) -> None:


def get_gpt_data() -> List[Dict[str, str]]:
"""Get history from Jarvis -> Ollama conversation.
Returns:
List[Dict[str, str]]:
A list of dictionaries with request and response key-value pairs.
"""
"""Get history from Jarvis -> Ollama conversation."""
return _loader(models.fileio.gpt_data)


Expand All @@ -184,12 +164,7 @@ def put_gpt_data(data: List[Dict[str, str]]) -> None:


def get_automation() -> Dict[str, List[Dict[str, str | bool]] | Dict[str, str | bool]]:
"""Load automation data from feed file.
Returns:
Dict[str, List[Dict[str, str | bool]] | Dict[str, str | bool]]:
Returns the automation data in the feed file.
"""
"""Load automation data from feed file."""
return _loader(models.fileio.automation, default={})


Expand All @@ -216,12 +191,7 @@ def put_automation(


def get_smart_devices() -> dict | bool | None:
"""Load smart devices' data from feed file.
Returns:
dict | bool | None:
Returns the smart devices' data in the feed file.
"""
"""Load smart devices' data from feed file."""
# fixme: Change the logic to NOT look for False specifically
try:
with open(models.fileio.smart_devices) as file:
Expand All @@ -248,22 +218,12 @@ def put_smart_devices(data: dict) -> None:


def get_processes() -> Dict[str, List[int | List[str]]]:
"""Get the processes' mapping from stored map file.
Returns:
Dict[str, List[int | List[str]]]:
Processes' mapping data.
"""
"""Get the processes' mapping from stored map file."""
return _loader(models.fileio.processes, default={})


def get_reminders() -> List[Dict[str, str]]:
"""Get all reminders stored.
Returns:
List[Dict[str, str]]:
Returns a list of dictionary of information for stored reminders.
"""
"""Get all reminders stored."""
return _loader(models.fileio.reminders, default=[])


Expand All @@ -277,12 +237,7 @@ def put_reminders(data: List[Dict[str, str]]) -> None:


def get_alarms() -> List[Dict[str, str | bool]]:
"""Get all alarms stored.
Returns:
Dict[str, str | bool]:
Returns a dictionary of information for stored alarms.
"""
"""Get all alarms stored."""
return _loader(models.fileio.alarms, default=[])


Expand All @@ -296,12 +251,7 @@ def put_alarms(data: List[Dict[str, str | bool]]) -> None:


def get_recognizer() -> classes.RecognizerSettings:
"""Get the stored settings for speech recognition.
Returns:
RecognizerSettings:
Returns the parsed recognizer settings or default.
"""
"""Get the stored settings for speech recognition."""
try:
rec_data = _loader(models.fileio.recognizer, default={})
return classes.RecognizerSettings(**rec_data)
Expand All @@ -311,12 +261,7 @@ def get_recognizer() -> classes.RecognizerSettings:


def get_crontab() -> List[str]:
"""Get the stored crontab settings.
Returns:
List[str]:
List of crontab entries.
"""
"""Get list of stored crontab settings."""
try:
data = _loader(models.fileio.crontab, default=[])
assert isinstance(data, list)
Expand All @@ -329,20 +274,15 @@ def get_crontab() -> List[str]:


def get_ip_info() -> Dict[str, Any]:
"""Get IP information from a stored yaml file.
Returns:
Dict[str, Any]:
Returns the public IP info.
"""
"""Get IP information from a stored yaml file."""
return _loader(models.fileio.ip_info, default={})


def put_ip_info(data: Dict[str, Any]) -> None:
"""Store IP address information in a mapping file.
Args:
data: Data to store.
data: IP information to be stored.
"""
data["timestamp"] = int(time.time())
if not data.get("loc") and data.get("lat") and data.get("lon"):
Expand Down
16 changes: 8 additions & 8 deletions jarvis/modules/audio/speaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import sys
from datetime import datetime
from threading import Thread
from urllib.parse import urljoin

import pynotification
import requests
Expand Down Expand Up @@ -54,8 +55,13 @@ def speech_synthesizer(
text = text.replace("\N{DEGREE SIGN}F", " degrees fahrenheit")
text = text.replace("\N{DEGREE SIGN}C", " degrees celsius")
try:
if models.env.speech_synthesis_api:
url = urljoin(str(models.env.speech_synthesis_api), "/api/tts")
else:
# noinspection HttpUrlsUsage
url = f"http://{models.env.speech_synthesis_host}:{models.env.speech_synthesis_port}/api/tts"
response = requests.post(
url=f"http://{models.env.speech_synthesis_host}:{models.env.speech_synthesis_port}/api/tts",
url=url,
headers={"Content-Type": "text/plain"},
params={"voice": voice, "vocoder": quality},
data=text,
Expand All @@ -68,13 +74,7 @@ def speech_synthesizer(
file.write(response.content)
file.flush()
return True
logger.error(
"{code}::http://{host}:{port}/api/tts".format(
code=response.status_code,
host=models.env.speech_synthesis_host,
port=models.env.speech_synthesis_port,
)
)
logger.error("%s::%s", response.status_code, url)
return False
except UnicodeError as error:
logger.error(error)
Expand Down
19 changes: 19 additions & 0 deletions jarvis/modules/audio/speech_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ def find_pid_by_port(port: int) -> int:
logger.error(error)


def check_external() -> bool:
"""Checks for speech synthesis API running externally.
Returns:
bool:
A boolean flag to indicate API usability.
"""
if models.env.speech_synthesis_api:
try:
response = requests.get(models.env.speech_synthesis_api)
response.raise_for_status()
return True
except EgressErrors as error:
logger.error(error)


def check_existing() -> bool:
"""Checks for existing connection.
Expand Down Expand Up @@ -245,6 +261,9 @@ def speech_synthesis_api() -> None:
except DockerException as error:
logger.critical(error.__str__())
return
if check_external():
logger.info("Speech synthesis API is running externally. Connection validated.")
return
if check_existing(): # Test call to speech synthesis API successful
# Map existing API session with docker
if container_id := run_existing_container(docker_client, True):
Expand Down
3 changes: 2 additions & 1 deletion jarvis/modules/models/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,10 @@ class EnvConfig(BaseSettings):
# Speech synthesis config (disabled for speaker by default)
speech_synthesis_timeout: int = 0
speech_synthesis_voice: str = "en-us_northern_english_male-glow_tts"
speech_synthesis_quality: enums.SSQuality = enums.SSQuality.Medium_Quality
speech_synthesis_quality: enums.SSQuality = enums.SSQuality.Medium
speech_synthesis_host: str = socket.gethostbyname("localhost")
speech_synthesis_port: PositiveInt = 5002
speech_synthesis_api: HttpUrl | None = None

# Background tasks
weather_alert: str | datetime | None = None
Expand Down
6 changes: 3 additions & 3 deletions jarvis/modules/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,6 @@ class SSQuality(StrEnum):
"""

High_Quality = "high"
Medium_Quality = "medium"
Low_Quality = "low"
High = "high"
Medium = "medium"
Low = "low"
Loading

0 comments on commit 11ce3cf

Please sign in to comment.