HoraVox - a multi-language speaking clock using local AI voice models
Project description
HoraVox
Hora (Latin: hour) + Vox (Latin: voice) -- the voice of the hour.
A multi-language speaking clock that announces the time using Piper text-to-speech. It runs entirely offline using local AI voice models -- no API key or internet connection required (except for the initial voice download). It speaks the current hour on the hour using natural language idioms (e.g., "quarter past two", "wpół do czwartej") and supports any language through JSON data files.
Features
- Natural time idioms -- not just "it is 14:30" but "half past two" (English) or "wpół do trzeciej" (Polish)
- Classic & modern modes -- idiomatic ("quarter past five") or digital ("five fifteen")
- Multi-language -- add a new language by creating a JSON file in
data/lang/ - Fully offline -- uses local AI voice models, no API key or cloud service needed
- Voice management -- browse, download, and auto-detect Piper voices from Hugging Face
- Bluetooth audio fix -- plays a silent MP3 before speech to prevent clipping on Bluetooth speakers
- Flexible scheduling -- restrict announcements to a time range (e.g., 7:00--22:00)
- Configurable interval -- announce every N minutes with
--freq(e.g., every 30 min) - Volume control -- set volume 0--100% with
--volume - Background mode -- run as a daemon with
--background, stop with--stop - Hour beeps -- 2 beeps on the full hour, 1 beep on the half hour
- Simulated time -- debug with
--time HH:MMto set a fake starting time - Silent by default -- no terminal output unless
--verboseis passed
Requirements
- Python 3.10+ and pip
aplay(ALSA utils, for WAV playback) --sudo apt install alsa-utilsmpg123(for MP3 playback) --sudo apt install mpg123
Installation
From PyPI
pip install horavox
This installs the vox command.
From source
git clone https://github.com/jcubic/horavox.git
cd horavox
pip install .
This installs the vox command from the local source, including all dependencies.
Usage
Speak the current time and exit
vox --now
Run as a clock (announces on the hour)
vox
Switch between classic and modern time style
vox --mode classic # "quarter past five", "za kwadrans szósta" (default)
vox --mode modern # "five fifteen", "siedemnasta piętnaście"
Classic mode uses idiomatic expressions (quarters, halves, past/to) with 12-hour names. Modern mode reads the time digitally (hour + minutes) using 24-hour names in Polish.
List available voices for a language
vox --list-voices --lang pl
vox --list-voices --lang en
Install and use a specific voice
vox --voice en_US-lessac-medium
Voices are auto-downloaded from Hugging Face if not already installed.
Limit speaking hours
vox --start 7 --end 22
vox --start 7:30 --end 22:30
Accepts H, HH, H:MM, or HH:MM. Supports midnight wrap (e.g., --start 22 --end 6).
Announce every 30 minutes
vox --freq 30
Valid values for --freq must divide 60 evenly: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60.
Debug with simulated time
vox --time 16:00 --exit
Run in the background
vox --lang pl --voice pl_PL-darkman-medium --start 8 --end 0 --background
Stop the background daemon:
vox --stop
Set volume
vox --volume 50 # 50% volume
vox --volume 0 # silent, same as --nosound
--nosound is equivalent to --volume 0 -- both skip voice loading and audio playback entirely.
Enable log output
vox --verbose
All options
usage: vox [-h] [--lang LANG] [--mode {classic,modern}] [--voice NAME]
[--list-voices] [--start HH:MM] [--end HH:MM] [--freq MIN]
[--time HH:MM] [--exit] [--now] [--background] [--stop]
[--verbose] [--volume PCT] [--nosound] [--debug]
HoraVox — announces the time using text-to-speech
options:
--lang LANG Language code, e.g. pl, en (default: from system locale)
--mode MODE Time style: classic (idiomatic) or modern (digital) (default: classic)
--voice NAME Voice name, e.g. en_US-lessac-medium (auto-downloads if missing)
--list-voices List available Piper voices for the current language and exit
--start HH:MM Start time for speaking range (default: 0:00)
--end HH:MM End time for speaking range (default: 23:59)
--freq MIN Announcement interval in minutes (default: 60)
--time HH:MM Set simulated start time for debugging (e.g., 16:00)
--exit Run once and exit (for debugging)
--now Speak the current time (with minutes) and exit
--background Run as a background daemon
--stop Stop the background daemon and exit
--verbose Show log messages (silent by default)
--volume PCT Volume level 0-100 percent (default: 100, 0 = no sound)
--nosound Same as --volume 0 — skip voice loading and audio playback
--debug Alias for --nosound --verbose
Adding a new language
Create a JSON file in data/lang/<code>.json (e.g., de.json for German). The file contains two mode sections:
{
"classic": {
"hours": ["midnight", "one o'clock", "...", "eleven o'clock"],
"hours_alt": ["midnight", "one", "...", "eleven"],
"minutes": {
"1": "one", "2": "two", "...": "...", "29": "twenty nine"
},
"patterns": {
"full_hour": "{hour}",
"quarter_past": "quarter past {hour_alt}",
"half_past": "half past {hour_alt}",
"quarter_to": "quarter to {next_hour_alt}",
"minutes_past": "{minutes} past {hour_alt}",
"minutes_to": "{minutes} to {next_hour_alt}"
}
},
"modern": {
"hours": ["midnight", "one o'clock", "..."],
"hours_alt": ["twelve", "one", "..."],
"minutes": {
"1": "oh one", "...": "...", "59": "fifty nine"
},
"patterns": {
"full_hour": "{hour}",
"time": "{hour_alt} {minutes}"
}
}
}
Fields
Classic mode (idiomatic -- quarters, halves, past/to):
| Field | Required | Description |
|---|---|---|
hours |
Yes | 24 entries (index 0 = midnight, 12 = noon, etc.) used in {hour} and {next_hour} |
hours_alt |
No | 24 entries for alternate forms (e.g., genitive case). Defaults to hours if omitted |
minutes |
Yes | Keys "1" through "29" -- spoken forms for minute counts |
patterns |
Yes | 6 patterns: full_hour, quarter_past, half_past, quarter_to, minutes_past, minutes_to |
Modern mode (digital -- hour + minutes):
| Field | Required | Description |
|---|---|---|
hours |
Yes | 24 entries for full-hour announcements (can include "midnight", "noon") |
hours_alt |
No | 24 entries for the hour in {hour_alt} {minutes} patterns. Defaults to hours |
minutes |
Yes | Keys "1" through "59" -- spoken forms for all minute values |
patterns |
Yes | 2 patterns: full_hour and time |
Placeholders
| Placeholder | Meaning |
|---|---|
{hour} |
Current hour from hours |
{hour_alt} |
Current hour from hours_alt |
{next_hour} |
Next hour from hours |
{next_hour_alt} |
Next hour from hours_alt |
{minutes} |
Minute count from minutes map |
{remaining} |
Minutes remaining to next hour (same source as {minutes}) |
Pattern rules
| Pattern | When | Example (English) |
|---|---|---|
full_hour |
:00 | "three o'clock" |
quarter_past |
:15 | "quarter past three" |
half_past |
:30 | "half past three" |
quarter_to |
:45 | "quarter to four" |
minutes_past |
:01--:29 (not :15) | "ten past three" |
minutes_to |
:31--:59 (not :45) | "ten to four" |
Project structure
src/horavox/
__init__.py Package init
cli.py Main script (installed as `vox` via pip)
data/
lang/
en.json English time data
pl.json Polish time data
blank.mp3 Silent MP3 for Bluetooth audio wake-up
beep.mp3 Beep sound for hour/half-hour signals
pyproject.toml Package configuration
~/.horavox/ Runtime data (created automatically)
voices/ Downloaded Piper voice models (.onnx)
cache/ Voice catalog cache + PID file
horavox.log Spoken words + error log
Development
git clone https://github.com/jcubic/horavox.git
cd horavox
pip install -r requirements.txt
This installs only the dependencies without installing the package itself. You can then run the script directly:
python src/horavox/cli.py --now
Alternatively, install in editable mode to get the vox command that reflects your source changes:
pip install -e .
Publishing to PyPI
Update the VERSION variable in the Makefile, then run:
make publish
This updates the version in pyproject.toml, cli.py, and README.md, builds the package, and uploads it to PyPI.
License
Copyright (C) 2026 Jakub T. Jankiewicz
Released under MIT license
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file horavox-0.1.0.tar.gz.
File metadata
- Download URL: horavox-0.1.0.tar.gz
- Upload date:
- Size: 26.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e82e53bd50efcfd1d6280047bad8064d4e105d4dc5805d262872f9165d49ac70
|
|
| MD5 |
d6390df740959891f6869ec5ae2d3d2e
|
|
| BLAKE2b-256 |
3f41527cef6f59af72b20789c38557b9a67287b1f521f864ecb2e23c9744d096
|
File details
Details for the file horavox-0.1.0-py3-none-any.whl.
File metadata
- Download URL: horavox-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4edf54ba75cc6ecd4ad811f44eb65a8d2bf6696b5ac3066f7214e8188037674a
|
|
| MD5 |
0812383f9e19b2f52c2abb7d7f3b2b0b
|
|
| BLAKE2b-256 |
e631cf7979ab07ead2fc24956a8e4e193dc69af5a661c52e55512807ba681d4c
|