Skip to content

Navigation Menu

Sign in
Sign up

AdHush 0.5.0: complete roadmap phases 1–5 plus builder guides - #2

Merged
socrtwo merged 5 commits into
main from
claude/commercial-mute-detector-c7sooi
Aug 28, 2026
Merged

AdHush 0.5.0: complete roadmap phases 1–5 plus builder guides #2
socrtwo merged 5 commits into
main from
claude/commercial-mute-detector-c7sooi

Conversation

@socrtwo

@socrtwo socrtwo commented Aug 28, 2026

Copy link
Copy Markdown
Owner

What changed

The full AdHush implementation, one commit per roadmap phase on top of the scaffold, plus builder documentation:

  • Phase 1 — Raspberry Pi reference (f23869a): events, TOML config with device-profile inheritance, file_replay (.npz + ffmpeg media) and hdmi_uvc capture, the black_frame / silence (spectral-flatness aware) / loudness (K-weighted vs. frozen-while-elevated baseline) detectors, constant-mass weighted fusion with Schmitt hysteresis, the PROGRAM→SUSPECT_AD→AD→RECOVERY state machine with asymmetric dwell and a hard max-mute ceiling, rs232_sharp (AQUOS discrete mute + readback) and ir_lirc controllers, engine (ADR 0004) with separate mute-onset/unmute-onset precision–recall scoring, and the run/replay/doctor/ir-test CLI.
  • Phase 2 — vision and memory (b0e8ce7): logo_absence with edge-template calibration (adhush calibrate) and Pearson-correlation presence scoring, scene_cut, and the fingerprint subsystem — DCT perceptual hash, chroma-bit audio fingerprint, SQLite store with TTL pruning, consecutive-hit matcher with audio corroboration and 15/30/45/60 s slot snapping, learner with duration averaging and duplicate detection. A confirmed match jumps straight to AD for the learned duration; only sustained positive program evidence (the logo back on screen) unmutes early.
  • Phase 3 — breadth of control (958db4a): cec, ir_pigpio (NEC / extended NEC / Samsung / Sharp / SIRC / RC-5 / raw waveform encoders), ir_blaster_net (iTach, Broadlink), network_ip (Sony Simple IP with discrete mute + readback, Roku ECP), adhush probe control-path discovery (ADR 0005), profile-driven option resolution, and the Samsung/LG/Sony/Vizio/Roku profile library.
  • Phase 4 — platforms (1afc2ab): versioned JSON wire protocol and a stdlib-only localhost HTTP+SSE API (ADR 0006) with override, confirm/reject-ad feedback (reject deletes a false-match fingerprint), and live trace; screen/camera (adaptive screen auto-crop)/microphone/line_in capture; local_audio host mute; a dependency-free web front end and per-platform run instructions.
  • Phase 5 — passthrough box (2f4d9b4): relay_hdmi GPIO relay control wired to fail unmuted, the TV-agnostic passthrough-box profile and config, docs/hardware-passthrough-box.md, and a real scripts/install-pi.sh with a systemd unit.
  • Builder guides (c5d895d): a solder-free beginner build guide with priced parts list, and a microcontroller guide (Arduino serial via the existing rs232_sharp protocol subset, ESP32/ESP8266 Wi-Fi TCP via pure-TOML network_ip config, Pico notes, minimum requirements).

Why

Implements the whole docs/roadmap.md for AdHush: detect commercials by multiple fused signals (logo absence, loudness jump, black frames, silence, scene-cut rate), remember previously seen ads by perceptual fingerprint and mute them instantly for their learned duration, and drive the mute over whatever path a given TV supports — RS-232, IR (LIRC, raw GPIO waveforms, network blasters), CEC, network APIs, host audio, or the inline passthrough relay that needs no TV cooperation at all.

Verification

  • make lint type test passes — ruff clean, strict mypy clean across src, 195 tests passing (CI also runs all three)
  • Replay fixtures added or updated for any detector change — every detector has labeled file_replay fixture tests; integration replays report mute-onset and unmute-onset precision/recall separately (1.00/1.00 on the synthesized broadcasts, including the learn-then-recognize fingerprint scenario)
  • Device profiles touched are listed below with hardware actually tested

Profiles added/touched: sharp-lc46le830u (reference; RS-232 command set from the AQUOS spec, IR codes deliberately left unpopulated per the profile's own rule), generic, samsung-generic, lg-generic, sony-bravia-generic, vizio-generic, roku-tv-generic, passthrough-box. No physical hardware was available in this environment — profile behavior is verified by tests that build real controllers from each profile and assert the exact bytes/requests/waveforms sent; IR codes and network endpoints come from community documentation and are flagged in-profile for confirmation with adhush probe / adhush ir-test on real sets.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4


Generated by Claude Code

claude added 5 commits August 28, 2026 08:45
Vision:
- logo_absence: edge-template calibration (adhush calibrate, live or from
 a recording), Pearson-correlation presence scoring — centered because
 edge maps are non-negative and plain cosine floors high on unrelated
 content — absence runs voting AD, and a positive program_present signal.
 Uncalibrated, the detector is excluded rather than diluting fusion.
- scene_cut: downscaled-luma shot-change detection, cuts-per-minute rate
 mapped to confidence between configurable bounds.
Fingerprint memory (repeat-ad recognition):
- video_phash: 63-bit DCT perceptual hash, numpy-only; flat frames gated
 out (their hashes are degenerate and cross-match everything)
- audio_chroma: 12-bit-per-block chroma signatures, level-invariant
- store: SQLite with per-ad hash/chroma rows, duration + sample count,
 TTL pruning
- matcher: vectorized Hamming scan, consecutive-hit confirmation, audio
 corroboration, slot-snapped duration until enough airings agree
- learner: stores fusion-confirmed segments, detects duplicates (updates
 duration instead of re-inserting), folds each airing into a duration
 mean; an early unmute shortens the stored duration
Pipeline:
- state machine: a confirmed fingerprint hit jumps PROGRAM/SUSPECT_AD
 straight to AD with no dwell; inside the matched window only sustained
 positive program evidence (logo back on screen) unmutes early, and the
 max-mute ceiling still wins; RECOVERY ignores promotion
- engine: promotion with learned-duration windows, learning on unmute,
 duration updates per airing, "fingerprint:promote" transition reasons
- cli: calibrate and learn implemented; replay --config runs the full
 pipeline including the fingerprint store
Tests: 102 passing. Integration proves the loop: first airing learned
through the fusion path, second airing (no boundary signals) recognized,
promoted to AD within ~1.2s, muted for the learned duration, and the
stored duration refined to sample_count=2. Ruff and strict mypy clean.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4 
Four new mute backends, all with injectable I/O for hardware-free tests:
- cec: User Control Pressed/Released mute through cec-client. Toggle-only
 by CEC's nature; declares non-discrete so audio verification compensates.
- ir_pigpio: LIRC-free raw IR waveforms on a GPIO pin. Pure-function
 encoders for NEC, extended NEC, Samsung, Sharp (frame + inverted frame),
 Sony SIRC (12/15/20-bit), RC-5 bi-phase, and raw pulse/space arrays;
 codes come from the device profile as {protocol, address, command} or
 raw timing, per docs/device-support.md.
- ir_blaster_net: Global Cache iTach sendir over TCP (completeir checked)
 and Broadlink RM via the optional broadlink package.
- network_ip: profile-driven TCP and HTTP control. Sony Simple IP gets
 discrete mute with real state readback; Roku ECP gets keypress toggle.
Control-path discovery (ADR 0005):
- control/probe.py + `adhush probe`: side-effect-free availability checks
 per backend (binaries, drivers, device nodes, TCP reachability, code
 completeness/encodability), reported in the profile's preference order
 with discrete-mute capability; --active sends one real mute/unmute pair.
- resolve_options in the control registry: profile-supplied settings
 (including the shared [ir] section) merged under [control.<backend>]
 overrides, so device specifics stay in profiles. ControlConfig now keeps
 every [control.<backend>] section for probing non-selected backends.
Profile library: samsung-generic, lg-generic, sony-bravia-generic,
vizio-generic, roku-tv-generic join the Sharp reference profile, with a
support matrix in docs/device-support.md. Profile-driven controller tests
prove each shipped profile actually drives its backend.
Tests: 147 passing; ruff and strict mypy clean.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4 
... mute, web front end
IPC (ADR 0006):
- ipc/protocol.py: versioned JSON wire schema, pure marshalling — events
 (status, transition, decision) and commands (get_status, override,
 confirm_ad, reject_ad, set_trace)
- ipc/api.py: stdlib-only localhost HTTP + Server-Sent-Events server:
 GET /status, POST /command, GET /events with keepalives; optional
 bearer-token auth; permissive CORS so a file:// page can connect.
 SSE instead of WebSocket: every needed feature is request/response
 plus one-way streaming, and the schema is transport-independent
- engine: thread-safe IPC surface — status snapshots, event listeners,
 override pinning at the controller boundary (the machine's record stays
 truthful), confirm_ad (forces learning past the duration bounds),
 reject_ad (immediate unmute, no learning, deletes the fingerprint
 behind a false match); state machine gains cancel_ad, the store
 delete_ad, the learner forget/force
- adhush run starts the API when [ipc] enabled = true
Capture (per-platform ffmpeg, argv builders as pure functions):
- screen: x11grab / avfoundation screen devices / gdigrab; audio from a
 loopback or microphone, or disabled with "none"
- camera: v4l2 / avfoundation / dshow, plus adaptive screen-rectangle
 detection (glare pixels excluded from the vote) and auto-crop,
 re-estimated every few seconds
- microphone and line_in: audio-only sources with fmt:device strings
 (alsa:/pulse:/avfoundation:/dshow:) and per-platform defaults
Control:
- local_audio: host mute via pactl/amixer (Linux/ChromeOS), osascript
 (macOS, with readback), nircmd (Windows); probe support included
Platform shells:
- platforms/web/index.html: dependency-free static front end (EventSource
 + fetch): live state, override buttons, is-an-ad / not-an-ad feedback
- concrete run instructions for linux-pi, windows, macos, chromeos,
 android, ios (mobile = thin client over a networked core, per ADR 0002)
Tests: 186 passing, including a live HTTP/SSE server exercise; ruff and
strict mypy clean.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4 
relay_hdmi controller:
- GPIO relay physically opening the intercepted audio path of the inline
 splitter/extractor topology. Discrete, instant, works on any TV, cannot
 desynchronize; state() reports the commanded pin state, which for a
 relay is the physical path state.
- Fail-unmuted by design: audio through normally-closed contacts, coil
 energized only to mute, and close() releases the relay before letting
 go of the pin — a crash or power loss always leaves the room with
 audio, matching the "stuck mute is a defect" posture.
- Injectable pin driver (pigpio default, imported lazily); active_high
 configurable for inverting relay boards.
Deployment:
- passthrough-box profile (TV-agnostic: relay only) and
 config/adhush-passthrough.example.toml — HDMI-UVC capture, full
 detector set, no audio verification (the relay is deterministic),
 LAN-visible IPC with a token for phone control.
- docs/hardware-passthrough-box.md: signal topology, parts list,
 fail-unmuted wiring, content-protection caveat, and the audio
 delay-line design note (deliberately out of scope).
- scripts/install-pi.sh implemented: apt packages, pigpiod enablement,
 venv install with [pi] extras, and an adhush.service systemd unit.
Wiring: relay_hdmi joins the registry and probe; every control backend
on the roadmap is now implemented, asserted by test.
Tests: 195 passing; ruff and strict mypy clean.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4 
...tors
- docs/build-guide-beginner.md: solder-free, low-voltage-only passthrough
 box build with a priced parts list, numbered steps, adult-check points,
 fail-unmuted relay wiring explained in plain language, and a
 troubleshooting table.
- docs/build-guide-microcontrollers.md: honest scope (microcontrollers are
 the muscle, not the detector core) plus complete recipes: Arduino
 Uno/Nano/Mega speaking the AQUOS MUTE serial subset so the existing
 rs232_sharp backend drives them unchanged; ESP32/ESP8266 Wi-Fi TCP
 firmware paired with a pure-TOML network_ip config giving discrete mute
 with state readback; Pico/Pico W notes; an IR-blaster variant; and the
 minimum requirements table for full-video and audio-only boxes.
- README: link the build guides.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4 
socrtwo merged commit dda96b0 into main Aug 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

2 participants

AltStyle によって変換されたページ (->オリジナル) /