Initial commit: OBS scene collection, profile, landing overlay
Tracked: scene JSON (Untitled + pre-migration backups), landing/ overlay (HTML + static-hum.wav), profile encoder/basic settings, rtmp-services metadata, modules list, ini files, CLAUDE.md. Ignored via .gitignore: stream keys (**/service.json), obs-websocket password (plugin_config/obs-websocket/), logs/, profiler_data/, .sentinel/, OBS-managed *.bak and *.json.v1 sidecars.
This commit is contained in:
58
.gitignore
vendored
Normal file
58
.gitignore
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
# ============================================================
|
||||
# SECRETS — never commit these. OBS stores live credentials here.
|
||||
# ============================================================
|
||||
|
||||
# Twitch (and other RTMP) stream keys — `settings.key` field.
|
||||
# Path: basic/profiles/<profile>/service.json
|
||||
**/service.json
|
||||
**/service.json.bak
|
||||
|
||||
# obs-websocket server password (plugin_config/obs-websocket/config.json
|
||||
# contains `server_password`). Block the whole dir defensively.
|
||||
plugin_config/obs-websocket/
|
||||
|
||||
# Defensive blanket — anything obviously credential-shaped.
|
||||
*.key
|
||||
*.pem
|
||||
*.secret
|
||||
*.token
|
||||
*credentials*
|
||||
*.env
|
||||
.env*
|
||||
|
||||
# ============================================================
|
||||
# OBS runtime / generated state — no value in version control
|
||||
# ============================================================
|
||||
|
||||
# One log per launch; OBS rotates these itself (MaxLogs=10).
|
||||
logs/
|
||||
|
||||
# Performance traces.
|
||||
profiler_data/
|
||||
|
||||
# Running-instance lockfile.
|
||||
.sentinel/
|
||||
|
||||
# Crash/safe-mode artefacts.
|
||||
crashes/
|
||||
safe_mode/
|
||||
|
||||
# ============================================================
|
||||
# OBS-managed sidecars — OBS rewrites these on every save.
|
||||
# Tracking them produces noisy diffs and they're not real history.
|
||||
# Manual migration backups (.pre-*) are intentionally NOT ignored —
|
||||
# they were created by hand and capture meaningful pre-change state.
|
||||
# ============================================================
|
||||
*.bak
|
||||
*.json.v1
|
||||
|
||||
# ============================================================
|
||||
# Editor / OS noise
|
||||
# ============================================================
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.idea/
|
||||
.vscode/
|
||||
113
CLAUDE.md
Normal file
113
CLAUDE.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
## How OBS is launched
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
## Migration history (matters when reading old scene JSON or AI-written files)
|
||||
|
||||
- **Was:** native Arch `obs-studio` (no browser support) + AUR `obs-linuxbrowser-source` (id: `linuxbrowser-source`).
|
||||
- **Is:** Flatpak `com.obsproject.Studio` with bundled CEF browser source (id: `browser_source`).
|
||||
- **2026-04 attempt (misdirected):** A scripted migration rewrote four sources (`Tip4Stream Scena Domyślna`, `Twitch Viewers`, `StreamLabs Jar`, `Landing — Stand By (Browser)`) from `linuxbrowser-source` → `browser_source` — but ran against the **ghost** `~/.var/app/.../obs-studio/` directory. OBS never read those edits.
|
||||
- **2026-04-25 23:09 (correct):** Same migration applied to the **active** `~/.config/obs-studio/basic/scenes/Untitled.json`. Backup at `basic/scenes/Untitled.json.pre-cef-migration`. Settings keys: `url`, `width`, `height`, `fps` map directly; CEF requires `fps_custom: true`, `reroute_audio: false`, `restart_when_active: false`, `shutdown: false`, `webpage_control_level: 1`. Stale `linuxbrowser.reloadpage` hotkey keys were also dropped.
|
||||
- If you encounter `linuxbrowser-source` anywhere — in a backup, a doc, a paste — assume it's pre-migration and migrate the same way.
|
||||
- Older backup of pre-rebuild scene JSON: `basic/scenes/Untitled.json.pre-landing-rebuild`.
|
||||
|
||||
## Critical gotcha: OBS overwrites on exit
|
||||
|
||||
OBS rewrites `basic/scenes/<collection>.json`, `global.ini`, and `user.ini` on shutdown (and periodically). **Close OBS before editing these files**, or the next OBS exit will clobber the edit. After editing, re-launch OBS to load the change.
|
||||
|
||||
OBS itself maintains `.bak` (previous save) and `.json.v1` (pre-version-migration) sidecars in `basic/scenes/`. Those are not git history — leave them alone unless explicitly recovering.
|
||||
|
||||
## Flatpak permissions — what the sandbox allows
|
||||
|
||||
OBS flatpak ships with `filesystems=host` by default — full host access. Asset paths like `/home/jin/HDD/Images/Gifs/stamd.png` and `/home/jin/cloud.jakubzych.com/_img/logos/dgw.png` work without `flatpak override`. PipeWire audio works via `xdg-run/pipewire-0`. NVIDIA acceleration works via `org.freedesktop.Platform.GL.nvidia-595-58-03` (auto-installed). If you ever need to widen permissions: `flatpak override --user com.obsproject.Studio --<flag>=<value>`.
|
||||
|
||||
`filesystems=host` is also the reason this config lives at `~/.config/obs-studio/` instead of the per-app sandboxed path — see "What this directory is" above.
|
||||
|
||||
## Layout — what lives where
|
||||
|
||||
- `user.ini` / `global.ini` — selects active scene collection and profile, plus window state. Currently both `Untitled`. Don't touch geometry/dock-state lines (huge base64 blobs OBS owns).
|
||||
- `basic/scenes/<name>.json` — **the scene collection**. One file = one full set of scenes + sources + transitions + audio routing. Active: `Untitled.json`.
|
||||
- `basic/profiles/<name>/` — encoder, streaming service, recording settings (separate from scenes).
|
||||
- `plugin_config/<plugin-id>/` — 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`.
|
||||
- `landing/` — overlays for the Landing scene (custom; not an OBS convention):
|
||||
- `index.html` — Fallout-style "PLEASE STAND BY" rendered by browser source. Self-contained, no external deps. Edit, then click "Refresh cache" on the browser source to see changes.
|
||||
- `static-hum.wav` — 12s seamless CRT static loop, generated via `ffmpeg -filter_complex` (anoisesrc white+brown+60Hz hum, filtered + crossfaded). Loaded by a Media source.
|
||||
- `profiler_data/` — perf traces. Ignore unless profiling.
|
||||
- `.sentinel/run_<uuid>` — running-instance lockfile; safe to delete only if no OBS process is alive.
|
||||
|
||||
## Scene collection JSON — structure to know before editing
|
||||
|
||||
Top-level keys in `basic/scenes/Untitled.json`:
|
||||
|
||||
- `name` — collection name (matches filename stem).
|
||||
- `sources` — **flat array of every source, scene, and group**. Scenes are entries here with `"id": "scene"`; their `settings.items[]` references child sources **by name string**, not uuid. Renaming a source means updating every scene that references it.
|
||||
- `scene_order` — array of `{name}` defining sidebar order.
|
||||
- `current_scene` / `current_program_scene` — strings matching scene names.
|
||||
- `transitions`, `quick_transitions`, `current_transition`, `transition_duration` — transition setup.
|
||||
- `groups` — group definitions.
|
||||
- `modules` — per-source-plugin state.
|
||||
- `version`, `migration_resolution`, `resolution`, `canvases` — collection schema + canvas sizing. Canvas: **2560×1336**.
|
||||
|
||||
Each source entry has: `name`, `uuid`, `id`, `versioned_id`, `settings` (plugin-specific), plus filters/transform/audio fields. Scene items inside `settings.items[]` reference sources via `source_uuid`. **Use uuids when adding new items programmatically; use names when reading.**
|
||||
|
||||
Source `id` values currently in use:
|
||||
|
||||
- `scene` — a scene
|
||||
- `pipewire-screen-capture-source` — Wayland/PipeWire screen/window capture
|
||||
- `pipewire-camera-source` — PipeWire camera
|
||||
- `v4l2_input` — V4L2 webcam (legacy path)
|
||||
- `xshm_input` — X11 screen capture (legacy path)
|
||||
- `browser_source` — CEF browser (post-migration; was `linuxbrowser-source`)
|
||||
- `image_source` — static image
|
||||
- `text_ft2_source` — FreeType2 text
|
||||
- `ffmpeg_source` — media file (audio/video) via ffmpeg
|
||||
|
||||
Active scenes: `Landing` (current), `Game`, `Desktop`.
|
||||
|
||||
When editing scene JSON: keep `uuid` values stable across edits, preserve 4-space indentation, and validate with `python3 -m json.tool` before re-launching OBS.
|
||||
|
||||
## Landing scene — current state
|
||||
|
||||
Composition (full-bleed at 2560×1336):
|
||||
|
||||
1. `Landing — Stand By (Browser)` (`browser_source`) — points at `file:///home/jin/.config/obs-studio/landing/index.html`. Renders: top HUD (`TRANSMISSION OPHI-118 / SIGNAL bars / UTC clock`), centered cyan-ring + red-orange triangle mark, big `PLEASE STAND BY`, footer (`CH 118.0 MHz · NTSC · AUDIO ACTIVE`), animated static + scanlines + vignette + occasional flicker.
|
||||
2. `Landing — Static Hum` (`ffmpeg_source`) — looped 12s WAV at volume 0.55.
|
||||
|
||||
Palette (in CSS vars at top of `landing/index.html`): `--bg #07080d`, `--hud #4fd2ff`, `--accent #e63a2e`, `--warn #ffd000` (matches the user's ophi118 Twitch identity).
|
||||
|
||||
To iterate: edit HTML → in OBS, right-click the browser source → "Refresh cache". No restart needed.
|
||||
|
||||
## Working with this directory
|
||||
|
||||
- **Confirm OBS is using THIS dir, not the ghost** before assuming an edit will land. While OBS is running: `ls -la /proc/$(pgrep -fx obs)/cwd` and check `lsof -p $(pgrep -fx obs) | grep scenes`. Or compare mtimes after an OBS session — the file OBS rewrites is the live one.
|
||||
- **Inspecting a scene collection:** `python3 -m json.tool basic/scenes/Untitled.json | less`. List scenes: `python3 -c "import json; d=json.load(open('basic/scenes/Untitled.json')); print([s['name'] for s in d['scene_order']])"`.
|
||||
- **Diffing after an OBS session:** OBS rewrites the file even when nothing visibly changed. Expect noisy diffs; focus on `sources[]` and `scene_order` for meaningful changes.
|
||||
- **Backing up before edits:** copy the active `.json` somewhere outside this dir — OBS's own `.bak` will be overwritten on the next save.
|
||||
- **Plugin development:** Flatpak OBS plugins live inside the runtime; you can't drop `.so` files into the host filesystem and have them load. Plugin extensions for the OBS flatpak come as `com.obsproject.Studio.Plugin.<name>` flatpak extensions (search Flathub). If a plugin only has source/AUR distribution, it won't work in Flatpak — that's the tradeoff of this distribution choice.
|
||||
- **WebSocket control:** `plugin_config/obs-websocket/config.json` holds connection settings. Programmatic scene edits while OBS is running should go through obs-websocket — file edits get clobbered on save.
|
||||
|
||||
## User-specific conventions (from global instructions)
|
||||
|
||||
- Never add co-author tags to commit messages.
|
||||
- On any path error, immediately run `pwd` to confirm location.
|
||||
- `ssu` is available for git submodule management.
|
||||
126
basic/profiles/Untitled/basic.ini
Normal file
126
basic/profiles/Untitled/basic.ini
Normal file
@@ -0,0 +1,126 @@
|
||||
[General]
|
||||
Name=Untitled
|
||||
|
||||
[Video]
|
||||
BaseCX=2560
|
||||
BaseCY=1336
|
||||
OutputCX=1920
|
||||
OutputCY=1080
|
||||
FPSType=0
|
||||
FPSCommon=30
|
||||
FPSInt=30
|
||||
FPSNum=30
|
||||
FPSDen=1
|
||||
ScaleType=bicubic
|
||||
ColorFormat=NV12
|
||||
ColorSpace=709
|
||||
ColorRange=Partial
|
||||
SdrWhiteLevel=300
|
||||
HdrNominalPeakLevel=1000
|
||||
|
||||
[Panels]
|
||||
CookieId=48546E60C6A25B1C
|
||||
|
||||
[Output]
|
||||
Mode=Advanced
|
||||
FilenameFormatting=%CCYY-%MM-%DD %hh-%mm-%ss
|
||||
DelayEnable=false
|
||||
DelaySec=20
|
||||
DelayPreserve=true
|
||||
Reconnect=true
|
||||
RetryDelay=2
|
||||
MaxRetries=25
|
||||
BindIP=default
|
||||
IPFamily=IPv4+IPv6
|
||||
NewSocketLoopEnable=false
|
||||
LowLatencyEnable=false
|
||||
|
||||
[AdvOut]
|
||||
RescaleRes=1280x720
|
||||
TrackIndex=1
|
||||
RecType=Standard
|
||||
RecRescaleRes=1280x1024
|
||||
RecTracks=1
|
||||
FFOutputToFile=true
|
||||
FFFormat=mpegts
|
||||
FFFormatMimeType=video/MP2T
|
||||
FFRescaleRes=1280x1024
|
||||
FFVEncoderId=27
|
||||
FFVEncoder=libx264
|
||||
FFAEncoderId=86018
|
||||
FFAEncoder=aac
|
||||
FFAudioMixes=1
|
||||
FFURL=
|
||||
FFExtension=ts
|
||||
FFMCustom=content_type=video/mp2ts ice_genre=fun
|
||||
FFIgnoreCompat=true
|
||||
RecFilePath=/home/jin/Videos/QS
|
||||
RecFileNameWithoutSpace=true
|
||||
RecFormat=mkv
|
||||
RecEncoder=ffmpeg_nvenc
|
||||
Rescale=false
|
||||
FLVTrack=1
|
||||
FFVBitrate=2400
|
||||
FFVGOPSize=249
|
||||
FFFilePath=/run/media/jin/NTFS/recordings
|
||||
VodTrackIndex=2
|
||||
RecMuxerCustom=
|
||||
Encoder=obs_x264
|
||||
RecSplitFileType=Time
|
||||
RecFormat2=mkv
|
||||
ApplyServiceSettings=true
|
||||
UseRescale=false
|
||||
RecUseRescale=false
|
||||
FFUseRescale=false
|
||||
FFABitrate=160
|
||||
Track1Bitrate=160
|
||||
Track2Bitrate=160
|
||||
Track3Bitrate=160
|
||||
Track4Bitrate=160
|
||||
Track5Bitrate=160
|
||||
Track6Bitrate=160
|
||||
RecSplitFileTime=15
|
||||
RecSplitFileSize=2048
|
||||
RecRB=false
|
||||
RecRBTime=20
|
||||
RecRBSize=512
|
||||
AudioEncoder=libfdk_aac
|
||||
RecAudioEncoder=libfdk_aac
|
||||
StreamMultiTrackAudioMixes=1
|
||||
|
||||
[SimpleOutput]
|
||||
UseAdvanced=false
|
||||
StreamEncoder=x264
|
||||
Preset=veryfast
|
||||
FilePath=/home/jin/screencasts
|
||||
RecFormat=mkv
|
||||
RecFormat2=mkv
|
||||
VBitrate=2500
|
||||
ABitrate=160
|
||||
NVENCPreset2=p5
|
||||
RecQuality=Stream
|
||||
RecRB=false
|
||||
RecRBTime=20
|
||||
RecRBSize=512
|
||||
RecRBPrefix=Replay
|
||||
StreamAudioEncoder=aac
|
||||
RecAudioEncoder=aac
|
||||
RecTracks=1
|
||||
RecEncoder=nvenc
|
||||
|
||||
[Twitch]
|
||||
AddonChoice=3
|
||||
|
||||
[Stream1]
|
||||
IgnoreRecommended=false
|
||||
EnableMultitrackVideo=false
|
||||
MultitrackVideoMaximumAggregateBitrateAuto=true
|
||||
MultitrackVideoMaximumVideoTracksAuto=true
|
||||
|
||||
[Audio]
|
||||
MonitoringDeviceId=default
|
||||
MonitoringDeviceName=Default
|
||||
SampleRate=48000
|
||||
ChannelSetup=Stereo
|
||||
MeterDecayRate=23.53
|
||||
PeakMeterType=0
|
||||
1
basic/profiles/Untitled/recordEncoder.json
Normal file
1
basic/profiles/Untitled/recordEncoder.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
basic/profiles/Untitled/streamEncoder.json
Normal file
1
basic/profiles/Untitled/streamEncoder.json
Normal file
@@ -0,0 +1 @@
|
||||
{"rate_control":"CBR","keyint_sec":2,"profile":"high"}
|
||||
1688
basic/scenes/Untitled.json
Normal file
1688
basic/scenes/Untitled.json
Normal file
File diff suppressed because it is too large
Load Diff
1729
basic/scenes/Untitled.json.pre-cef-migration
Normal file
1729
basic/scenes/Untitled.json.pre-cef-migration
Normal file
File diff suppressed because it is too large
Load Diff
1887
basic/scenes/Untitled.json.pre-landing-rebuild
Normal file
1887
basic/scenes/Untitled.json.pre-landing-rebuild
Normal file
File diff suppressed because it is too large
Load Diff
90
global.ini
Normal file
90
global.ini
Normal file
@@ -0,0 +1,90 @@
|
||||
[General]
|
||||
Pre19Defaults=false
|
||||
LicenseAccepted=true
|
||||
FirstRun=true
|
||||
Pre21Defaults=false
|
||||
Pre23Defaults=false
|
||||
LastVersion=536936449
|
||||
Pre24.1Defaults=true
|
||||
MaxLogs=10
|
||||
InfoIncrement=-1
|
||||
ProcessPriority=Normal
|
||||
EnableAutoUpdates=true
|
||||
ConfirmOnExit=true
|
||||
HotkeyFocusType=NeverDisableHotkeys
|
||||
Pre31Migrated=true
|
||||
BrowserHWAccel=true
|
||||
|
||||
[Basic]
|
||||
SceneCollection=Untitled
|
||||
SceneCollectionFile=Untitled
|
||||
Profile=Untitled
|
||||
ProfileDir=Untitled
|
||||
ConfigOnNewProfile=true
|
||||
|
||||
[PropertiesWindow]
|
||||
cx=720
|
||||
cy=580
|
||||
|
||||
[BasicWindow]
|
||||
geometry=AdnQywADAAAAAAeAAAAAKAAADv0AAAQ3AAAHgAAAAFMAAA79AAAENwAAAAEAAAAAB4AAAAeAAAAAUwAADv0AAAQ3
|
||||
DockState=AAAA/wAAAAD9AAAAAQAAAAMAAAd+AAABWfwBAAAABvsAAAAUAHMAYwBlAG4AZQBzAEQAbwBjAGsBAAAAAAAAAV4AAACYAP////sAAAAWAHMAbwB1AHIAYwBlAHMARABvAGMAawEAAAFiAAABRQAAAJgA////+wAAABIAbQBpAHgAZQByAEQAbwBjAGsBAAACqwAAAdkAAADeAP////sAAAAeAHQAcgBhAG4AcwBpAHQAaQBvAG4AcwBEAG8AYwBrAQAABIgAAAFfAAAAoQD////7AAAAGABjAG8AbgB0AHIAbwBsAHMARABvAGMAawEAAAXrAAABkwAAAKIA////+wAAABIAcwB0AGEAdABzAEQAbwBjAGsCAAAExAAAAXAAAAK8AAAAyAAAB34AAAJPAAAABAAAAAQAAAAIAAAACPwAAAAA
|
||||
PreviewEnabled=true
|
||||
AlwaysOnTop=false
|
||||
SceneDuplicationMode=true
|
||||
SwapScenesMode=true
|
||||
EditPropertiesMode=false
|
||||
PreviewProgramMode=false
|
||||
DocksLocked=false
|
||||
gridMode=false
|
||||
SnappingEnabled=false
|
||||
ScreenSnapping=false
|
||||
SourceSnapping=false
|
||||
WarnBeforeStartingStream=false
|
||||
WarnBeforeStoppingStream=false
|
||||
WarnBeforeStoppingRecord=false
|
||||
HideProjectorCursor=false
|
||||
ProjectorAlwaysOnTop=false
|
||||
CenterSnapping=false
|
||||
SnapDistance=10
|
||||
SpacingHelpersEnabled=true
|
||||
RecordWhenStreaming=false
|
||||
KeepRecordingWhenStreamStops=false
|
||||
SysTrayEnabled=true
|
||||
SysTrayWhenStarted=false
|
||||
SaveProjectors=false
|
||||
ShowTransitions=true
|
||||
ShowListboxToolbars=true
|
||||
ShowStatusBar=true
|
||||
ShowSourceIcons=true
|
||||
ShowContextToolbars=true
|
||||
StudioModeLabels=true
|
||||
VerticalVolControl=false
|
||||
MultiviewMouseSwitch=true
|
||||
MultiviewDrawNames=true
|
||||
MultiviewDrawAreas=true
|
||||
MediaControlsCountdownTimer=true
|
||||
SideDocks=false
|
||||
|
||||
[ScriptLogWindow]
|
||||
geometry=AdnQywADAAAAAAABAAAAQQAAAlgAAAHQAAAAAQAAAEEAAAJYAAAB0AAAAAAAAAAAB4AAAAABAAAAQQAAAlgAAAHQ
|
||||
|
||||
[Video]
|
||||
Renderer=OpenGL
|
||||
|
||||
[Accessibility]
|
||||
SelectRed=255
|
||||
SelectGreen=65280
|
||||
SelectBlue=16744192
|
||||
MixerGreen=2522918
|
||||
MixerYellow=2523007
|
||||
MixerRed=2500223
|
||||
MixerGreenActive=5046092
|
||||
MixerYellowActive=5046271
|
||||
MixerRedActive=5000447
|
||||
|
||||
[Locations]
|
||||
Configuration=/home/jin/.config
|
||||
SceneCollections=/home/jin/.config
|
||||
Profiles=/home/jin/.config
|
||||
PluginManagerSettings=/home/jin/.config
|
||||
274
landing/index.html
Normal file
274
landing/index.html
Normal file
@@ -0,0 +1,274 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>OPHI-118 / STAND BY</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #07080d;
|
||||
--ink: #e8e8e0;
|
||||
--hud: #4fd2ff;
|
||||
--hud-dim: #2a7fa6;
|
||||
--accent: #e63a2e;
|
||||
--warn: #ffd000;
|
||||
--mono: 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', monospace;
|
||||
--display: 'DejaVu Sans', 'Liberation Sans', 'Helvetica', 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
width: 100%; height: 100%;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: var(--display);
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto auto;
|
||||
padding: 40px 72px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* ───────── HUD top strip ───────── */
|
||||
.hud {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-family: var(--mono);
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--hud);
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.hud .group { display: flex; gap: 28px; align-items: center; }
|
||||
.hud .dim { color: var(--hud-dim); }
|
||||
.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;
|
||||
animation: rec-blink 1.6s ease-in-out infinite;
|
||||
}
|
||||
@keyframes rec-blink {
|
||||
0%, 55% { opacity: 1; }
|
||||
65%, 100% { opacity: 0.2; }
|
||||
}
|
||||
|
||||
/* ───────── Centered mark ───────── */
|
||||
.stage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 36px;
|
||||
}
|
||||
.mark {
|
||||
width: 360px; height: 360px;
|
||||
filter: drop-shadow(0 0 22px rgba(58, 209, 255, 0.35));
|
||||
animation: mark-breathe 6s ease-in-out infinite;
|
||||
}
|
||||
@keyframes mark-breathe {
|
||||
0%, 100% { filter: drop-shadow(0 0 18px rgba(58, 209, 255, 0.30)); }
|
||||
50% { filter: drop-shadow(0 0 30px rgba(58, 209, 255, 0.50)); }
|
||||
}
|
||||
.bars {
|
||||
display: flex;
|
||||
width: 720px;
|
||||
height: 14px;
|
||||
opacity: 0.32;
|
||||
}
|
||||
.bars i { flex: 1; display: block; }
|
||||
.bars i:nth-child(1) { background: #c7c7c7; }
|
||||
.bars i:nth-child(2) { background: #c7c700; }
|
||||
.bars i:nth-child(3) { background: #00c7c7; }
|
||||
.bars i:nth-child(4) { background: #00c700; }
|
||||
.bars i:nth-child(5) { background: #c700c7; }
|
||||
.bars i:nth-child(6) { background: #c70000; }
|
||||
.bars i:nth-child(7) { background: #0000c7; }
|
||||
|
||||
/* ───────── Standby block ───────── */
|
||||
.standby {
|
||||
text-align: center;
|
||||
}
|
||||
.standby .big {
|
||||
font-size: 172px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.20em;
|
||||
line-height: 1;
|
||||
text-shadow: 0 0 18px rgba(79, 210, 255, 0.18);
|
||||
animation: standby-pulse 3s ease-in-out infinite;
|
||||
}
|
||||
.standby .sub {
|
||||
margin-top: 30px;
|
||||
font-family: var(--mono);
|
||||
font-size: 26px;
|
||||
letter-spacing: 0.45em;
|
||||
color: var(--hud-dim);
|
||||
}
|
||||
@keyframes standby-pulse {
|
||||
0%, 100% { opacity: 0.90; }
|
||||
50% { opacity: 1.00; }
|
||||
}
|
||||
|
||||
/* ───────── Bottom HUD ───────── */
|
||||
.foot {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-family: var(--mono);
|
||||
font-size: 19px;
|
||||
color: var(--hud-dim);
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
.foot .warn { color: var(--warn); }
|
||||
|
||||
/* ───────── Overlays ───────── */
|
||||
#static {
|
||||
position: absolute; inset: 0;
|
||||
width: 100%; height: 100%;
|
||||
pointer-events: none;
|
||||
opacity: 0.045;
|
||||
mix-blend-mode: screen;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.scanlines {
|
||||
position: absolute; inset: 0;
|
||||
pointer-events: none;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
rgba(0,0,0,0) 0px,
|
||||
rgba(0,0,0,0) 2px,
|
||||
rgba(0,0,0,0.20) 3px,
|
||||
rgba(0,0,0,0.20) 4px
|
||||
);
|
||||
opacity: 0.5;
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
.vignette {
|
||||
position: absolute; inset: 0;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
rgba(0,0,0,0) 45%,
|
||||
rgba(0,0,0,0.60) 100%
|
||||
);
|
||||
}
|
||||
.flicker {
|
||||
position: absolute; inset: 0;
|
||||
pointer-events: none;
|
||||
background: rgba(0,0,0,0);
|
||||
animation: flicker 11s ease-in-out infinite;
|
||||
}
|
||||
@keyframes flicker {
|
||||
0%, 100% { background: rgba(0,0,0,0); }
|
||||
18% { background: rgba(0,0,0,0); }
|
||||
18.3% { background: rgba(0,0,0,0.06); }
|
||||
18.5% { background: rgba(0,0,0,0); }
|
||||
47% { background: rgba(0,0,0,0); }
|
||||
47.3% { background: rgba(0,0,0,0.08); }
|
||||
47.5% { background: rgba(0,0,0,0); }
|
||||
78% { background: rgba(0,0,0,0); }
|
||||
78.4% { background: rgba(0,0,0,0.05); }
|
||||
78.7% { background: rgba(0,0,0,0); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="hud">
|
||||
<div class="group">
|
||||
<span><span class="led"></span>TRANSMISSION</span>
|
||||
<span>OPHI-118</span>
|
||||
</div>
|
||||
<div class="group">
|
||||
<span class="dim">SIGNAL</span>
|
||||
<span id="signal">▮▮▮▯▯</span>
|
||||
</div>
|
||||
<div class="group">
|
||||
<span class="dim">UTC</span>
|
||||
<span id="clock">--:--:--</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="stage">
|
||||
<svg class="mark" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="100" cy="100" r="92" fill="none" stroke="#3ad1ff" stroke-width="1.2" opacity="0.35"/>
|
||||
<circle cx="100" cy="100" r="80" fill="none" stroke="#3ad1ff" stroke-width="2.5"/>
|
||||
<g stroke="#3ad1ff" stroke-width="2" opacity="0.55">
|
||||
<line x1="100" y1="8" x2="100" y2="22"/>
|
||||
<line x1="100" y1="178" x2="100" y2="192"/>
|
||||
<line x1="8" y1="100" x2="22" y2="100"/>
|
||||
<line x1="178" y1="100" x2="192" y2="100"/>
|
||||
</g>
|
||||
<polygon points="100,52 148,140 52,140"
|
||||
fill="none" stroke="#e63a2e" stroke-width="3.5" stroke-linejoin="miter"/>
|
||||
<circle cx="100" cy="116" r="4" fill="#ffd000"/>
|
||||
</svg>
|
||||
<div class="bars"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
</main>
|
||||
|
||||
<section class="standby">
|
||||
<div class="big">PLEASE STAND BY</div>
|
||||
<div class="sub">— DO NOT ADJUST YOUR RECEIVER —</div>
|
||||
</section>
|
||||
|
||||
<footer class="foot">
|
||||
<span>CH 118.0 MHz</span>
|
||||
<span>NTSC · 525i · MONO</span>
|
||||
<span class="warn">▲ AUDIO ACTIVE</span>
|
||||
</footer>
|
||||
|
||||
<canvas id="static"></canvas>
|
||||
<div class="scanlines"></div>
|
||||
<div class="vignette"></div>
|
||||
<div class="flicker"></div>
|
||||
|
||||
<script>
|
||||
// Live UTC clock
|
||||
const clockEl = document.getElementById('clock');
|
||||
const pad = n => String(n).padStart(2, '0');
|
||||
const tickClock = () => {
|
||||
const d = new Date();
|
||||
clockEl.textContent = `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
|
||||
};
|
||||
tickClock();
|
||||
setInterval(tickClock, 1000);
|
||||
|
||||
// Signal bar fluctuation — 3 bars typical, occasional drop or spike
|
||||
const sigEl = document.getElementById('signal');
|
||||
const FILL = '▮', EMPTY = '▯';
|
||||
const drawSig = n => FILL.repeat(n) + EMPTY.repeat(5 - n);
|
||||
setInterval(() => {
|
||||
const r = Math.random();
|
||||
const n = r < 0.06 ? 2 : r < 0.70 ? 3 : r < 0.95 ? 4 : 5;
|
||||
sigEl.textContent = drawSig(n);
|
||||
}, 1500);
|
||||
|
||||
// Animated CRT static (small buffer, scaled by browser)
|
||||
const cv = document.getElementById('static');
|
||||
const ctx = cv.getContext('2d');
|
||||
const W = 320, H = 180;
|
||||
cv.width = W; cv.height = H;
|
||||
const img = ctx.createImageData(W, H);
|
||||
const drawNoise = () => {
|
||||
const d = img.data;
|
||||
for (let i = 0; i < d.length; i += 4) {
|
||||
const v = (Math.random() * 255) | 0;
|
||||
d[i] = d[i+1] = d[i+2] = v;
|
||||
d[i+3] = 255;
|
||||
}
|
||||
ctx.putImageData(img, 0, 0);
|
||||
};
|
||||
drawNoise();
|
||||
setInterval(drawNoise, 1000 / 12);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
landing/static-hum.wav
Normal file
BIN
landing/static-hum.wav
Normal file
Binary file not shown.
1
plugin_config/rtmp-services/meta.json
Normal file
1
plugin_config/rtmp-services/meta.json
Normal file
@@ -0,0 +1 @@
|
||||
{"etag":"W/\"6254aa76-7a\""}
|
||||
1
plugin_config/rtmp-services/package.json
Normal file
1
plugin_config/rtmp-services/package.json
Normal file
@@ -0,0 +1 @@
|
||||
{"$schema":"schema/package-schema.json","url":"https://obsproject.com/obs2_update/rtmp-services/v5","version":285,"files":[{"name":"services.json","version":285}]}
|
||||
1
plugin_config/rtmp-services/services.json
Normal file
1
plugin_config/rtmp-services/services.json
Normal file
File diff suppressed because one or more lines are too long
130
plugin_config/rtmp-services/twitch_ingests.json
Normal file
130
plugin_config/rtmp-services/twitch_ingests.json
Normal file
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"ingests": [
|
||||
{
|
||||
"_id": 0,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "Default",
|
||||
"url_template": "rtmp://ingest.global-contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://ingest.global-contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 0
|
||||
},
|
||||
{
|
||||
"_id": 1,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "Europe (Frankfurt)",
|
||||
"url_template": "rtmp://euc10.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://euc10.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 1
|
||||
},
|
||||
{
|
||||
"_id": 2,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "Europe (Stockholm)",
|
||||
"url_template": "rtmp://eun10.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://eun10.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 2
|
||||
},
|
||||
{
|
||||
"_id": 3,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "Europe (Paris)",
|
||||
"url_template": "rtmp://euw30.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://euw30.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 3
|
||||
},
|
||||
{
|
||||
"_id": 4,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "Europe (Ireland)",
|
||||
"url_template": "rtmp://euw10.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://euw10.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 4
|
||||
},
|
||||
{
|
||||
"_id": 5,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "Asia Pacific (Mumbai)",
|
||||
"url_template": "rtmp://aps30.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://aps30.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 5
|
||||
},
|
||||
{
|
||||
"_id": 6,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "US East (N. Virginia)",
|
||||
"url_template": "rtmp://use10.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://use10.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 6
|
||||
},
|
||||
{
|
||||
"_id": 7,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "US East (Ohio)",
|
||||
"url_template": "rtmp://use20.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://use20.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 7
|
||||
},
|
||||
{
|
||||
"_id": 8,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "Asia Pacific (Seoul)",
|
||||
"url_template": "rtmp://apn20.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://apn20.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 8
|
||||
},
|
||||
{
|
||||
"_id": 9,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "US West (Oregon)",
|
||||
"url_template": "rtmp://usw20.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://usw20.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 9
|
||||
},
|
||||
{
|
||||
"_id": 10,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "Asia Pacific (Tokyo)",
|
||||
"url_template": "rtmp://apn10.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://apn10.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 10
|
||||
},
|
||||
{
|
||||
"_id": 11,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "Asia Pacific (Singapore)",
|
||||
"url_template": "rtmp://aps10.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://aps10.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 11
|
||||
},
|
||||
{
|
||||
"_id": 12,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "South America (São Paulo)",
|
||||
"url_template": "rtmp://sae10.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://sae10.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 12
|
||||
},
|
||||
{
|
||||
"_id": 13,
|
||||
"availability": 1.0,
|
||||
"default": false,
|
||||
"name": "Asia Pacific (Sydney)",
|
||||
"url_template": "rtmp://aps20.contribute.live-video.net/app/{stream_key}",
|
||||
"url_template_secure": "rtmps://aps20.contribute.live-video.net/app/{stream_key}",
|
||||
"priority": 13
|
||||
}
|
||||
]
|
||||
}
|
||||
13
plugin_manager/modules.json
Normal file
13
plugin_manager/modules.json
Normal file
@@ -0,0 +1,13 @@
|
||||
[
|
||||
{
|
||||
"display_name": "",
|
||||
"enabled": true,
|
||||
"encoders": [],
|
||||
"id": "",
|
||||
"module_name": "linux-pipewire-audio",
|
||||
"outputs": [],
|
||||
"services": [],
|
||||
"sources": [],
|
||||
"version": ""
|
||||
}
|
||||
]
|
||||
91
user.ini
Normal file
91
user.ini
Normal file
@@ -0,0 +1,91 @@
|
||||
[General]
|
||||
Pre19Defaults=false
|
||||
LicenseAccepted=true
|
||||
FirstRun=true
|
||||
Pre21Defaults=false
|
||||
Pre23Defaults=false
|
||||
LastVersion=503447555
|
||||
Pre24.1Defaults=true
|
||||
MaxLogs=10
|
||||
InfoIncrement=-1
|
||||
ProcessPriority=Normal
|
||||
EnableAutoUpdates=true
|
||||
ConfirmOnExit=true
|
||||
HotkeyFocusType=NeverDisableHotkeys
|
||||
|
||||
[Basic]
|
||||
SceneCollection=Untitled
|
||||
SceneCollectionFile=Untitled.json
|
||||
Profile=Untitled
|
||||
ProfileDir=Untitled
|
||||
ConfigOnNewProfile=true
|
||||
|
||||
[PropertiesWindow]
|
||||
cx=720
|
||||
cy=580
|
||||
|
||||
[BasicWindow]
|
||||
geometry=AdnQywADAAAAAAAAAAAAAAAACf8AAAVDAAACgQAAABkAAAn+AAAEHAAAAAECAAAACgAAAAAAAAAAAAAACf8AAAVD
|
||||
DockState=AAAA/wAAAAD9AAAAAQAAAAMAAAoAAAABLPwBAAAABvsAAAAUAHMAYwBlAG4AZQBzAEQAbwBjAGsBAAAAAAAAAe8AAACYAP////sAAAAWAHMAbwB1AHIAYwBlAHMARABvAGMAawEAAAHzAAAB6QAAAJgA////+wAAABIAbQBpAHgAZQByAEQAbwBjAGsBAAAD4AAAAn4AAAECAP////sAAAAeAHQAcgBhAG4AcwBpAHQAaQBvAG4AcwBEAG8AYwBrAQAABmIAAAHRAAAArQD////7AAAAGABjAG8AbgB0AHIAbwBsAHMARABvAGMAawEAAAg3AAAByQAAAKIA////+wAAABIAcwB0AGEAdABzAEQAbwBjAGsCAAAExAAAAXAAAAK8AAAAyAAACgAAAAPYAAAABAAAAAQAAAAIAAAACPwAAAAA
|
||||
PreviewEnabled=true
|
||||
AlwaysOnTop=false
|
||||
SceneDuplicationMode=true
|
||||
SwapScenesMode=true
|
||||
EditPropertiesMode=false
|
||||
PreviewProgramMode=false
|
||||
DocksLocked=false
|
||||
gridMode=false
|
||||
SnappingEnabled=false
|
||||
ScreenSnapping=false
|
||||
SourceSnapping=false
|
||||
WarnBeforeStartingStream=false
|
||||
WarnBeforeStoppingStream=false
|
||||
WarnBeforeStoppingRecord=false
|
||||
HideProjectorCursor=false
|
||||
ProjectorAlwaysOnTop=false
|
||||
CenterSnapping=false
|
||||
SnapDistance=10
|
||||
SpacingHelpersEnabled=true
|
||||
RecordWhenStreaming=false
|
||||
KeepRecordingWhenStreamStops=false
|
||||
SysTrayEnabled=true
|
||||
SysTrayWhenStarted=false
|
||||
SaveProjectors=false
|
||||
ShowTransitions=true
|
||||
ShowListboxToolbars=true
|
||||
ShowStatusBar=true
|
||||
ShowSourceIcons=true
|
||||
ShowContextToolbars=true
|
||||
StudioModeLabels=true
|
||||
VerticalVolControl=false
|
||||
MultiviewMouseSwitch=true
|
||||
MultiviewDrawNames=true
|
||||
MultiviewDrawAreas=true
|
||||
MediaControlsCountdownTimer=true
|
||||
SideDocks=false
|
||||
VerticalVolumeControl=true
|
||||
MixerShowInactive=false
|
||||
MixerKeepInactiveLast=false
|
||||
MixerShowHidden=false
|
||||
MixerKeepHiddenLast=false
|
||||
|
||||
[ScriptLogWindow]
|
||||
geometry=AdnQywADAAAAAAAAAAAAAAAAAlcAAAGPAAAAAAAAAAAAAAJXAAABjwAAAAEAAAAACgAAAAAAAAAAAAAAAlcAAAGP
|
||||
|
||||
[Video]
|
||||
Renderer=OpenGL
|
||||
|
||||
[Accessibility]
|
||||
SelectRed=255
|
||||
SelectGreen=65280
|
||||
SelectBlue=16744192
|
||||
MixerGreen=2522918
|
||||
MixerYellow=2523007
|
||||
MixerRed=2500223
|
||||
MixerGreenActive=5046092
|
||||
MixerYellowActive=5046271
|
||||
MixerRedActive=5000447
|
||||
|
||||
[Appearance]
|
||||
FontScale=10
|
||||
Density=1
|
||||
Reference in New Issue
Block a user