Skip to content

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

Merged
socrtwo merged 5 commits into
mainfrom
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
mainfrom
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
@socrtwo
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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants