rewrite audio announcements feature to be simpler to setup

This commit is contained in:
Kaaninchen
2026-08-18 20:20:30 +02:00
parent 7a3902b7d3
commit 66ec499c27
7 changed files with 28 additions and 94 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

+5 -30
View File
@@ -187,6 +187,8 @@ I highly recommend keeping `"OTHER"` blacklisted, if the API doesn't know what t
- `timezone` is timezone in the IANA timezone format. You can [look it up here](https://www.addevent.com/c/documentation/tools/time-zone-lookup)
##### announcements
##### `text_announcements:`
The bot can send a text announcement in the voice chat at the start/end of a trip.
At the end of a trip, it would send this embed:
@@ -196,37 +198,10 @@ It will also send the `/info` embed at the start of a new connection with inform
To reduce spam, the bot will only send announcements if someone is in the voice chat
##### voice
⚠️ Requires `announcements` to be set to enabled
The bot can join the voice chat, play an audio file, and leave at various points of your trip. You have to have [FFmpeg](https://www.ffmpeg.org/) installed for this to work.
Place the audio file of your desired station in [src/data/announcements](src/data/announcements/). Then, define the stations name with the name of the audio file in either `end_stations` or `stops`. The path will be autocompleted to [src/data/announcements](src/data/announcements/). The station name has to be EXACT, if you're unsure then [get the name through the helper script](#stations)
`end_stations` is for audio files that should play at the end of your trip, and `stops` is for audio files that should play while the train is passing through your desired station. If you set the name of a station to `general`, then the bot will always play that file before the trip ends/a new stop has been reached.
I'm hoping that I didn't explain this too complicated. Here's an example to visualize this:
![example_files](.github/voice_announcements_visualized.png)
```json
"announcements": {
"enabled": true,
"voice": [
{
"enabled": true,
"end_stations": {
"general": "general.aac",
"Hannover Hbf": "hannover.aac"
},
"stops": {
"Amsterdam, Noorderpark": "noorderpark.aac"
}
}
]
},
```
##### `voice_announcements:`
The bot can join the voice chat, play an audio file, and disconnect from the voice chat, at various points of your trip. You have to have [FFmpeg](https://www.ffmpeg.org/) installed for this to work.
Place the audio file of your desired station in [src/data/announcements](src/data/announcements/) with the EXACT name of the station. The bot will automatically check if an audio file with the stations name exists, and if it does, play it.
#### http
- `"user_agent"`: The user agent of the bot for the API. If you don't know what that is, then you shouldn't have to change that. Even if you do, you still probably don't have to
+2 -11
View File
@@ -24,17 +24,8 @@
"timezone": "Europe/Berlin"
},
"announcements": {
"enabled": true,
"voice": [
{
"enabled": false,
"end_stations": {
"general": "general.aac",
},
"stops": {
}
}
]
"text_announcements": true,
"voice_announcements": false
},
"http": {
"user_agent": "Gleiswechsel-Discord-Bot"
+1 -1
View File
@@ -243,7 +243,7 @@ def get_trip_details(random_connection: dict | None) -> dict | None:
arrival_dt = parse_iso(end_time)
departure_dt = parse_iso(start_time)
train_name = get_train_name(display_name, mode)
train_name = get_train_name(display_name, mode) # used by lang
if train_from == from_station:
long_name = long_name_lang.train_from()
else:
+3 -12
View File
@@ -21,16 +21,10 @@ class ConnectionsConfig:
max_wait_time: Optional[int] = None
max_duration: Optional[int] = None
@dataclass
class VoiceAnnouncementConfig:
enabled: bool
end_stations: dict[str, str]
stops: dict[str, str]
@dataclass
class AnnouncementConfig:
enabled: bool
voice: list[VoiceAnnouncementConfig]
text_announcements: bool
voice_announcements: bool
@dataclass
class HttpConfig:
@@ -50,10 +44,7 @@ def _load_config() -> Config:
return Config(
discord=DiscordConfig(**raw["discord"]),
connections=ConnectionsConfig(**raw["connections"]),
announcements=AnnouncementConfig(
enabled=raw["announcements"]["enabled"],
voice=[VoiceAnnouncementConfig(**v) for v in raw["announcements"]["voice"]],
),
announcements=AnnouncementConfig(**raw["announcements"]),
http=HttpConfig(**raw["http"]),
)
+10 -16
View File
@@ -39,30 +39,23 @@ async def rename_vc(bot: discord.Bot, voice_channel, from_scheduler: bool = Fals
formatting = channel_formatting(mode)
await voice_channel.edit(name=f"{formatting}{long_name}")
await voice_channel.set_status(None)
start_next_stop_updates(bot, voice_channel)
start_next_stop_updates(voice_channel)
logger(f"Updated channel name!")
await announcer("transfer", voice_channel)
_scheduled_task = asyncio.create_task(_schedule_next_transfer(bot, trip["arrival_dt"], voice_channel, trip["to"]))
async def announcer(announcement: str, voice_channel: discord.VoiceChannel, destination = None):
from src.dc.embeds import build_info_embed, build_announcement_embed
announcements_enabled = config.announcements.enabled
voice_announcement_enabled = config.announcements.voice[0].enabled
announcements_enabled = config.announcements.text_announcements
if announcements_enabled:
if len(voice_channel.members) > 0:
match announcement:
case "end_of_connection":
if voice_announcement_enabled:
announcement_status = await voice_announcer(destination, voice_channel, "end_stations")
if announcement_status:
return
embed = build_announcement_embed(lang.embeds.announcement.end_of_connection.message())
embed = build_announcement_embed(lang.embeds.announcement.end_of_connection.message())
case "transfer":
embed = build_info_embed()
case _:
@@ -72,8 +65,8 @@ async def announcer(announcement: str, voice_channel: discord.VoiceChannel, dest
if embed:
await voice_channel.send(embed=embed)
async def voice_announcer(destination: str, voice_channel: discord.VoiceChannel, type_announcement: str) -> bool:
sound_path = get_sound_path(destination=destination, type_announcement=type_announcement)
async def voice_announcer(station: str, voice_channel: discord.VoiceChannel) -> bool:
sound_path = get_sound_path(station=station)
if sound_path is None:
return False
@@ -121,7 +114,7 @@ async def _schedule_next_transfer(bot: discord.Bot, arrival_dt: datetime, voice_
logger("Train arrived, searching for a new connection....")
await rename_vc(bot, voice_channel, from_scheduler=True)
async def _update_next_loop(bot: discord.Bot, voice_channel: discord.VoiceChannel):
async def _update_next_loop(voice_channel: discord.VoiceChannel):
global trip
try:
if trip is None:
@@ -145,7 +138,8 @@ async def _update_next_loop(bot: discord.Bot, voice_channel: discord.VoiceChanne
status_text = f"{lang.embeds.info.next_stop()}: {next_stop_str}"
await voice_channel.set_status(status_text, reason="Next stop status")
await voice_announcer(next_stop_str, voice_channel, type_announcement="stops")
if config.announcements.voice_announcements:
await voice_announcer(next_stop_str, voice_channel)
wait_seconds = (next_stop["arrival"] - datetime.now(LOCAL_TZ)).total_seconds()
if wait_seconds > 0:
@@ -154,10 +148,10 @@ async def _update_next_loop(bot: discord.Bot, voice_channel: discord.VoiceChanne
except asyncio.CancelledError:
raise
def start_next_stop_updates(bot: discord.bot, voice_channel: discord.VoiceChannel):
def start_next_stop_updates(voice_channel: discord.VoiceChannel):
global _next_stop_task
if _next_stop_task is not None and not _next_stop_task.done():
_next_stop_task.cancel()
_next_stop_task = asyncio.create_task(_update_next_loop(bot, voice_channel))
_next_stop_task = asyncio.create_task(_update_next_loop(voice_channel))
+7 -24
View File
@@ -123,31 +123,14 @@ def get_operator_metadata(agency: str, route_color: str, mode: str) -> dict:
"slogans": slogans
}
def get_sound_path(destination, type_announcement: str) -> str | None:
if type_announcement == "end_stations":
voice_stations = config.announcements.voice[0].end_stations
elif type_announcement == "stops":
voice_stations = config.announcements.voice[0].stops
if destination in voice_stations:
announcement_for = destination
else:
general_sound_enabled = voice_stations.get("general", "")
if not general_sound_enabled:
return None
announcement_for = "general"
if voice_stations.values() == list:
sound_file = random.choice(voice_stations.get(announcement_for))
else:
sound_file = voice_stations.get(announcement_for)
def get_sound_path(station: str) -> str | None:
announcement_dir = Path("src/data/announcements")
sound_path = f"src/data/announcements/{sound_file}"
if Path(sound_path).is_file() is False:
logger(f"Couldn't find {sound_path}", "error")
return None
return sound_path
for file in announcement_dir.iterdir():
if file.is_file():
if station.lower() in file.stem.lower():
sound_file = file.resolve()
return sound_file
def get_next_station(stops: dict, train_from: str) -> dict | None:
now = datetime.now(LOCAL_TZ)