Volume control + closed captions for the HTPC. No app install required. Any LAN browser works.
# First time setup
setup.bat
# Or manually
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
python -m stfuOpen http://pluto:5000 from any device on the LAN.
Stability & Version Display
- Added version badge to web UI (renders from
stfu/__init__.py) - Audio: Cached pycaw COM interface in
AudioController.__init__withthreading.Lock(fixes CoInitialize on every call) - Captions: Added MQTT reconnection with exponential backoff (1s→30s, 10% jitter) +
stop()method for graceful shutdown - Config: Full validation via
__post_init__on all dataclasses; unknown TOML keys now warned;MQTT_BROKERenv var override - Overlay: Fixed bare
except:→ logs error with traceback - MCP: Removed global state; new
create_mcp_server(audio, config)factory with closure injection - Defaults: Synced
volume.default = 20in config.py and stfu.toml
Initial release — web UI, overlay, MQTT captions, MCP server, Windows service support.
| Command | Description |
|---|---|
python -m stfu |
Run web server + overlay |
python -m stfu --no-overlay |
Run without overlay |
python -m stfu --mcp |
Run MCP server (for AI control) |
python -m stfu --service |
Run as Windows service |
manage.bat |
Interactive menu |
[pluto - Windows 11 HTPC]
├── audio.py (pycaw) → REST API + MQTT state
├── web.py (Flask :5000) → browser UI
├── overlay.py (tkinter) → on-screen volume display
├── captions.py (mss+OCR) → MQTT htpc/captions
└── mcp_server.py (FastMCP) → AI volume control
↕ MQTT (192.168.1.215:1883)
[Edge devices — any LAN browser]
├── phone / tablet
├── ESP32 voice devices
└── any browser
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Web UI |
/volume |
GET | Get volume + mute state |
/volume/up |
POST | Volume up by step |
/volume/down |
POST | Volume down by step |
/volume/set |
POST | Set volume ({"volume": 75}) |
/volume/mute |
POST | Toggle mute |
/config |
GET/POST | Read/update config |
The MCP server exposes these tools for AI control:
get_volume— Read current volume/mute stateset_volume(percent)— Set volume 0-100volume_up()— Increase by stepvolume_down()— Decrease by steptoggle_mute()— Toggle muteset_mute(muted)— Set mute explicitly
Configure in your MCP client:
{
"mcpServers": {
"stfu": {
"command": "python",
"args": ["-m", "stfu", "--mcp"],
"cwd": "C:\\scripts\\stfu"
}
}
}# Install
nssm install STFU python -m stfu --service
nssm set STFU AppDirectory C:\scripts\stfu
nssm set STFU DisplayName "STFU Volume Control"
nssm set STFU Start SERVICE_AUTO_START
nssm set STFU AppStdout C:\scripts\stfu\logs\service-stdout.log
nssm set STFU AppStderr C:\scripts\stfu\logs\service-stderr.log
# Control
nssm start STFU
nssm stop STFU
nssm remove STFU confirmEdit stfu.toml to customize. All values have sensible defaults.
[volume]— step size, min/max, default[web]— host, port, poll interval[overlay]— position, fonts, opacity, duration[mqtt]— broker, port, topics[cc]— OCR region, thresholds, scan interval[mcp]— server name, transport[log]— level, rotation, retention
| Topic | Direction | Purpose |
|---|---|---|
htpc/captions |
HTPC → edges | OCR captions |
htpc/volume/state |
HTPC → edges | Volume state broadcast |
htpc/volume/set |
edges → HTPC | Volume commands |
- Windows 11 (pluto)
- Python 3.11+
- NSSM (for service mode)
- Tesseract OCR (for captions, optional)
- Mosquitto broker (for MQTT, optional)
Stability & Config Fixes
- Audio: Cached pycaw COM interface in
AudioController.__init__+ addedthreading.Lockfor thread safety (was recreating on every call) - MQTT: Added exponential backoff reconnection (1s→30s) in
CaptionCapturewith_stop_eventfor graceful shutdown - Config: Full validation via
__post_init__on all dataclasses; unknown TOML keys now warned;MQTT_BROKERenv var override - Config: Fixed volume default mismatch (now
20in bothstfu.tomlandconfig.py) - MCP: Removed global state; new
create_mcp_server(audio, config)factory with dependency injection - Overlay: Fixed bare
except: pass→ logs errors with traceback - Web UI: Version badge displayed (
v{{ version }})
- Initial release: web UI, overlay, MQTT captions, MCP server, Windows service
# Install dev dependencies
pip install -e .[dev]
# Run tests
python -m pytest tests/
# Lint
ruff check stfu/
black stfu/- Overlay single Tk instance —
run_overlay()creates newTk()each call; only one overlay can run per process. Guard or reuse root. - Busy-wait loops —
__main__.py:124andservice.py:74usesleep(1)polling. Usethreading.Eventfor clean shutdown. - SSE endpoint unused —
web.py:/cc/streamexists but captions use MQTT. Remove or wire up. - Per-app volume — pycaw session API not yet exposed via REST/MCP.
- Structured logging (JSON) for Loki/Grafana
-
/healthendpoint for monitoring - Prometheus metrics (
/metrics) - Request ID correlation across REST + MQTT
- WebSocket for real-time volume state (replace polling)
- Multiple audio endpoint support (select output device)
- Per-app volume control via pycaw session API
- Caption history in web UI (searchable)
- MQTT TLS + auth support
- Home Assistant discovery integration
- MCP server: add
list_devicestool - MCP server: add
set_devicetool - Voice command intent mapping (e.g., "make it louder" → volume_up)
- Scheduled volume profiles (night mode, movie mode)
- LLM-driven caption summarization via MCP
- Linux support (PulseAudio/PipeWire via
pactlorwpctl) - macOS support (CoreAudio via
osascriptorSwitchAudioSource) - Docker image for headless deployment
- ARM64 builds for Pi/ESP32 edge nodes
- Fork the repo
- Create a feature branch
- Make changes with tests
- Run
ruff checkandblack --check - Submit PR
MIT — do what you want.