diff --git a/.gitignore b/.gitignore index 49308ac..f528046 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,10 @@ Thumbs.db *~ .idea/ .vscode/ + +# ============================================================ +# Python bytecode (bridges/, twitch-bot/) +# ============================================================ +__pycache__/ +*.pyc +*.pyo diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9f16e61 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "station"] + path = station + url = git@github.com:golem15com/html-ophi118-rig.git diff --git a/CLAUDE.md b/CLAUDE.md index a5cf85c..aaf54a6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,15 +2,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## Current state (read this first) + +This is the active OBS Studio config dir for the **ophi118** Twitch streaming rig. It hosts: five scenes (Landing, Project Loading, Game, Desktop, Good Bye), four custom HTML/CSS/JS overlays rendered as `browser_source`, an MPD-based audio architecture with a state bridge to OBS WebSocket, and a Twitch chat bot that controls MPD via `!skip`. Two rigs share this codebase via a single git repo: **Ignia** (current) and **Midgolem** (deployment target). Bring up a new rig by running `bash scripts/deploy-rig.sh` after `git clone`. All `.sh` automation lives in `scripts/`; everything Python lives where it serves (`bridges/`, `twitch-bot/`). + ## What this directory is Active OBS Studio user config for the **Flatpak** install (`com.obsproject.Studio` from Flathub). Path: `~/.config/obs-studio/`. **Why `~/.config/` and not `~/.var/app/com.obsproject.Studio/config/obs-studio/`?** The Flatpak ships with `filesystems=host` (full host access, see `flatpak info --show-permissions com.obsproject.Studio`). With that permission, Flatpak does **not** indirect XDG paths into the per-app private dir — OBS sees the real `$HOME/.config/` and uses `~/.config/obs-studio/` directly, like a native install would. -A ghost copy lives at `~/.var/app/com.obsproject.Studio/config/obs-studio/` — that's the path Flatpak *would* use if `filesystems=host` weren't set. **OBS does not read or write there.** Some files (older CLAUDE.md, an aborted 2026-04 source-id migration, a `landing/` asset duplicate) accumulated there from work that targeted the wrong path. Treat it as orphaned; don't edit it expecting OBS to pick changes up. The 2026-04 migration script described below ran against the ghost dir and left this active dir un-migrated until 2026-04-25 23:09. +A ghost copy lives at `~/.var/app/com.obsproject.Studio/config/obs-studio/` — that's the path Flatpak *would* use if `filesystems=host` weren't set. **OBS does not read or write there.** Some files (older CLAUDE.md, an aborted 2026-04 source-id migration, a `landing/` asset duplicate) accumulated there from work that targeted the wrong path. Treat it as orphaned; don't edit it expecting OBS to pick changes up. -The user works here to (a) edit scene collections directly as JSON and (b) build/iterate stream overlays as local HTML/CSS/JS rendered via the browser source. +The user works here to (a) edit scene collections directly as JSON, (b) build/iterate stream overlays as local HTML/CSS/JS rendered via the browser source, and (c) maintain the audio + chat-bot infrastructure that surrounds OBS. ## How OBS is launched @@ -18,16 +22,175 @@ The user works here to (a) edit scene collections directly as JSON and (b) build flatpak run com.obsproject.Studio ``` -Or via the app menu ("OBS Studio"). The native `obs` binary still exists from `pacman` but should be considered dead — `obs-studio` and `obs-pipewire-audio-capture-bin{,-debug}` are pending removal once the user is fully comfortable with Flatpak. +Or via the app menu ("OBS Studio"). The native `obs` binary still exists from `pacman` but should be considered dead — `obs-studio` and `obs-pipewire-audio-capture-bin{,-debug}` are pending removal. + +## Layout — what lives where + +**OBS-owned (don't edit while OBS is running — see "OBS overwrites on exit"):** +- `user.ini` / `global.ini` — selects active scene collection and profile, plus window state. `user.ini` is the live one (currently collection `Default Stream HUD`, profile `ophi118`). Don't touch geometry/dock-state lines (huge base64 blobs OBS owns). +- `basic/scenes/.json` — **the scene collection**. One file = one full set of scenes + sources + transitions + audio routing. Active: `Default_Stream_HUD.json` (display name `Default Stream HUD`). Scenes inside: `Landing`, `Project Loading`, `Game`, `Desktop`, `Good Bye`. +- `basic/profiles//` — encoder, streaming service, recording settings (separate from scenes). Active profile: `ophi118`. +- `plugin_config//` — per-plugin settings. `obs-websocket/config.json` holds the WebSocket password — secret. +- `plugin_manager/modules.json` — third-party plugins OBS loads. Currently lists only `linux-pipewire-audio` (the bundled-into-flatpak version, same source ids as the AUR plugin it replaced). +- `logs/YYYY-MM-DD HH-MM-SS.txt` — one per launch. Most recent is the source of truth for "why did source X fail to load" or "what plugin crashed". `MaxLogs=10`. +- `profiler_data/` — perf traces. Ignore unless profiling. +- `.sentinel/run_` — running-instance lockfile; safe to delete only if no OBS process is alive. + +**Custom overlays (browser sources rendering local HTML):** +- `landing/index.html` — Fallout-style "PLEASE STAND BY" overlay. Self-contained: HUD + cyan ring + accent triangle + footer + animated static/scanlines/vignette. Reads `landing/telemetry.js` for the rig and hardware info shown on screen. +- `landing/static-hum.wav` — 12-second seamless CRT static loop, generated via `ffmpeg -filter_complex` (white+brown noise + 60Hz hum, filtered + crossfaded). Loaded by an `ffmpeg_source`. +- `landing/telemetry.json` / `telemetry.js` — hardware + OBS + rig snapshot. JSON is hand-editable (or interactively curated via `scripts/telemetry.sh --collect`), JS is the auto-generated ` + + + + + + + diff --git a/landing/index.html b/landing/index.html index 5e15ebb..6a7c00d 100644 --- a/landing/index.html +++ b/landing/index.html @@ -50,16 +50,25 @@ body { .hud > .group:nth-child(2) { justify-self: center; } .hud > .group:nth-child(3) { justify-self: end; } .hud .dim { color: var(--hud-dim); } +.hud .group > span { display: inline-flex; align-items: center; gap: 12px; } .hud .led { display: inline-block; width: 11px; height: 11px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 10px var(--accent); - margin-right: 12px; - vertical-align: middle; + /* Small upward nudge: flex centers on line-box mid, but caps-only text + reads centered around cap-height mid which sits a touch higher. */ + margin-top: -0.08em; animation: rec-blink 1.6s ease-in-out infinite; } +/* The ▮/▯ glyphs in DejaVu Sans Mono are designed centered on x-height, + not cap-height, so against all-caps SIGNAL their visual middle sits low. + Lift by ~(cap-mid − x-mid) ≈ 0.10em to put the glyph center on cap center. */ +#signal { + display: inline-block; + transform: translateY(-0.10em); +} @keyframes rec-blink { 0%, 55% { opacity: 1; } 65%, 100% { opacity: 0.2; } diff --git a/landing/telemetry.json b/landing/telemetry.json index 4007284..54a4938 100644 --- a/landing/telemetry.json +++ b/landing/telemetry.json @@ -1,21 +1,22 @@ { - "collectedAt": "2026-04-25T22:10:52Z", + "collectedAt": "2026-04-26T20:17:38Z", + "rig": "Ignia", "cpu": { - "model": "AMD Ryzen", + "model": "AMD Ryzen 5 2600 Six-Core Processor", "threads": 12 }, "mem": { - "totalGB": 32 + "totalGB": 31.3 }, "host": { - "kernel": "Arch Linux 6.18.23-1-lts" + "kernel": "6.18.23-1-lts" }, "gpu": { "name": "NVIDIA GeForce RTX 3060 Ti", "vramTotalMB": 8192 }, "obs": { - "profile": "Untitled", + "profile": "ophi118", "renderer": "OpenGL", "outputMode": "Advanced", "canvas": { "w": 2560, "h": 1336 }, diff --git a/loading/index.html b/loading/index.html index 2c4c8e0..9d59822 100644 --- a/loading/index.html +++ b/loading/index.html @@ -17,7 +17,7 @@ --term-fg-bright: #97f99a; --term-fg-dim: #2c8d2f; --term-glow: rgba(80, 220, 100, 0.55); - --term-edge: rgba(80, 220, 100, 0.30); + --term-edge: rgba(80, 220, 100, 0.45); --mono: 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', monospace; --display: 'DejaVu Sans', 'Liberation Sans', 'Helvetica', 'Arial', sans-serif; @@ -58,16 +58,25 @@ body { .hud > .group:nth-child(2) { justify-self: center; } .hud > .group:nth-child(3) { justify-self: end; } .hud .dim { color: var(--hud-dim); } +.hud .group > span { display: inline-flex; align-items: center; gap: 12px; } .hud .led { display: inline-block; width: 11px; height: 11px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 10px var(--accent); - margin-right: 12px; - vertical-align: middle; + /* Small upward nudge: flex centers on line-box mid, but caps-only text + reads centered around cap-height mid which sits a touch higher. */ + margin-top: -0.08em; animation: rec-blink 1.6s ease-in-out infinite; } +/* The ▮/▯ glyphs in DejaVu Sans Mono are designed centered on x-height, + not cap-height, so against all-caps SIGNAL their visual middle sits low. + Lift by ~(cap-mid − x-mid) ≈ 0.10em to put the glyph center on cap center. */ +#signal { + display: inline-block; + transform: translateY(-0.10em); +} @keyframes rec-blink { 0%, 55% { opacity: 1; } 65%, 100% { opacity: 0.2; } @@ -81,16 +90,17 @@ body { } .terminal { - width: 64vw; - max-width: 1500px; - height: 100%; - min-height: 380px; + width: 60vw; + max-width: 1380px; + height: 58vh; + max-height: 740px; + min-height: 400px; position: relative; overflow: hidden; background: var(--term-bg); - border: 1px solid var(--term-edge); + border: 2px solid var(--term-edge); border-radius: 6px; - padding: 32px 40px; + padding: 28px 36px; font-family: var(--mono); font-size: 26px; line-height: 1.55; @@ -158,11 +168,15 @@ body { } /* ───────── Big countdown ───────── */ +/* text-indent compensates for trailing letter-spacing on the last char, + which would otherwise be included in the centered inline box and shift + visible text left by half the letter-spacing value. */ .countdown { text-align: center; } .countdown .label { font-family: var(--mono); font-size: 22px; letter-spacing: 0.45em; + text-indent: 0.45em; color: var(--hud-dim); margin-bottom: 12px; } @@ -171,6 +185,7 @@ body { font-size: 132px; font-weight: 900; letter-spacing: 0.10em; + text-indent: 0.10em; line-height: 1; color: var(--ink); text-shadow: 0 0 22px rgba(79, 210, 255, 0.18); @@ -181,6 +196,7 @@ body { font-family: var(--mono); font-size: 22px; letter-spacing: 0.45em; + text-indent: 0.45em; color: var(--hud-dim); } @keyframes cd-pulse { @@ -191,6 +207,7 @@ body { color: var(--term-fg); text-shadow: 0 0 32px var(--term-glow); letter-spacing: 0.18em; + text-indent: 0.18em; } .countdown.ready .sub { color: var(--term-fg-bright); } .countdown.ready .label { color: var(--term-fg-dim); } @@ -301,9 +318,12 @@ body {
- + + + - @@ -356,9 +376,10 @@ body { const cdMin = data.countdownMin ?? 5; const PROMPT = 'OPHI-118://> '; + const rigName = (window.__TEL?.rig || 'unknown').toUpperCase(); const lines = [ { kind: 'cmd', text: 'loadproject --manifest' }, - { kind: 'out', text: 'initializing transmission envelope...' }, + { kind: 'out', text: `detected rig :: ${rigName} - initializing transmission...` }, { kind: 'gap' }, { kind: 'out', text: `target :: ${upper(data.game) || '—'}` }, ...(data.subtitle ? [{ kind: 'out', text: `mode :: ${upper(data.subtitle)}` }] : []), @@ -450,7 +471,7 @@ body { let lastTrackIndex = null; async function connectMusic() { if (!window.__OBSWS) { - logBeforeNp('[audio] vendor/obs-config.js missing — run setup.sh', 'term-dim'); + logBeforeNp('[audio] vendor/obs-config.js missing — run scripts/setup.sh', 'term-dim'); return; } try { @@ -482,7 +503,7 @@ body { await typeCommand('mpd --queue ~/playlists/stream-mix'); if (allTracks.length === 0) { - append('> ', '[audio] no tracks queued — run loading/playlist.sh', 'term-dim'); + append('> ', '[audio] no tracks queued — run scripts/playlist.sh', 'term-dim'); return; } diff --git a/music/index.html b/music/index.html index 35cfa81..3d57e4c 100644 --- a/music/index.html +++ b/music/index.html @@ -86,7 +86,7 @@ let obs = null; async function connectOBS() { if (!window.__OBSWS) { - setWs('vendor/obs-config.js MISSING — run setup.sh', 'err'); + setWs('vendor/obs-config.js MISSING — run scripts/setup.sh', 'err'); return; } try { @@ -221,7 +221,7 @@ if (queue.length > 0) { playCurrent(); } else { - console.warn('[music] no tracks — run loading/playlist.sh'); + console.warn('[music] no tracks — run scripts/playlist.sh'); } diff --git a/profile/Ignia Spec.md b/profile/Ignia Spec.md new file mode 100644 index 0000000..425ec6e --- /dev/null +++ b/profile/Ignia Spec.md @@ -0,0 +1,30 @@ +**IGNIA SPECIFICATION** + +| ![][image1] | ![][image2] | ![][image3] | +| :---: | :---: | :---: | +| ![][image4] | ![][image5] | ![][image6] | +| | ![][image7] | | + +| CPU | [AMD Ryzen 5 2600 6x3.9GHz / 2nd Gen](https://www.amd.com/en/products/cpu/amd-ryzen-5-2600) | +| :---- | :---- | +| **Cooling** | [SILENTIUMPC Spartan 3 PRO RGB HE1024](https://www.silentiumpc.com/en/product/spartan-3-pro-rgb-he1024/) | +| **RAM** | [ADATA XPG Gammix D30 4x8GB DDR4 / 3200 MhZ](https://www.adata.com/en/feature/588) | +| **GPU** | [~~AMD Radeon RX 570 Expedition 4GB DDR5 PCIe x16~~](https://www.asus.com/Graphics-Cards/EX-RX570-O4G/) burned :( Lspc [~~GeForce GTX 1650 4GB DDR5~~](https://www.x-kom.pl/p/492147-karta-graficzna-nvidia-gigabyte-geforce-gtx-1650-oc-4gb-gddr5.html) [GeForce RTX 3060Ti 8GB DDR6 PCIe x16](https://www.x-kom.pl/p/1091156-karta-graficzna-nvidia-inno3d-geforce-rtx-3060-ti-x3-oc-8gb-gddr6x.html) | +| **Motherboard** | [MSI B450-A PRO](https://www.msi.com/Motherboard/B450-A-PRO) | +| **HDD** | [SSD GOODRAM CX400 512GB](https://www.goodram.com/en/products/ssd-cx400-sata-iii-25/) **x2** HDD SEAGATE ST3250318AS 512GB HDD TOSHIBA DT01ACA0 256 GB | +| **Power Supply** | [SILENTIUMPC Vero M2 Bronze 600W](https://www.silentiumpc.com/en/product/vero-m2-bronze-600w/) | +| **Chassis** | [SILENTIUMPC Regnum RG4T RGB](https://www.silentiumpc.com/en/product/regnum-rg4t-rgb/) | + +[image1]: + +[image2]: + +[image3]: + +[image4]: + +[image5]: + +[image6]: + +[image7]: \ No newline at end of file diff --git a/profile/Midgolem Spec.md b/profile/Midgolem Spec.md new file mode 100644 index 0000000..1e58561 --- /dev/null +++ b/profile/Midgolem Spec.md @@ -0,0 +1,32 @@ +**MIDGOLEM SPECIFICATION** + +| ![][image1] | ![][image2] | ![][image3] | +| :---: | :---: | :---: | +| ![][image4] | ![][image5] | ![][image6] | +| | ![][image7] | | + +| CPU | [AMD Ryzen 9 7900 12x5.4GHz](https://www.amd.com/en/products/processors/desktops/ryzen/7000-series/amd-ryzen-9-7900.html) | +| :---- | :---- | +| **COOLING** | [AMD Wraith Prism](https://www.amd.com/en/products/processors/ecosystem/cpu-cooler-solutions.html) | +| **RAM** | [Patriot Viper Venom 4x16GB DDR5 6000MHz](https://viper.patriotmemory.com/products/viper-venom-ddr5-performance-ram) | +| **GPU** | [NVidia GeForce RTX4060Ti 16GB](https://www.nvidia.com/pl-pl/geforce/graphics-cards/40-series/rtx-4060-4060ti/) [Nvidia Tesla M10 32GB 0H56H0](https://computerowiec.com.pl/obraz-i-grafika/17841-karta-akcelerator-dell-nvidia-tesla-m10-32gb-gddr5-0h56h0-0h56h0.html) (odłączona ![][image8]) | +| **Motherboard** | [ASRock X870 Steel Legend](https://www.asrock.com/mb/AMD/X870%20Steel%20Legend%20WiFi/index.pl.asp) | +| **HDD** | [WD Black SN770 2TB M.2 2280 PCIE x4](https://shop.sandisk.com/en-kz/products/ssd/internal-ssd/wd-black-sn770-nvme-ssd?sku=WDS200T3X0E-00B3N0) | +| **Power Supply** | [MSI MAG A750GL PCIE5 750W](https://pl-store.msi.com/products/zasilacz-mag-a750gl-pcie5-power-supply) | +| **Chassis** | [be quiet\! Pure Base 500DX](https://www.bequiet.com/pl/case/1854) | + +[image1]: + +[image2]: + +[image3]: + +[image4]: + +[image5]: + +[image6]: + +[image7]: + +[image8]: \ No newline at end of file diff --git a/profile/logitech.png b/profile/logitech.png new file mode 100644 index 0000000..db57989 Binary files /dev/null and b/profile/logitech.png differ diff --git a/profile/steelseries.png b/profile/steelseries.png new file mode 100644 index 0000000..c4ca1b8 Binary files /dev/null and b/profile/steelseries.png differ diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100755 index 0000000..8fd0463 --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# scripts/clean.sh — audit which .webm sources in playlist/ already have a +# converted .m4a sibling (produced by scripts/convert.sh). Read-only; deletes +# nothing. Run from anywhere — operates on $OBS_DIR/playlist/. +set -euo pipefail + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OBS_DIR="$(cd "$DIR/.." && pwd)" +PLAYLIST="$OBS_DIR/playlist" + +[[ -d "$PLAYLIST" ]] || { echo " ✗ $PLAYLIST does not exist" >&2; exit 1; } +cd "$PLAYLIST" + +shopt -s nullglob +for f in *.webm; do + m="${f%.webm}.m4a" + [[ -f "$m" ]] && echo "OK $f → $m" || echo "MISS $f (no m4a yet, keep!)" +done diff --git a/loading/convert.sh b/scripts/convert.sh similarity index 97% rename from loading/convert.sh rename to scripts/convert.sh index ef5c2da..4c10c7b 100755 --- a/loading/convert.sh +++ b/scripts/convert.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# loading/convert.sh — strip video tracks out of audio files in +# scripts/convert.sh — strip video tracks out of audio files in # ~/.config/obs-studio/playlist/ and re-encode to AAC m4a in place. # # WHY: yt-dlp .webm downloads include 1080p video. Chromium's