AdHush 0.5.0: complete roadmap phases 1–5 plus builder guides - #2
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
The full AdHush implementation, one commit per roadmap phase on top of the scaffold, plus builder documentation:
f23869a): events, TOML config with device-profile inheritance,file_replay(.npz + ffmpeg media) andhdmi_uvccapture, theblack_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) andir_lirccontrollers, engine (ADR 0004) with separate mute-onset/unmute-onset precision–recall scoring, and therun/replay/doctor/ir-testCLI.b0e8ce7):logo_absencewith 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.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 probecontrol-path discovery (ADR 0005), profile-driven option resolution, and the Samsung/LG/Sony/Vizio/Roku profile library.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_incapture;local_audiohost mute; a dependency-free web front end and per-platform run instructions.2f4d9b4):relay_hdmiGPIO relay control wired to fail unmuted, the TV-agnosticpassthrough-boxprofile and config,docs/hardware-passthrough-box.md, and a realscripts/install-pi.shwith a systemd unit.c5d895d): a solder-free beginner build guide with priced parts list, and a microcontroller guide (Arduino serial via the existingrs232_sharpprotocol subset, ESP32/ESP8266 Wi-Fi TCP via pure-TOMLnetwork_ipconfig, Pico notes, minimum requirements).Why
Implements the whole
docs/roadmap.mdfor 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 testpasses — ruff clean, strict mypy clean acrosssrc, 195 tests passing (CI also runs all three)file_replayfixture 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)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 withadhush probe/adhush ir-teston real sets.🤖 Generated with Claude Code
https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
Generated by Claude Code