From 4a2d11399e17d21f7ec69c1233d592cec9d01e16 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 17 Feb 2026 10:50:56 +0200 Subject: [PATCH] sound options --- python/utils/interval_timer_ding.py | 91 +++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/python/utils/interval_timer_ding.py b/python/utils/interval_timer_ding.py index 1b33c3d..a04fec7 100644 --- a/python/utils/interval_timer_ding.py +++ b/python/utils/interval_timer_ding.py @@ -4,10 +4,13 @@ Plays a ding/timer sound at regular intervals aligned to round period marks (e.g. every 15 min), with an optional offset so the sound plays a few seconds before each mark. -Default: period 15 minutes, offset 15 seconds. -Notification plays at :14:45, :29:45, :44:45, :59:45. +Default: period 15 min, offset 15 s, 1 ding, sound asterisk. +Notification at :14:45, :29:45, :44:45, :59:45. Windows + Linux (stdlib). -Works on Windows and Linux (stdlib + optional system players on Linux). + python scripts/python/utils/interval_timer_ding.py + python scripts/python/utils/interval_timer_ding.py -p 30 -o 10 + python scripts/python/utils/interval_timer_ding.py -s exclamation -n 2 + python scripts/python/utils/interval_timer_ding.py -s /path/to/notify.wav """ import argparse @@ -38,20 +41,54 @@ def _wait_seconds(period_sec: int, offset_sec: int) -> int: return max(1, wait) if wait > 0 else 1 +# Gap between repeated dings (Windows MessageBeep is async so need ~0.5s+) +_DING_GAP_SEC = 1.7 +_WIN_BEEP_MS = 350 + +# Sound choice: None = use default, "asterisk"|"exclamation"|... = Windows preset, or file path +_sound: str | None = None + + def _play_ding_windows() -> None: - try: - import winsound - winsound.MessageBeep(winsound.MB_ICONASTERISK) - except Exception: + import winsound + sound = _sound or "asterisk" + if sound in ("asterisk", "exclamation", "hand", "question", "beep"): try: - import winsound - winsound.Beep(800, 200) + if sound == "asterisk": + winsound.MessageBeep(winsound.MB_ICONASTERISK) + elif sound == "exclamation": + winsound.MessageBeep(winsound.MB_ICONEXCLAMATION) + elif sound == "hand": + winsound.MessageBeep(winsound.MB_ICONHAND) + elif sound == "question": + winsound.MessageBeep(winsound.MB_ICONQUESTION) + else: # beep + winsound.Beep(800, _WIN_BEEP_MS) + return except Exception: - sys.stdout.write("\a") - sys.stdout.flush() + pass + # Custom file path (SND_FILENAME only = blocking until sound finishes) + try: + winsound.PlaySound(sound, winsound.SND_FILENAME) + return + except Exception: + pass + try: + winsound.Beep(800, _WIN_BEEP_MS) + except Exception: + sys.stdout.write("\a") + sys.stdout.flush() def _play_ding_linux() -> None: + sound = _sound + if sound: + for cmd in (["paplay", sound], ["aplay", sound]): + try: + subprocess.run(cmd, capture_output=True, timeout=3) + return + except (FileNotFoundError, subprocess.TimeoutExpired, OSError): + continue candidates = [ ["paplay", "/usr/share/sounds/freedesktop/stereo/bell.oga"], ["paplay", "/usr/share/sounds/freedesktop/stereo/message.oga"], @@ -68,13 +105,6 @@ def _play_ding_linux() -> None: sys.stdout.flush() -def play_ding() -> None: - if sys.platform == "win32": - _play_ding_windows() - else: - _play_ding_linux() - - def main() -> None: parser = argparse.ArgumentParser( description="Play a ding every N minutes, offset seconds before each round mark." @@ -93,12 +123,31 @@ def main() -> None: metavar="SEC", help="Seconds before each round mark to play (default: 15)", ) + parser.add_argument( + "-s", "--sound", + type=str, + default=None, + metavar="PRESET|PATH", + help="Notification sound: Windows presets asterisk|exclamation|hand|question|beep, or path to .wav file (default: asterisk)", + ) + parser.add_argument( + "-n", "--dings", + type=int, + default=1, + metavar="N", + help="Number of dings per notification (default: 1, max: 10)", + ) args = parser.parse_args() + global _sound + _sound = args.sound + if args.period < 1: parser.error("period must be >= 1") if args.offset < 0: parser.error("offset must be >= 0") + if not 1 <= args.dings <= 10: + parser.error("dings must be 1..10") period_sec = args.period * 60 now = datetime.now() @@ -113,10 +162,14 @@ def main() -> None: "Press Ctrl+C to stop." ) + play = _play_ding_windows if sys.platform == "win32" else _play_ding_linux while True: wait_sec = _wait_seconds(period_sec, args.offset) time.sleep(wait_sec) - play_ding() + for i in range(args.dings): + play() + if i < args.dings - 1: + time.sleep(_DING_GAP_SEC) print(f"[{datetime.now().strftime('%H:%M:%S')}] ding")