Minus is an open-source HDMI box that sits between your streaming device and your TV. It watches the video feed, detects ads with ML models running locally on its own NPUs, then mutes and covers them until your show comes back. Instead of the ad you get something useful, like a Spanish vocabulary card.
Nothing leaves your living room. There is no cloud service, no subscription, and no account. The OCR, the vision model, and the speech recognition all run on the device.

An ad break as seen through Minus. The ad is muted and covered by a vocabulary card, with a small greyscale preview in the corner so you can tell when it's over.
Minus is a 4K@60fps HDMI passthrough. Your Roku, Fire TV, or Google TV plugs into Minus, and Minus plugs into your TV. Three detectors watch the stream and vote on whether an ad is playing:
┌──────────────┐ ┌────────────────────┐ ┌─────────────────────┐
│ HDMI IN │────▶│ ustreamer │────▶│ HDMI OUT to TV │
│ (Roku, etc.) │ │ (MPP hw encoding) │ │ (DRM/KMS, 60fps) │
└──────────────┘ └────────┬───────────┘ └─────────────────────┘
│ snapshots + audio
┌───────────────┼────────────────┐
▼ ▼ ▼
┌────────────────┐ ┌──────────────┐ ┌──────────────┐
│ PaddleOCR │ │ minus-v0.1 │ │ Moonshine │
│ RK3588 NPU │ │ (VLM) │ │ ASR (CPU) │
│ reads ad UI │ │ Axera NPU │ │ hears ad │
│ text ~400ms │ │ ~370ms │ │ language │
└────────────────┘ └──────────────┘ └──────────────┘
└───────────────┼────────────────┘
▼
blocking decision engine
(mute + overlay, typically < 2s to react)
When the votes say "ad", audio mutes immediately and the screen switches to an overlay rendered at 60fps inside the hardware JPEG encoder (no desktop stack, no X11). When the ad ends, usually within a second, the show comes back.
minus-v0.1 is published on Hugging Face: TheGarageDev/Minus-v0.1.
It's a fine-tune of LiquidAI's LFM2.5-VL-450M trained on frames captured from real TV streams and compiled into fused-layer models for the Axera NPU. Inference is prefill-only: the yes/no answer is read straight from the logits without generating any tokens, so latency is deterministic.
| Metric | Value |
|---|---|
| Accuracy (800-image holdout) | 97.0% |
| Ad recall | 94.8% |
| Non-ad recall (false-positive resistance) | 99.2% |
| Inference latency | ~0.37s per frame |
| Parameters | 450M |
The same model also classifies screen state (playing / paused / menu / dialog / screensaver) for autonomous mode.
| Desktop | Mobile |
|---|---|
![]() | ![]() |
The web UI is served by the device itself (Flask). Live feed, block/pause controls, per-detection history, a swipe-based screenshot review flow for cleaning up training data, device remotes, and every setting. No app to install.
| Component | Notes |
|---|---|
| Radxa Rock 5B+ | RK3588 SoC with an HDMI input port, NPU for OCR, VPU for 4K60 JPEG encoding |
| Axera AX8850 accelerator (M.2) | Runs minus-v0.1 at ~370ms per inference |
| HDMI cables | Source → Minus → TV |
| Optional: IR LED on GPIO | Controls an HDMI switch for multi-device setups |
| Optional: WS2812B 8-LED strip | Status indicator (idle / blocking / error / ...) |
| Optional: HDCP 1.4 sink key | For capturing HDCP-protected sources, see hdcp/ |
There is no display server or desktop environment involved. Minus talks straight to DRM/KMS and the hardware encoders.
git clone https://github.com/garagehq/Minus-streaming-stick.git /home/radxa/Minus
cd /home/radxa/Minus
# Install system + Python dependencies (GStreamer, fonts, rknnlite, axengine, ...)
# then build the patched ustreamer:
git clone https://github.com/garagehq/ustreamer.git /home/radxa/ustreamer-garagehq
cd /home/radxa/ustreamer-garagehq && make WITH_MPP=1
cp ustreamer /home/radxa/ustreamer-patched
# Download the model
# → https://huggingface.co/TheGarageDev/Minus-v0.1
# into /home/radxa/axera_models/minus-v0.1/
# Run directly:
cd /home/radxa/Minus && sudo python3 minus.py
# ...or install as a service that starts on boot:
sudo ./install.sh
The full dependency list and deployment walkthrough are in docs/DEPLOYMENT.md. Model paths and detection thresholds are set with environment variables (MINUS_VLM_MODEL_DIR, MINUS_OCR_MODEL_DIR, etc.); CLAUDE.md documents all of them.
Minus auto-detects the connected HDMI output, resolution, DRM plane, and audio device at startup, and works with both 4K and 1080p displays.
| Document | Description |
|---|---|
| docs/FEATURES.md | Complete feature list |
| docs/ARCHITECTURE.md | System architecture and data flow |
| docs/DEPLOYMENT.md | Setting up a new device from scratch |
| docs/API.md | Web UI REST API |
| docs/ASR.md | Audio-based ad confirmation |
| docs/AUDIO.md | Audio passthrough pipeline |
| docs/AESTHETICS.md | Visual design guide |
| docs/IR_TRANSMITTER.md | IR blaster for HDMI switches |
| docs/STATUS_LEDS.md | WS2812B status strip |
| hdcp/README.md | HDCP 1.4 setup for the HDMI input |
| CLAUDE.md | Engineering notes: detection tuning and the full bug history |
Minus is free, open-source, and runs entirely on your own hardware. No ads, no tracking, no server bills passed on to you. If it has saved you from a few unskippable ad breaks, you can buy me a coffee ☕:
29 commits
Python
85.4%
HTML
9.5%
CSS
2.8%
Shell
2.2%
Minus is an open-source HDMI box that sits between your streaming device and your TV. It watches the video feed, detects ads with ML models running locally on its own NPUs, then mutes and covers them until your show comes back. Instead of the ad you get something useful, like a Spanish vocabulary card.
Nothing leaves your living room. There is no cloud service, no subscription, and no account. The OCR, the vision model, and the speech recognition all run on the device.

An ad break as seen through Minus. The ad is muted and covered by a vocabulary card, with a small greyscale preview in the corner so you can tell when it's over.
Minus is a 4K@60fps HDMI passthrough. Your Roku, Fire TV, or Google TV plugs into Minus, and Minus plugs into your TV. Three detectors watch the stream and vote on whether an ad is playing:
┌──────────────┐ ┌────────────────────┐ ┌─────────────────────┐
│ HDMI IN │────▶│ ustreamer │────▶│ HDMI OUT to TV │
│ (Roku, etc.) │ │ (MPP hw encoding) │ │ (DRM/KMS, 60fps) │
└──────────────┘ └────────┬───────────┘ └─────────────────────┘
│ snapshots + audio
┌───────────────┼────────────────┐
▼ ▼ ▼
┌────────────────┐ ┌──────────────┐ ┌──────────────┐
│ PaddleOCR │ │ minus-v0.1 │ │ Moonshine │
│ RK3588 NPU │ │ (VLM) │ │ ASR (CPU) │
│ reads ad UI │ │ Axera NPU │ │ hears ad │
│ text ~400ms │ │ ~370ms │ │ language │
└────────────────┘ └──────────────┘ └──────────────┘
└───────────────┼────────────────┘
▼
blocking decision engine
(mute + overlay, typically < 2s to react)
When the votes say "ad", audio mutes immediately and the screen switches to an overlay rendered at 60fps inside the hardware JPEG encoder (no desktop stack, no X11). When the ad ends, usually within a second, the show comes back.
minus-v0.1 is published on Hugging Face: TheGarageDev/Minus-v0.1.
It's a fine-tune of LiquidAI's LFM2.5-VL-450M trained on frames captured from real TV streams and compiled into fused-layer models for the Axera NPU. Inference is prefill-only: the yes/no answer is read straight from the logits without generating any tokens, so latency is deterministic.
| Metric | Value |
|---|---|
| Accuracy (800-image holdout) | 97.0% |
| Ad recall | 94.8% |
| Non-ad recall (false-positive resistance) | 99.2% |
| Inference latency | ~0.37s per frame |
| Parameters | 450M |
The same model also classifies screen state (playing / paused / menu / dialog / screensaver) for autonomous mode.
| Desktop | Mobile |
|---|---|
![]() | ![]() |
The web UI is served by the device itself (Flask). Live feed, block/pause controls, per-detection history, a swipe-based screenshot review flow for cleaning up training data, device remotes, and every setting. No app to install.
| Component | Notes |
|---|---|
| Radxa Rock 5B+ | RK3588 SoC with an HDMI input port, NPU for OCR, VPU for 4K60 JPEG encoding |
| Axera AX8850 accelerator (M.2) | Runs minus-v0.1 at ~370ms per inference |
| HDMI cables | Source → Minus → TV |
| Optional: IR LED on GPIO | Controls an HDMI switch for multi-device setups |
| Optional: WS2812B 8-LED strip | Status indicator (idle / blocking / error / ...) |
| Optional: HDCP 1.4 sink key | For capturing HDCP-protected sources, see hdcp/ |
There is no display server or desktop environment involved. Minus talks straight to DRM/KMS and the hardware encoders.
git clone https://github.com/garagehq/Minus-streaming-stick.git /home/radxa/Minus
cd /home/radxa/Minus
# Install system + Python dependencies (GStreamer, fonts, rknnlite, axengine, ...)
# then build the patched ustreamer:
git clone https://github.com/garagehq/ustreamer.git /home/radxa/ustreamer-garagehq
cd /home/radxa/ustreamer-garagehq && make WITH_MPP=1
cp ustreamer /home/radxa/ustreamer-patched
# Download the model
# → https://huggingface.co/TheGarageDev/Minus-v0.1
# into /home/radxa/axera_models/minus-v0.1/
# Run directly:
cd /home/radxa/Minus && sudo python3 minus.py
# ...or install as a service that starts on boot:
sudo ./install.sh
The full dependency list and deployment walkthrough are in docs/DEPLOYMENT.md. Model paths and detection thresholds are set with environment variables (MINUS_VLM_MODEL_DIR, MINUS_OCR_MODEL_DIR, etc.); CLAUDE.md documents all of them.
Minus auto-detects the connected HDMI output, resolution, DRM plane, and audio device at startup, and works with both 4K and 1080p displays.
| Document | Description |
|---|---|
| docs/FEATURES.md | Complete feature list |
| docs/ARCHITECTURE.md | System architecture and data flow |
| docs/DEPLOYMENT.md | Setting up a new device from scratch |
| docs/API.md | Web UI REST API |
| docs/ASR.md | Audio-based ad confirmation |
| docs/AUDIO.md | Audio passthrough pipeline |
| docs/AESTHETICS.md | Visual design guide |
| docs/IR_TRANSMITTER.md | IR blaster for HDMI switches |
| docs/STATUS_LEDS.md | WS2812B status strip |
| hdcp/README.md | HDCP 1.4 setup for the HDMI input |
| CLAUDE.md | Engineering notes: detection tuning and the full bug history |
Minus is free, open-source, and runs entirely on your own hardware. No ads, no tracking, no server bills passed on to you. If it has saved you from a few unskippable ad breaks, you can buy me a coffee ☕:
29 commits
Python
85.4%
HTML
9.5%
CSS
2.8%
Shell
2.2%