speaking-clock 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- speaking_clock-1.0.0/PKG-INFO +248 -0
- speaking_clock-1.0.0/README.md +227 -0
- speaking_clock-1.0.0/pyproject.toml +35 -0
- speaking_clock-1.0.0/setup.cfg +4 -0
- speaking_clock-1.0.0/setup.py +73 -0
- speaking_clock-1.0.0/speaking_clock/__init__.py +18 -0
- speaking_clock-1.0.0/speaking_clock/audio.py +84 -0
- speaking_clock-1.0.0/speaking_clock/cli.py +105 -0
- speaking_clock-1.0.0/speaking_clock/clock.py +385 -0
- speaking_clock-1.0.0/speaking_clock/config.py +154 -0
- speaking_clock-1.0.0/speaking_clock/const.py +48 -0
- speaking_clock-1.0.0/speaking_clock/data/__init__.py +11 -0
- speaking_clock-1.0.0/speaking_clock/data/clock-chime.mp3 +0 -0
- speaking_clock-1.0.0/speaking_clock/data/defaults/config.yml +33 -0
- speaking_clock-1.0.0/speaking_clock/languages/__init__.py +3 -0
- speaking_clock-1.0.0/speaking_clock/languages/pl.yml +71 -0
- speaking_clock-1.0.0/speaking_clock/utils/__init__.py +7 -0
- speaking_clock-1.0.0/speaking_clock/utils/audio_processor.py +100 -0
- speaking_clock-1.0.0/speaking_clock/utils/number_converter.py +176 -0
- speaking_clock-1.0.0/speaking_clock.egg-info/PKG-INFO +248 -0
- speaking_clock-1.0.0/speaking_clock.egg-info/SOURCES.txt +23 -0
- speaking_clock-1.0.0/speaking_clock.egg-info/dependency_links.txt +1 -0
- speaking_clock-1.0.0/speaking_clock.egg-info/entry_points.txt +3 -0
- speaking_clock-1.0.0/speaking_clock.egg-info/requires.txt +4 -0
- speaking_clock-1.0.0/speaking_clock.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: speaking-clock
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A clock that speaks the current time using ElevenLabs API
|
|
5
|
+
Home-page: https://github.com/example/speaking-clock
|
|
6
|
+
Author: Marcin Orlowski
|
|
7
|
+
Author-email: Marcin Orlowski <mail@marcinorlowski.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.7
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: elevenlabs
|
|
15
|
+
Requires-Dist: PyYAML
|
|
16
|
+
Requires-Dist: python-dateutil
|
|
17
|
+
Requires-Dist: num2words
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: requires-python
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
▄▀▀▄ █ ▀ ▄▀▀▄ ▀█ █
|
|
24
|
+
▀▄▄ █▀▀▄ ▄▀▀▄ ▄▀▀▄ █ ▄▀ ▀█ █▀▀▄ ▄▀▀█ █ █ ▄▀▀▄ ▄▀▀▄ █ ▄▀
|
|
25
|
+
█ █ █ █▀▀ ▄▄█ █▀▄ █ █ █ █ █ █ █ █ █ █ █▀▄
|
|
26
|
+
▀▄▄▀ █▄▄▀ ▀▄▄▀ ▀▄▄▀ █ █ ▄█▄ █ █ ▀▄▄█ ▀▄▄▀ ▄█▄ ▀▄▄▀ ▀▄▄▀ █ █
|
|
27
|
+
█ ▄▄▀
|
|
28
|
+
|
|
29
|
+
@project Speaking Clock - time announcer using ElevenLabs TTS API
|
|
30
|
+
@author Marcin Orlowski <mail (#) marcinOrlowski (.) com>
|
|
31
|
+
@copyright 2025 Marcin Orlowski
|
|
32
|
+
@license https://www.opensource.org/licenses/mit-license.php MIT
|
|
33
|
+
@link https://github.com/MarcinOrlowski/speaking-clock
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
# Speaking Clock
|
|
37
|
+
|
|
38
|
+
A small utility that speaks (usually current) time in specified language. Uses ElevenLabs API
|
|
39
|
+
to generate the speech. Supports caching and reusing audio files so free ElevenLabs
|
|
40
|
+
access is more than enough. Handy to sit in cron jobs to periodically announce the time.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- Gets the current time and speaks it in specified language
|
|
45
|
+
- Converts numbers to their Polish word representation (e.g., "13:05" → "trzynasta pięć")
|
|
46
|
+
- Uses ElevenLabs API for high-quality text-to-speech
|
|
47
|
+
- Caches generated audio files for quick reuse
|
|
48
|
+
- Supports both 12-hour and 24-hour time formats
|
|
49
|
+
- Optional audio chime before speaking the time
|
|
50
|
+
- Parallel processing - prepares audio while chime is playing
|
|
51
|
+
- Overlaid audio playback with configurable timing offset
|
|
52
|
+
- Command-line interface driven, perfect for background jobs like cron
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
This app is regular Python package and is also hosted
|
|
57
|
+
on [PyPi](https://pypi.org/project/speaking-clock/) so
|
|
58
|
+
you can install it as usual. But because this one is supposed to rather act as the application, I
|
|
59
|
+
strongly recommend to use [pipx](https://pipx.pypa.io/) to install this tool in isolated
|
|
60
|
+
environment be it on Linux, Windows or MacOS machines. Once you got `pipx` up
|
|
61
|
+
and running, install the package:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
$ pipx install speaking-clock
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Of course, you can also use plain `pip` to do that:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
$ pip install speaking-clock
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
But that might be a problem as some distributions no longer allow system-wide installations,
|
|
74
|
+
therefore use of `pipx` is strongly recommended as the all-in-one solution.
|
|
75
|
+
|
|
76
|
+
Once installed `speaking-clock` executable (and it's alias `speak-time`) should be available in
|
|
77
|
+
your system ready to be used. Please use `--help` to see all available options.
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
### As a command-line tool
|
|
82
|
+
|
|
83
|
+
Once installed, you can use the command-line interface:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
speak-time
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
You can specify a custom config file:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
speak-time --config /path/to/your/config.yml
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Configuration
|
|
96
|
+
|
|
97
|
+
The speaking-clock looks for configuration in the following places:
|
|
98
|
+
|
|
99
|
+
1. Command-line specified config (if provided)
|
|
100
|
+
1. User config file at `~/.config/speaking-clock/config.yml`
|
|
101
|
+
1. Default configuration built into the package
|
|
102
|
+
|
|
103
|
+
You can create a config file at `~/.config/speaking-clock/config.yml` with the following structure:
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
language:
|
|
107
|
+
code: "pl" # Language code matching a file in 'languages/' directory
|
|
108
|
+
use_24h_clock: true # Use 24-hour (true) or 12-hour (false) format
|
|
109
|
+
|
|
110
|
+
elevenlabs:
|
|
111
|
+
api_key: "your-api-key-here" # Your ElevenLabs API key
|
|
112
|
+
voice_id: "voice-id-here" # ElevenLabs voice ID
|
|
113
|
+
model_id: "eleven_multilingual_v2" # TTS model to use
|
|
114
|
+
|
|
115
|
+
audio:
|
|
116
|
+
play_chime: true # Play a chime before speaking the time
|
|
117
|
+
chime_file: "clock-chime.mp3" # Path to the chime audio file
|
|
118
|
+
overlay_speech: true # Overlay speech over the chime sound
|
|
119
|
+
speech_offset_ms: 1000 # Milliseconds to wait before starting speech
|
|
120
|
+
|
|
121
|
+
cache:
|
|
122
|
+
directory: ".cache" # Directory for caching audio files
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
You can also set your ElevenLabs API key as an environment variable:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
export ELEVENLABS_API_KEY="your-api-key-here"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Language Support
|
|
132
|
+
|
|
133
|
+
The application supports multiple languages through YAML language definition files. These files are stored in the `languages/` directory with filenames matching their language code (e.g., `pl.yml` for Polish).
|
|
134
|
+
|
|
135
|
+
To add support for a new language:
|
|
136
|
+
|
|
137
|
+
1. Create a new YAML file in the `languages/` directory (e.g., `en.yml` for English)
|
|
138
|
+
1. Define the language elements following the structure in the existing language files
|
|
139
|
+
1. Update the `language.code` value in `config.yml` to match your new language file
|
|
140
|
+
|
|
141
|
+
Example language file structure:
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
days_of_week:
|
|
145
|
+
- poniedziałek # Monday
|
|
146
|
+
- wtorek # Tuesday
|
|
147
|
+
# ...
|
|
148
|
+
|
|
149
|
+
hours:
|
|
150
|
+
0: dwudziesta czwarta
|
|
151
|
+
1: pierwsza
|
|
152
|
+
# ...
|
|
153
|
+
|
|
154
|
+
special_minutes:
|
|
155
|
+
0: ""
|
|
156
|
+
15: piętnaście
|
|
157
|
+
# ...
|
|
158
|
+
|
|
159
|
+
special_times:
|
|
160
|
+
midnight: północ, nastał {day_name}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Cache Files
|
|
164
|
+
|
|
165
|
+
Audio files are automatically cached in the `.cache` directory using the following naming convention:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
LANG-VOICE-HH-MM.mp3
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
For example:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
pl-voice_id-13-05.mp3 # Polish, voice_id, 13:05
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
This caching system ensures that each unique time announcement is only generated once, reducing API calls to ElevenLabs and improving response time for frequently requested times.
|
|
178
|
+
|
|
179
|
+
# Audio Support for Cron Jobs in KDE 5
|
|
180
|
+
|
|
181
|
+
## Problem
|
|
182
|
+
|
|
183
|
+
When running Python applications from cron jobs in KDE 5, audio playback doesn't work automatically. This is because cron jobs run in a separate environment without access to your desktop session's audio system.
|
|
184
|
+
|
|
185
|
+
## Solution
|
|
186
|
+
|
|
187
|
+
Configure the cron job with specific environment variables to access your KDE desktop session's audio services.
|
|
188
|
+
|
|
189
|
+
### Required Environment Variables
|
|
190
|
+
|
|
191
|
+
For audio playback in KDE 5, your cron job needs these key environment variables:
|
|
192
|
+
|
|
193
|
+
- `DISPLAY`: Points to your X server display
|
|
194
|
+
- `DBUS_SESSION_BUS_ADDRESS`: Connects to your D-Bus session
|
|
195
|
+
- `XDG_RUNTIME_DIR`: Required for PulseAudio/PipeWire access
|
|
196
|
+
|
|
197
|
+
### Setting Up the Cron Job
|
|
198
|
+
|
|
199
|
+
Edit your crontab:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
crontab -e
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Add your job using this template:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Run every hour at minute 0
|
|
209
|
+
0 * * * * DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$(pgrep -u $LOGNAME plasma-session)/environ | cut -d= -f2-) XDG_RUNTIME_DIR=/run/user/$(id -u) python /path/to/your/script.py
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Testing Your Configuration
|
|
213
|
+
|
|
214
|
+
To test without waiting for the scheduled time:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Test the exact command that cron will run
|
|
218
|
+
DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$(pgrep -u $LOGNAME plasma-session)/environ | cut -d= -f2-) XDG_RUNTIME_DIR=/run/user/$(id -u) python /path/to/your/script.py
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Troubleshooting
|
|
222
|
+
|
|
223
|
+
### Common Issues and Solutions
|
|
224
|
+
|
|
225
|
+
1. **No audio despite correct configuration**:
|
|
226
|
+
|
|
227
|
+
- Ensure PulseAudio is running: `pulseaudio --check`
|
|
228
|
+
- Try adding `PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native` to your environment variables
|
|
229
|
+
|
|
230
|
+
1. **D-Bus address not found**:
|
|
231
|
+
|
|
232
|
+
- If plasma-session isn't found, try: `pgrep -u $LOGNAME plasmashell` instead
|
|
233
|
+
|
|
234
|
+
1. **Permission issues**:
|
|
235
|
+
|
|
236
|
+
- Check audio group membership: `groups | grep audio`
|
|
237
|
+
- Add yourself if needed: `sudo usermod -a -G audio $USER`
|
|
238
|
+
|
|
239
|
+
1. **Application-specific audio servers**:
|
|
240
|
+
|
|
241
|
+
- For PipeWire: Add `PIPEWIRE_RUNTIME_DIR=${XDG_RUNTIME_DIR}/pipewire-0`
|
|
242
|
+
|
|
243
|
+
## Limitations
|
|
244
|
+
|
|
245
|
+
This solution only works when:
|
|
246
|
+
|
|
247
|
+
- You are logged into your KDE session
|
|
248
|
+
- You are using the same user account for both KDE and the cron job
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
```
|
|
2
|
+
▄▀▀▄ █ ▀ ▄▀▀▄ ▀█ █
|
|
3
|
+
▀▄▄ █▀▀▄ ▄▀▀▄ ▄▀▀▄ █ ▄▀ ▀█ █▀▀▄ ▄▀▀█ █ █ ▄▀▀▄ ▄▀▀▄ █ ▄▀
|
|
4
|
+
█ █ █ █▀▀ ▄▄█ █▀▄ █ █ █ █ █ █ █ █ █ █ █▀▄
|
|
5
|
+
▀▄▄▀ █▄▄▀ ▀▄▄▀ ▀▄▄▀ █ █ ▄█▄ █ █ ▀▄▄█ ▀▄▄▀ ▄█▄ ▀▄▄▀ ▀▄▄▀ █ █
|
|
6
|
+
█ ▄▄▀
|
|
7
|
+
|
|
8
|
+
@project Speaking Clock - time announcer using ElevenLabs TTS API
|
|
9
|
+
@author Marcin Orlowski <mail (#) marcinOrlowski (.) com>
|
|
10
|
+
@copyright 2025 Marcin Orlowski
|
|
11
|
+
@license https://www.opensource.org/licenses/mit-license.php MIT
|
|
12
|
+
@link https://github.com/MarcinOrlowski/speaking-clock
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
# Speaking Clock
|
|
16
|
+
|
|
17
|
+
A small utility that speaks (usually current) time in specified language. Uses ElevenLabs API
|
|
18
|
+
to generate the speech. Supports caching and reusing audio files so free ElevenLabs
|
|
19
|
+
access is more than enough. Handy to sit in cron jobs to periodically announce the time.
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- Gets the current time and speaks it in specified language
|
|
24
|
+
- Converts numbers to their Polish word representation (e.g., "13:05" → "trzynasta pięć")
|
|
25
|
+
- Uses ElevenLabs API for high-quality text-to-speech
|
|
26
|
+
- Caches generated audio files for quick reuse
|
|
27
|
+
- Supports both 12-hour and 24-hour time formats
|
|
28
|
+
- Optional audio chime before speaking the time
|
|
29
|
+
- Parallel processing - prepares audio while chime is playing
|
|
30
|
+
- Overlaid audio playback with configurable timing offset
|
|
31
|
+
- Command-line interface driven, perfect for background jobs like cron
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
This app is regular Python package and is also hosted
|
|
36
|
+
on [PyPi](https://pypi.org/project/speaking-clock/) so
|
|
37
|
+
you can install it as usual. But because this one is supposed to rather act as the application, I
|
|
38
|
+
strongly recommend to use [pipx](https://pipx.pypa.io/) to install this tool in isolated
|
|
39
|
+
environment be it on Linux, Windows or MacOS machines. Once you got `pipx` up
|
|
40
|
+
and running, install the package:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
$ pipx install speaking-clock
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Of course, you can also use plain `pip` to do that:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
$ pip install speaking-clock
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
But that might be a problem as some distributions no longer allow system-wide installations,
|
|
53
|
+
therefore use of `pipx` is strongly recommended as the all-in-one solution.
|
|
54
|
+
|
|
55
|
+
Once installed `speaking-clock` executable (and it's alias `speak-time`) should be available in
|
|
56
|
+
your system ready to be used. Please use `--help` to see all available options.
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
### As a command-line tool
|
|
61
|
+
|
|
62
|
+
Once installed, you can use the command-line interface:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
speak-time
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
You can specify a custom config file:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
speak-time --config /path/to/your/config.yml
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Configuration
|
|
75
|
+
|
|
76
|
+
The speaking-clock looks for configuration in the following places:
|
|
77
|
+
|
|
78
|
+
1. Command-line specified config (if provided)
|
|
79
|
+
1. User config file at `~/.config/speaking-clock/config.yml`
|
|
80
|
+
1. Default configuration built into the package
|
|
81
|
+
|
|
82
|
+
You can create a config file at `~/.config/speaking-clock/config.yml` with the following structure:
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
language:
|
|
86
|
+
code: "pl" # Language code matching a file in 'languages/' directory
|
|
87
|
+
use_24h_clock: true # Use 24-hour (true) or 12-hour (false) format
|
|
88
|
+
|
|
89
|
+
elevenlabs:
|
|
90
|
+
api_key: "your-api-key-here" # Your ElevenLabs API key
|
|
91
|
+
voice_id: "voice-id-here" # ElevenLabs voice ID
|
|
92
|
+
model_id: "eleven_multilingual_v2" # TTS model to use
|
|
93
|
+
|
|
94
|
+
audio:
|
|
95
|
+
play_chime: true # Play a chime before speaking the time
|
|
96
|
+
chime_file: "clock-chime.mp3" # Path to the chime audio file
|
|
97
|
+
overlay_speech: true # Overlay speech over the chime sound
|
|
98
|
+
speech_offset_ms: 1000 # Milliseconds to wait before starting speech
|
|
99
|
+
|
|
100
|
+
cache:
|
|
101
|
+
directory: ".cache" # Directory for caching audio files
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
You can also set your ElevenLabs API key as an environment variable:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
export ELEVENLABS_API_KEY="your-api-key-here"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Language Support
|
|
111
|
+
|
|
112
|
+
The application supports multiple languages through YAML language definition files. These files are stored in the `languages/` directory with filenames matching their language code (e.g., `pl.yml` for Polish).
|
|
113
|
+
|
|
114
|
+
To add support for a new language:
|
|
115
|
+
|
|
116
|
+
1. Create a new YAML file in the `languages/` directory (e.g., `en.yml` for English)
|
|
117
|
+
1. Define the language elements following the structure in the existing language files
|
|
118
|
+
1. Update the `language.code` value in `config.yml` to match your new language file
|
|
119
|
+
|
|
120
|
+
Example language file structure:
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
days_of_week:
|
|
124
|
+
- poniedziałek # Monday
|
|
125
|
+
- wtorek # Tuesday
|
|
126
|
+
# ...
|
|
127
|
+
|
|
128
|
+
hours:
|
|
129
|
+
0: dwudziesta czwarta
|
|
130
|
+
1: pierwsza
|
|
131
|
+
# ...
|
|
132
|
+
|
|
133
|
+
special_minutes:
|
|
134
|
+
0: ""
|
|
135
|
+
15: piętnaście
|
|
136
|
+
# ...
|
|
137
|
+
|
|
138
|
+
special_times:
|
|
139
|
+
midnight: północ, nastał {day_name}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Cache Files
|
|
143
|
+
|
|
144
|
+
Audio files are automatically cached in the `.cache` directory using the following naming convention:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
LANG-VOICE-HH-MM.mp3
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
For example:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
pl-voice_id-13-05.mp3 # Polish, voice_id, 13:05
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
This caching system ensures that each unique time announcement is only generated once, reducing API calls to ElevenLabs and improving response time for frequently requested times.
|
|
157
|
+
|
|
158
|
+
# Audio Support for Cron Jobs in KDE 5
|
|
159
|
+
|
|
160
|
+
## Problem
|
|
161
|
+
|
|
162
|
+
When running Python applications from cron jobs in KDE 5, audio playback doesn't work automatically. This is because cron jobs run in a separate environment without access to your desktop session's audio system.
|
|
163
|
+
|
|
164
|
+
## Solution
|
|
165
|
+
|
|
166
|
+
Configure the cron job with specific environment variables to access your KDE desktop session's audio services.
|
|
167
|
+
|
|
168
|
+
### Required Environment Variables
|
|
169
|
+
|
|
170
|
+
For audio playback in KDE 5, your cron job needs these key environment variables:
|
|
171
|
+
|
|
172
|
+
- `DISPLAY`: Points to your X server display
|
|
173
|
+
- `DBUS_SESSION_BUS_ADDRESS`: Connects to your D-Bus session
|
|
174
|
+
- `XDG_RUNTIME_DIR`: Required for PulseAudio/PipeWire access
|
|
175
|
+
|
|
176
|
+
### Setting Up the Cron Job
|
|
177
|
+
|
|
178
|
+
Edit your crontab:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
crontab -e
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Add your job using this template:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
# Run every hour at minute 0
|
|
188
|
+
0 * * * * DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$(pgrep -u $LOGNAME plasma-session)/environ | cut -d= -f2-) XDG_RUNTIME_DIR=/run/user/$(id -u) python /path/to/your/script.py
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Testing Your Configuration
|
|
192
|
+
|
|
193
|
+
To test without waiting for the scheduled time:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Test the exact command that cron will run
|
|
197
|
+
DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$(pgrep -u $LOGNAME plasma-session)/environ | cut -d= -f2-) XDG_RUNTIME_DIR=/run/user/$(id -u) python /path/to/your/script.py
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Troubleshooting
|
|
201
|
+
|
|
202
|
+
### Common Issues and Solutions
|
|
203
|
+
|
|
204
|
+
1. **No audio despite correct configuration**:
|
|
205
|
+
|
|
206
|
+
- Ensure PulseAudio is running: `pulseaudio --check`
|
|
207
|
+
- Try adding `PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native` to your environment variables
|
|
208
|
+
|
|
209
|
+
1. **D-Bus address not found**:
|
|
210
|
+
|
|
211
|
+
- If plasma-session isn't found, try: `pgrep -u $LOGNAME plasmashell` instead
|
|
212
|
+
|
|
213
|
+
1. **Permission issues**:
|
|
214
|
+
|
|
215
|
+
- Check audio group membership: `groups | grep audio`
|
|
216
|
+
- Add yourself if needed: `sudo usermod -a -G audio $USER`
|
|
217
|
+
|
|
218
|
+
1. **Application-specific audio servers**:
|
|
219
|
+
|
|
220
|
+
- For PipeWire: Add `PIPEWIRE_RUNTIME_DIR=${XDG_RUNTIME_DIR}/pipewire-0`
|
|
221
|
+
|
|
222
|
+
## Limitations
|
|
223
|
+
|
|
224
|
+
This solution only works when:
|
|
225
|
+
|
|
226
|
+
- You are logged into your KDE session
|
|
227
|
+
- You are using the same user account for both KDE and the cron job
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "speaking-clock"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "A clock that speaks the current time using ElevenLabs API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.7"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Marcin Orlowski", email = "mail@marcinorlowski.com"}
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"elevenlabs",
|
|
17
|
+
"PyYAML",
|
|
18
|
+
"python-dateutil",
|
|
19
|
+
"num2words",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
speaking-clock = "speaking_clock.cli:main"
|
|
29
|
+
speak-time = "speaking_clock.cli:main"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
include = ["speaking_clock", "speaking_clock.*"]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
speaking_clock = ["data/*.mp3", "data/defaults/*.yml", "languages/*.yml"]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
##################################################################################
|
|
4
|
+
#
|
|
5
|
+
# ▄▀▀▄ █ ▀ ▄▀▀▄ ▀█ █
|
|
6
|
+
# ▀▄▄ █▀▀▄ ▄▀▀▄ ▄▀▀▄ █ ▄▀ ▀█ █▀▀▄ ▄▀▀█ █ █ ▄▀▀▄ ▄▀▀▄ █ ▄▀
|
|
7
|
+
# █ █ █ █▀▀ ▄▄█ █▀▄ █ █ █ █ █ █ █ █ █ █ █▀▄
|
|
8
|
+
# ▀▄▄▀ █▄▄▀ ▀▄▄▀ ▀▄▄▀ █ █ ▄█▄ █ █ ▀▄▄█ ▀▄▄▀ ▄█▄ ▀▄▄▀ ▀▄▄▀ █ █
|
|
9
|
+
# █ ▄▄▀
|
|
10
|
+
#
|
|
11
|
+
# @project Speaking Clock - time announcer using ElevenLabs TTS API
|
|
12
|
+
# @author Marcin Orlowski <mail (#) marcinOrlowski (.) com>
|
|
13
|
+
# @copyright 2025 Marcin Orlowski
|
|
14
|
+
# @license https://www.opensource.org/licenses/mit-license.php MIT
|
|
15
|
+
# @link https://github.com/MarcinOrlowski/speaking-clock
|
|
16
|
+
#
|
|
17
|
+
##################################################################################
|
|
18
|
+
#
|
|
19
|
+
# python -m venv venv
|
|
20
|
+
# source venv/bin/activate
|
|
21
|
+
# pip install -r requirements-dev.txt
|
|
22
|
+
# python -m build
|
|
23
|
+
# # Reinstall the app (do not do "install --upgrade" as cached bytecode can not be updated)
|
|
24
|
+
# pip uninstall --yes dist/speaking_clock-1.0.0-py3-none-any.whl
|
|
25
|
+
# # intentionally no --upgrade for install to enforce conflict if not uninstalled fully first.
|
|
26
|
+
# pip install dist/spaking_clock-1.0.0-py3-none-any.whl
|
|
27
|
+
# twine upload dist/*
|
|
28
|
+
#
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import os
|
|
32
|
+
from setuptools import setup, find_packages
|
|
33
|
+
|
|
34
|
+
# Read the contents of README.md
|
|
35
|
+
with open('README.md', 'r', encoding='utf-8') as fh:
|
|
36
|
+
long_description = fh.read()
|
|
37
|
+
|
|
38
|
+
setup(
|
|
39
|
+
name="speaking-clock",
|
|
40
|
+
version="1.0.0",
|
|
41
|
+
author="Marcin Orlowski",
|
|
42
|
+
author_email="info@marcinorlowski.com",
|
|
43
|
+
description="A clock that speaks the current time using ElevenLabs API",
|
|
44
|
+
long_description=long_description,
|
|
45
|
+
long_description_content_type="text/markdown",
|
|
46
|
+
url="https://github.com/example/speaking-clock",
|
|
47
|
+
packages=find_packages(),
|
|
48
|
+
install_requires=[
|
|
49
|
+
'elevenlabs',
|
|
50
|
+
'PyYAML',
|
|
51
|
+
'python-dateutil',
|
|
52
|
+
'num2words',
|
|
53
|
+
],
|
|
54
|
+
classifiers=[
|
|
55
|
+
"Programming Language :: Python :: 3",
|
|
56
|
+
"License :: OSI Approved :: MIT License",
|
|
57
|
+
"Operating System :: OS Independent",
|
|
58
|
+
],
|
|
59
|
+
python_requires='>=3.7',
|
|
60
|
+
entry_points={
|
|
61
|
+
'console_scripts': [
|
|
62
|
+
'speak-time=speaking_clock.cli:main',
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
package_data={
|
|
66
|
+
'speaking_clock': [
|
|
67
|
+
'data/*.mp3',
|
|
68
|
+
'data/defaults/config.yml',
|
|
69
|
+
'languages/*.yml',
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
include_package_data=True,
|
|
73
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Speaking Clock - A Python package that speaks the current time
|
|
3
|
+
using ElevenLabs API and caches audio for reuse
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .clock import SpeakingClock
|
|
7
|
+
from .config import ConfigManager
|
|
8
|
+
from .audio import AudioCache
|
|
9
|
+
from .utils.number_converter import PolishTimeFormatter, PolishNumberConverter
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
12
|
+
__author__ = "Carlos"
|
|
13
|
+
__email__ = "carlos@example.com"
|
|
14
|
+
|
|
15
|
+
# Expose main function for easy imports
|
|
16
|
+
def speak_time():
|
|
17
|
+
"""Main entry point to speak the current time"""
|
|
18
|
+
SpeakingClock().speak_time()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
##################################################################################
|
|
3
|
+
#
|
|
4
|
+
# ▄▀▀▄ █ ▀ ▄▀▀▄ ▀█ █
|
|
5
|
+
# ▀▄▄ █▀▀▄ ▄▀▀▄ ▄▀▀▄ █ ▄▀ ▀█ █▀▀▄ ▄▀▀█ █ █ ▄▀▀▄ ▄▀▀▄ █ ▄▀
|
|
6
|
+
# █ █ █ █▀▀ ▄▄█ █▀▄ █ █ █ █ █ █ █ █ █ █ █▀▄
|
|
7
|
+
# ▀▄▄▀ █▄▄▀ ▀▄▄▀ ▀▄▄▀ █ █ ▄█▄ █ █ ▀▄▄█ ▀▄▄▀ ▄█▄ ▀▄▄▀ ▀▄▄▀ █ █
|
|
8
|
+
# █ ▄▄▀
|
|
9
|
+
#
|
|
10
|
+
# @project Speaking Clock - time announcer using ElevenLabs TTS API
|
|
11
|
+
# @author Marcin Orlowski <mail (#) marcinOrlowski (.) com>
|
|
12
|
+
# @copyright 2025 Marcin Orlowski
|
|
13
|
+
# @license https://www.opensource.org/licenses/mit-license.php MIT
|
|
14
|
+
# @link https://github.com/MarcinOrlowski/speaking-clock
|
|
15
|
+
#
|
|
16
|
+
##################################################################################
|
|
17
|
+
|
|
18
|
+
Audio caching functionality for the speaking clock
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Optional
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AudioCache:
|
|
27
|
+
"""Audio cache manager for speaking clock"""
|
|
28
|
+
def __init__(self, cache_dir: str, language: str):
|
|
29
|
+
self.base_dir = Path(os.path.expanduser(cache_dir))
|
|
30
|
+
self.base_dir.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
self.language = language
|
|
32
|
+
|
|
33
|
+
def get_cache_filename(self, voice_id: str, hour: int, minute: int) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Get cache filename using the format: LANG-VOICE-HH-MM.mp3 (lowercased)
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
voice_id: ElevenLabs voice ID or name
|
|
39
|
+
hour: Hour (00-23)
|
|
40
|
+
minute: Minute (00-59)
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Cache filename
|
|
44
|
+
"""
|
|
45
|
+
return f"{self.language}-{voice_id}-{hour:02d}-{minute:02d}.mp3".lower()
|
|
46
|
+
|
|
47
|
+
def get_cached_file_path(self, voice_id: str, hour: int, minute: int) -> Path:
|
|
48
|
+
"""Get full path for a cached audio file"""
|
|
49
|
+
filename = self.get_cache_filename(voice_id, hour, minute)
|
|
50
|
+
return self.base_dir / filename
|
|
51
|
+
|
|
52
|
+
def get_cached_audio(self, voice_id: str, hour: int, minute: int) -> Optional[Path]:
|
|
53
|
+
"""
|
|
54
|
+
Get cached audio file if it exists
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
voice_id: ElevenLabs voice ID
|
|
58
|
+
hour: Hour (0-23)
|
|
59
|
+
minute: Minute (0-59)
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Path to cached file or None if not found
|
|
63
|
+
"""
|
|
64
|
+
cache_path = self.get_cached_file_path(voice_id, hour, minute)
|
|
65
|
+
return cache_path if cache_path.exists() else None
|
|
66
|
+
|
|
67
|
+
def save_audio(self, audio_data: bytes, voice_id: str, hour: int, minute: int) -> Path:
|
|
68
|
+
"""
|
|
69
|
+
Save audio data to cache
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
audio_data: Audio data bytes
|
|
73
|
+
voice_id: ElevenLabs voice ID
|
|
74
|
+
hour: Hour (0-23)
|
|
75
|
+
minute: Minute (0-59)
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Path to cached file
|
|
79
|
+
"""
|
|
80
|
+
cache_path = self.get_cached_file_path(voice_id, hour, minute)
|
|
81
|
+
with open(cache_path, 'wb') as file:
|
|
82
|
+
file.write(audio_data)
|
|
83
|
+
|
|
84
|
+
return cache_path
|