feat: add French language support and update config example

main
swanie98635 2 months ago
parent 118cf5216c
commit d04f4f8fc6

@ -19,8 +19,16 @@ class AudioHandler:
# Simple cleanup? # Simple cleanup?
if os.path.exists(filepath): if os.path.exists(filepath):
os.remove(filepath) os.remove(filepath)
# Resolve language code
lang_cfg = self.config.get('language', 'en')
lang_map = {
'en': 'en-US',
'fr': 'fr-FR'
}
lang_code = lang_map.get(lang_cfg, 'en-US')
cmd = self.tts_template.format(file=filepath, text=text) cmd = self.tts_template.format(file=filepath, text=text, lang=lang_code)
self.logger.info(f"Generating audio: {cmd}") self.logger.info(f"Generating audio: {cmd}")
try: try:

@ -37,7 +37,7 @@ def do_full_report(config):
sun_info = astro.get_astro_info(loc_info) sun_info = astro.get_astro_info(loc_info)
# Narrate # Narrate
narrator = Narrator() narrator = Narrator(config)
text = narrator.build_full_report(loc_info, conditions, forecast, alerts, sun_info) text = narrator.build_full_report(loc_info, conditions, forecast, alerts, sun_info)
logger.info(f"Report Text: {text}") logger.info(f"Report Text: {text}")
@ -54,7 +54,7 @@ def monitor_loop(config):
known_alerts = set() known_alerts = set()
loc_svc = LocationService(config) loc_svc = LocationService(config)
narrator = Narrator() narrator = Narrator(config)
handler = AudioHandler(config) handler = AudioHandler(config)
nodes = config.get('audio', {}).get('nodes', []) nodes = config.get('audio', {}).get('nodes', [])
prov_code = config.get('location', {}).get('provider') prov_code = config.get('location', {}).get('provider')

@ -1,79 +1,117 @@
from datetime import datetime from datetime import datetime
import pytz import pytz
from typing import List from typing import List, Dict
from .models import LocationInfo, CurrentConditions, WeatherForecast, WeatherAlert from .models import LocationInfo, CurrentConditions, WeatherForecast, WeatherAlert
class Narrator: class Narrator:
def __init__(self): STRINGS = {
pass 'en': {
'wind_fmt': ", with winds from the {dir} at {kph} kilometers per hour, or {mph} miles per hour",
'current_intro': "Current conditions for {city}, {region}.",
'temp_fmt': "The temperature is {temp_c} degrees celsius, {temp_f} degrees fahrenheit.",
'conditions_fmt': "Conditions are {desc}{wind}.",
'forecast_intro': "Here is the forecast. ",
'high_fmt': " with a high of {c} celsius, {f} fahrenheit",
'low_fmt': " with a low of {c} celsius, {f} fahrenheit",
'period_fmt': "{period}: {summary}{temp}. ",
'no_alerts': "There are no active weather alerts.",
'alerts_intro': "There are {count} active weather alerts. ",
'alert_item': "A {title} is in effect until {expires}. ",
'greeting': "Good day. The time is {time}.",
'intro_city': "This is the automated weather report for {city}.",
'alert_advise': "Please be advised: ",
'time_fmt': "%I %M %p"
},
'fr': {
'wind_fmt': ", avec des vents du {dir} à {kph} kilomètres à l'heure",
'current_intro': "Conditions actuelles pour {city}, {region}.",
'temp_fmt': "La température est de {temp_c} degrés Celsius, {temp_f} degrés Fahrenheit.",
'conditions_fmt': "Les conditions sont {desc}{wind}.",
'forecast_intro': "Voici les prévisions. ",
'high_fmt': " avec un maximum de {c} Celsius, {f} Fahrenheit",
'low_fmt': " avec un minimum de {c} Celsius, {f} Fahrenheit",
'period_fmt': "{period}: {summary}{temp}. ",
'no_alerts': "Il n'y a aucune alerte météo en vigueur.",
'alerts_intro': "Il y a {count} alertes météo en vigueur. ",
'alert_item': "Un {title} est en vigueur jusqu'à {expires}. ",
'greeting': "Bonjour. Il est {time}.",
'intro_city': "Ceci est le bulletin météo automatisé pour {city}.",
'alert_advise': "Veuillez noter : ",
'time_fmt': "%H heures %M"
}
}
def __init__(self, config: Dict):
self.lang = config.get('language', 'en')
self.s = self.STRINGS.get(self.lang, self.STRINGS['en'])
def _c_to_f(self, temp_c: float) -> int: def _c_to_f(self, temp_c: float) -> int:
return int((temp_c * 9/5) + 32) return int((temp_c * 9/5) + 32)
def _t(self, key, **kwargs):
tpl = self.s.get(key, "")
return tpl.format(**kwargs)
def announce_conditions(self, loc: LocationInfo, current: CurrentConditions) -> str: def announce_conditions(self, loc: LocationInfo, current: CurrentConditions) -> str:
wind = "" wind = ""
if current.wind_speed and current.wind_speed > 5: if current.wind_speed and current.wind_speed > 5:
# Describe wind speed in km/h and mph
mph = int(current.wind_speed * 0.621371) mph = int(current.wind_speed * 0.621371)
wind = f", with winds from the {current.wind_direction} at {int(current.wind_speed)} kilometers per hour, or {mph} miles per hour" wind = self._t('wind_fmt', dir=current.wind_direction, kph=int(current.wind_speed), mph=mph)
intro = self._t('current_intro', city=loc.city, region=loc.region)
temp_str = self._t('temp_fmt', temp_c=int(current.temperature), temp_f=self._c_to_f(current.temperature))
cond_str = self._t('conditions_fmt', desc=current.description, wind=wind)
return ( return f"{intro} {temp_str} {cond_str}"
f"Current conditions for {loc.city}, {loc.region}. "
f"The temperature is {int(current.temperature)} degrees celsius, {self._c_to_f(current.temperature)} degrees fahrenheit. "
f"Conditions are {current.description}{wind}."
)
def announce_forecast(self, forecasts: List[WeatherForecast]) -> str: def announce_forecast(self, forecasts: List[WeatherForecast]) -> str:
if not forecasts: if not forecasts:
return "" return ""
text = "Here is the forecast. " text = self._t('forecast_intro')
for f in forecasts[:3]: # Read first 3 periods for f in forecasts[:3]:
temp = "" temp = ""
if f.high_temp is not None: if f.high_temp is not None:
temp = f" with a high of {int(f.high_temp)} celsius, {self._c_to_f(f.high_temp)} fahrenheit" temp = self._t('high_fmt', c=int(f.high_temp), f=self._c_to_f(f.high_temp))
elif f.low_temp is not None: elif f.low_temp is not None:
temp = f" with a low of {int(f.low_temp)} celsius, {self._c_to_f(f.low_temp)} fahrenheit" temp = self._t('low_fmt', c=int(f.low_temp), f=self._c_to_f(f.low_temp))
text += f"{f.period_name}: {f.summary}{temp}. " text += self._t('period_fmt', period=f.period_name, summary=f.summary, temp=temp)
return text return text
def announce_alerts(self, alerts: List[WeatherAlert]) -> str: def announce_alerts(self, alerts: List[WeatherAlert]) -> str:
if not alerts: if not alerts:
return "There are no active weather alerts." return self._t('no_alerts')
text = f"There are {len(alerts)} active weather alerts. " text = self._t('alerts_intro', count=len(alerts))
for a in alerts: for a in alerts:
# "A Severe Thunderstorm Warning is in effect until 5 PM." # Need locale specific time format?
expires_str = a.expires.strftime("%I %M %p") # Alerts usually have specific expiration.
text += f"A {a.title} is in effect until {expires_str}. " expires_str = a.expires.strftime(self.s.get('time_fmt', "%I %M %p"))
text += self._t('alert_item', title=a.title, expires=expires_str)
return text return text
def build_full_report(self, loc: LocationInfo, current: CurrentConditions, forecast: List[WeatherForecast], alerts: List[WeatherAlert], sun_info: str = "") -> str: def build_full_report(self, loc: LocationInfo, current: CurrentConditions, forecast: List[WeatherForecast], alerts: List[WeatherAlert], sun_info: str = "") -> str:
parts = [] parts = []
# Localize time
try: try:
tz = pytz.timezone(loc.timezone) tz = pytz.timezone(loc.timezone)
now = datetime.now(tz) now = datetime.now(tz)
except Exception: except Exception:
now = datetime.now() now = datetime.now()
# Time string: "10 30 PM" time_fmt = self.s.get('time_fmt', "%I %M %p")
now_str = now.strftime("%I %M %p") now_str = now.strftime(time_fmt)
# Remove leading zero from hour if desired, but TTS usually handles "09" fine. if self.lang == 'en' and now_str.startswith("0"):
# For cleaner TTS:
if now_str.startswith("0"):
now_str = now_str[1:] now_str = now_str[1:]
parts.append(f"Good day. The time is {now_str}.") parts.append(self._t('greeting', time=now_str))
parts.append(f"This is the automated weather report for {loc.city}.") parts.append(self._t('intro_city', city=loc.city))
if alerts: if alerts:
parts.append("Please be advised: " + self.announce_alerts(alerts)) parts.append(self._t('alert_advise') + self.announce_alerts(alerts))
parts.append(self.announce_conditions(loc, current)) parts.append(self.announce_conditions(loc, current))
parts.append(self.announce_forecast(forecast)) parts.append(self.announce_forecast(forecast))

@ -8,10 +8,13 @@ class ECProvider(WeatherProvider):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.points_cache = {} self.points_cache = {}
self.extra_zones = kwargs.get('alerts', {}).get('extra_zones', []) self.extra_zones = kwargs.get('alerts', {}).get('extra_zones', [])
# Map config language code (fr/en) to ECWeather expected parameter (french/english)
self.language = 'french' if kwargs.get('language') == 'fr' else 'english'
def _get_ec_data(self, lat, lon): def _get_ec_data(self, lat, lon):
# ECWeather auto-selects station based on lat/lon # ECWeather auto-selects station based on lat/lon
ec = ECWeather(coordinates=(lat, lon)) ec = ECWeather(coordinates=(lat, lon), language=self.language)
ec.update() ec.update()
return ec return ec
@ -62,7 +65,7 @@ class ECProvider(WeatherProvider):
for zone_id in self.extra_zones: for zone_id in self.extra_zones:
if "/" in zone_id: # Basic check if it looks like EC station ID if "/" in zone_id: # Basic check if it looks like EC station ID
try: try:
ec_objects.append(ECWeather(station_id=zone_id)) ec_objects.append(ECWeather(station_id=zone_id, language=self.language))
except Exception: except Exception:
pass pass

@ -4,10 +4,14 @@ location:
# longitude: -75.6972 # longitude: -75.6972
# provider: CA # Optional: force US or CA # provider: CA # Optional: force US or CA
language: en # Options: en, fr
voice: voice:
# Example utilizing pico2wave (sudo apt install libttspico-utils) # Example utilizing pico2wave (sudo apt install libttspico-utils)
tts_command: 'pico2wave -w /tmp/temp.wav "{text}" && sox /tmp/temp.wav -r 8000 -c 1 {file}' # {lang} is replaced by 'en-US' or 'fr-FR'
tts_command: 'pico2wave -l {lang} -w /tmp/temp.wav "{text}" && sox /tmp/temp.wav -r 8000 -c 1 {file}'
audio:
nodes: nodes:
- "YOUR_NODE_NUMBER_HERE" - "YOUR_NODE_NUMBER_HERE"

Loading…
Cancel
Save

Powered by TurnKey Linux.