Jakub Zych b47f8fca83 README: add human-facing root README
CLAUDE.md is the agent-facing repo guide; this is the same project
described for a human walking up to the repo cold. Covers the
architecture, the daily-operation commands, the fresh-rig bring-up,
the critical gotchas (OBS scene-JSON-on-exit, pipewire-pulse restart,
the Flatpak detection trap), and pointers into docs/ and archived/.
2026-05-21 13:32:25 +02:00
2026-04-30 00:15:23 +02:00
2026-04-28 12:14:33 +02:00
2026-04-28 12:14:33 +02:00
2026-05-21 13:32:25 +02:00
2026-04-29 22:57:12 +02:00

OPHI-118

OBS Studio rig for the ophi118 Twitch channel.

This repo is ~/.config/obs-studio for two physical machines, Ignia and Midgolem. It holds the OBS scene collection, profile, plugin config, the Laravel webapp that renders all on-stream overlays, the MPD-backed audio stack with its OBS-WebSocket bridge, and the Twitch chat bot that drives playback from chat.

┌──────────────┐    mpd:state    ┌──────────────────────────┐
│  MPD         │ ──────────────► │  bridges/mpd-state.py    │
│ (PipeWire    │                 │  (systemd --user)        │
│  null-sink)  │ ◄────────────── │                          │
└──────────────┘    mpc next     └──────────────┬───────────┘
                      ▲                         │ OBS WS CustomEvent
                      │                         ▼
                      │              ┌──────────────────────┐
              ┌───────┴───────┐      │  obs-websocket :4455 │
              │ twitch-bot.py │◄────►│  (Studio plugin)     │
              │ (!skip/!info) │      └──────────┬───────────┘
              └───────────────┘                 │ on(mpd:state)
                                                ▼
                                    ┌───────────────────────────┐
                                    │  webapp/  (Laravel)       │
                                    │  http://127.0.0.1:1118/   │
                                    │  ┌─────────────────────┐  │
                                    │  │ /landing /loading   │  │
                                    │  │ /game /desktop      │  │
                                    │  │ /goodbye            │  │
                                    │  │ /music-box[/...]    │  │
                                    │  │ /music-daemon       │  │
                                    │  │ POST /cmd/{type}    │  │
                                    │  └─────────────────────┘  │
                                    └───────────────────────────┘
                                                ▲
                                                │ browser_source
                                                │
                                       ┌────────┴────────┐
                                       │   OBS Studio    │
                                       │   (Flatpak)     │
                                       └─────────────────┘

Stack

  • OBS Studio (Flatpak, com.obsproject.Studio) with filesystems=host permission so ~/.config/obs-studio/ is read directly, not indirected through the per-app private dir.
  • Laravel 13 webapp under webapp/, served by php artisan serve on 127.0.0.1:1118 and managed by supervisord. Routes return Blade-rendered overlay pages and a small data API (/data/playlist.js, /cover.jpg, /track?p=…, POST /cmd/{skip|prev|pause|resume}).
  • MPD (system service) is the audio player. A small Python bridge (bridges/mpd-state.py, obs-mpd-bridge.service) listens to MPD and re-broadcasts state as mpd:state CustomEvents on the OBS WS bus.
  • Twitch chat bot (twitch-bot/, obs-twitch-bot.service) handles !skip / !queue / !info by talking to MPD directly.

Layout

.
├── basic/                  OBS scene collection + profile
├── plugin_config/          obs-websocket password (gitignored)
├── plugin_manager/         third-party plugin manifest
├── bridges/                MPD → OBS WS state bridge + cover.jpg cache
├── twitch-bot/             chat bot (git submodule)
├── station/                public rig-spec page (git submodule)
├── webapp/                 Laravel app — every overlay served from here
│   ├── app/
│   │   ├── Console/Commands/   rig:setup, rig:telemetry, rig:loading,
│   │   │                       rig:playlist, rig:cmd
│   │   ├── Http/Controllers/   Overlays/{Landing,Loading,Game,…}, Data,
│   │   │                       MusicCommand, Audio
│   │   ├── Services/           ObsWsClient, TwitchHelix, HardwareSnapshot
│   │   └── Support/RigData.php
│   ├── resources/views/    Blade layouts + partials + per-scene overlays
│   ├── public/             css/hud.css, js/{hud,crt-static,obs-ws-*}.js,
│   │                       audio/static-hum.wav
│   └── storage/data/       rig:* commands write JSON here
├── scripts/
│   ├── deploy-rig.sh                idempotent bring-up (run on fresh rig)
│   ├── obs-webapp.supervisord.conf  → /etc/supervisor.d/obs-webapp.conf
│   ├── rewrite-scene-urls.py        one-shot migration helper
│   ├── convert.sh, clean.sh         yt-dlp .webm → .m4a re-encode tools
├── archived/               pre-webapp artifacts (rollback only)
├── docs/                   deeper context — audio, scenes, deployment, history
├── CLAUDE.md               agent-facing repo guide (orientation for AI tools)
└── README.md               you are here

Daily operation

# server health
sudo supervisorctl status obs-webapp
tail -F webapp/storage/logs/supervisord.{out,err}.log

# restart after route / controller / .env changes
sudo supervisorctl restart obs-webapp

# blade-view-only changes: no restart needed — just right-click the source
# in OBS → Refresh cache

# refresh a per-session manifest before going live (game / mode / countdown)
cd webapp && php artisan rig:loading

# reindex the music library (after dropping new tracks into ~/HDD/Music)
cd webapp && php artisan rig:playlist

# re-snapshot hardware/OBS profile telemetry (kernel/GPU/encoder changes)
cd webapp && php artisan rig:telemetry --collect

# CLI control of music playback (mirrors the Twitch chat commands)
cd webapp && php artisan rig:cmd skip
# or
curl -X POST http://127.0.0.1:1118/cmd/skip

# rotate the obs-websocket password (or after a fresh rig install)
cd webapp && php artisan rig:setup
sudo supervisorctl restart obs-webapp

The OBS scene browser sources point at http://127.0.0.1:1118/<route>, so they always pick up the current state of the webapp on next Refresh cache.

Fresh rig (Midgolem)

git clone --recurse-submodules <repo> ~/.config/obs-studio
cd ~/.config/obs-studio
bash scripts/deploy-rig.sh

deploy-rig.sh is idempotent. It installs the Arch packages it needs (pacman -S, AUR helper for the Sansation font), seeds PipeWire null sinks for the MPD and Discord audio paths, writes the systemd user units for the bridge + bot, installs the supervisord program, runs composer install for the webapp, and calls php artisan rig:setup / rig:telemetry --collect to materialize per-rig state. Then fill in the Twitch tokens it leaves you prompts for and you're done.

Run it again any time after a git pull — it'll only act on what's drifted.

Critical gotchas

  • Close OBS before editing basic/scenes/*.json, global.ini, or user.ini. OBS rewrites them on exit and will clobber your edits.
  • Never restart pipewire-pulse.service while OBS is running. OBS does not auto-rebind pulse_output_capture sources when sinks reload; meters go flat and stream audio cuts. Stop OBS first, or load modules ad-hoc with pactl.
  • OBS is the Flatpak (com.obsproject.Studio), not the pacman obs binary. Detect with flatpak ps --columns=application | grep -qx com.obsproject.Studiopgrep -fa 'flatpak run …' lies (the wrapper exits, the sandbox keeps running).
  • Secrets are per-rig, never copy between rigs: twitch-bot/.env*, plugin_config/obs-websocket/config.json, basic/profiles/*/service.json, webapp/.env. Everything else is portable.

Deeper reading

  • docs/audio.md — PipeWire null-sink architecture, MPD config, the mpd:state event payload, systemd user units.
  • docs/scenes-and-overlays.md — scene JSON internals, source ids, HUD browser-resolution constraints, camera-frame OBS WS sync.
  • docs/deployment.md — Twitch identity + token scopes, per-rig vs portable state, Flatpak permission story.
  • docs/history.md — migration history: linuxbrowser → CEF, browser-daemon → MPD bridge, Discord audio split, file:// → HTTP webapp.
  • docs/cheatsheet.md — one-liners.
  • archived/README.md — pre-webapp scene HTML / bash helpers, kept around for rollback.

License

Personal config; nothing here is a published product. The Blade templates, CSS, and scripts are MIT-style — copy what's useful.

Description
No description provided
Readme 3.5 MiB
Languages
HTML 31.5%
Blade 25.4%
PHP 18.9%
Shell 11.4%
Python 5.5%
Other 7.3%