Skip to content

Commit 36ebf84

Browse files
committed
chore: Update AI agent documents
1 parent 3794ee9 commit 36ebf84

3 files changed

Lines changed: 314 additions & 276 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@
3333

3434
# Except for AI agent instructions
3535
!AGENTS.md
36+
!CLAUDE.md
3637
!/.claude

AGENTS.md

Lines changed: 35 additions & 276 deletions
Original file line numberDiff line numberDiff line change
@@ -1,278 +1,37 @@
11
# AGENTS.md — Branch Output Plugin for OBS Studio
22

3-
## Project Overview
4-
5-
**Branch Output** is an OBS Studio plugin (shared library / module) that adds an effect filter allowing
6-
individual sources or scenes to stream and/or record independently from the main OBS output.
7-
It is developed and maintained by **OPENSPHERE Inc.** under the GPLv2+ license.
8-
9-
- Repository language: **C++ 17** with **Qt 6** for UI
10-
- Build system: **CMake** (≥ 3.16)
11-
- Based on the official [obs-plugintemplate](https://github.com/obsproject/obs-plugintemplate)
12-
- Target OBS Studio version: **≥ 30.1.0** (Qt6, x64 / ARM64 / Apple Silicon)
13-
14-
---
15-
16-
## Repository Layout
17-
18-
```
19-
branch-output/
20-
├── CMakeLists.txt # Top-level CMake project definition
21-
├── CMakePresets.json # CMake presets (windows-x64, macos, linux-x86_64, CI variants)
22-
├── buildspec.json # Build specification (name, version, dependencies, UUIDs)
23-
├── build.ps1 # Local Windows build script (RelWithDebInfo)
24-
├── .clang-format # C++ code style (clang-format ≥ 16)
25-
├── .cmake-format.json # CMake code style
26-
├── .github/
27-
│ ├── actions/ # Reusable format-check actions
28-
│ └── workflows/ # CI workflows (build, format check, release)
29-
├── cmake/ # CMake helpers & platform-specific modules
30-
├── build-aux/ # Auxiliary build scripts (format runners)
31-
├── data/
32-
│ └── locale/ # Translation files (en-US, ja-JP, zh-CN, ko-KR, etc.)
33-
├── src/
34-
│ ├── plugin-main.cpp # Plugin entry point, BranchOutputFilter implementation
35-
│ ├── plugin-main.hpp # BranchOutputFilter class declaration
36-
│ ├── plugin-replay-buffer.cpp # Replay buffer output logic
37-
│ ├── plugin-ui.cpp # OBS properties UI, filter settings UI
38-
│ ├── plugin-support.h # Auto-generated plugin support header
39-
│ ├── plugin-support.c.in # Template for plugin-support.c
40-
│ ├── utils.cpp / .hpp # Utility functions, encoder helpers, profile helpers
41-
│ ├── audio/
42-
│ │ └── audio-capture.cpp / .hpp # Audio capture abstraction
43-
│ ├── video/
44-
│ │ └── filter-video-capture.cpp / .hpp # Filter input video capture (GPU texrender proxy)
45-
│ └── UI/
46-
│ ├── output-status-dock.cpp / .hpp # Status dock widget
47-
│ ├── resources.qrc # Qt resource file
48-
│ └── images/ # Icons and images
49-
└── release/ # Build output directory
50-
```
51-
52-
---
53-
54-
## Build Instructions
55-
56-
### Prerequisites
57-
58-
| Platform | Toolchain |
59-
|----------|-----------|
60-
| Windows | Visual Studio 17 2022, CMake ≥ 3.16, Qt 6 |
61-
| macOS | Xcode ≤ 16.4.0 (macOS SDK ≤ 15.5), CMake ≥ 3.16, Qt 6 |
62-
| Linux | GCC / Clang, CMake ≥ 3.16, Qt 6 |
63-
64-
> **Note (macOS):** This project requires **Xcode 16.4.0 (macOS 15.5 SDK) or below**. Xcode 26 / macOS SDK 26 and later are currently unsupported due to compatibility constraints with OBS Studio's build dependencies.
65-
66-
OBS Studio sources and pre-built dependencies are fetched automatically via `buildspec.json`
67-
(obs-studio 30.1.2, obs-deps, Qt6).
68-
69-
### Windows (local)
70-
71-
```powershell
72-
# Configure + Build + Install
73-
.\build.ps1
74-
75-
# With Inno Setup installer
76-
.\build.ps1 -installer
77-
```
78-
79-
Or manually:
80-
81-
```powershell
82-
cmake --fresh -S . -B build_x64 -G "Visual Studio 17 2022" -A x64
83-
cmake --build build_x64 --config RelWithDebInfo
84-
cmake --install build_x64 --prefix release/Package --config RelWithDebInfo
85-
```
86-
87-
### CMake Presets
88-
89-
Use `cmake --preset <name>` with one of:
90-
- `windows-x64` / `windows-ci-x64`
91-
- `macos` / `macos-ci`
92-
- `linux-x86_64` / `linux-ci-x86_64`
93-
94-
---
95-
96-
## Architecture & Key Concepts
97-
98-
### Plugin Entry Point
99-
100-
The plugin registers an OBS **effect filter** via `obs_source_info` (filter ID: `osi_branch_output`).
101-
The main class is `BranchOutputFilter` (declared in `plugin-main.hpp`), which is a `QObject` subclass.
102-
103-
### Core Responsibilities of BranchOutputFilter
104-
105-
| Area | Description |
106-
|------|-------------|
107-
| **Streaming** | Creates up to `MAX_SERVICES` (8) independent streaming outputs with dedicated services, encoders, and reconnect logic. |
108-
| **Recording** | Supports file recording with various container formats, time/size-based splitting, pause/unpause, and chapter markers. |
109-
| **Replay Buffer** | Supports replay buffer output (implemented in `plugin-replay-buffer.cpp`). Allows saving the last N seconds of encoded output to file on demand via hotkey or UI button. |
110-
| **Audio** | Manages up to `MAX_AUDIO_MIXES` audio contexts via `AudioCapture` class. Supports filter audio, per-source audio, and audio track selection. |
111-
| **Video** | Creates an OBS view (`obs_view_t`) with a private video output for per-filter encoding and resolution control. Supports **filter input mode** via `FilterVideoCapture` class, which captures the filter's input using GPU `gs_texrender` and provides a private proxy source for the `obs_view`, avoiding CPU roundtrips and enabling GPU encoder compatibility (NVENC, QSV, AMF, etc.). |
112-
| **UI** | Properties panel built via OBS properties API (`plugin-ui.cpp`). Status dock (`BranchOutputStatusDock`) shows live statistics for all filters, including replay buffer save buttons. |
113-
| **Hotkeys** | Registers hotkey pairs for enable/disable, split recording, pause/unpause, chapter markers, and save replay buffer. |
114-
| **Interlock** | Can link filter activation to OBS streaming, recording, virtual camera, or replay buffer states. |
115-
| **Blanking** | Uses a private solid-color source to blank output when the parent source is inactive. |
116-
117-
### Threading Model
118-
119-
- OBS callbacks (video render, audio filter, video tick) may run on **different threads** from the UI thread.
120-
- `pthread_mutex_t outputMutex` protects streaming/recording output state.
121-
- `QMutex` protects audio buffers in `AudioCapture`.
122-
- UI updates use `QMetaObject::invokeMethod` with `Qt::QueuedConnection` for thread safety.
123-
- Settings changes are tracked via revision counters (`storedSettingsRev` / `activeSettingsRev`) to defer restarts.
124-
125-
### OBS API Usage
126-
127-
The plugin heavily uses:
128-
- `obs-module.h` — Module registration, locale, config paths
129-
- `obs-frontend-api.h` — Profile config, scene enumeration, frontend events
130-
- `obs.hpp` — OBS RAII wrappers (`OBSSourceAutoRelease`, `OBSEncoderAutoRelease`, `OBSOutputAutoRelease`, etc.)
131-
- `util/deque.h`, `util/threading.h`, `util/platform.h` — Low-level utilities
132-
133-
---
134-
135-
## Code Style & Formatting
136-
137-
### C++ (clang-format)
138-
139-
- **Standard**: C++17
140-
- **Column limit**: 120
141-
- **Indent**: 4 spaces (tabs not used)
142-
- **Brace style**: Custom — functions use next-line braces; control statements use same-line braces
143-
- **clang-format version**: ≥ 16 required
144-
- Run: `clang-format -i src/**/*.cpp src/**/*.hpp`
145-
146-
### CMake (cmake-format)
147-
148-
- **Line width**: 120
149-
- **Tab size**: 2
150-
- Config: `.cmake-format.json`
151-
152-
### CI Enforcement
153-
154-
Format is checked in CI via `.github/workflows/check-format.yaml` using reusable actions
155-
(`run-clang-format`, `run-cmake-format`). PRs to `master`/`main`/`dev` and pushes to `master`/`dev` are validated.
156-
157-
---
158-
159-
## Coding Guidelines
160-
161-
### General Rules
162-
163-
1. **Follow OBS plugin conventions** — Use `obs_log()` for logging, `OBS_DECLARE_MODULE()` for entry, locale via `obs_module_text()`.
164-
2. **RAII wrappers** — Always use `OBSSourceAutoRelease`, `OBSDataAutoRelease`, etc. instead of manual `obs_*_release()`.
165-
3. **Thread safety** — Any data shared between OBS callbacks and UI must be guarded by mutex. Use `QMetaObject::invokeMethod` with `Qt::QueuedConnection` when calling UI methods from non-UI threads.
166-
4. **Settings migration** — When changing settings schema, add backward-compatible migration code in the constructor (see `audio_source` migration example in `BranchOutputFilter` constructor).
167-
5. **Qt MOC** — Classes using `Q_OBJECT` must be in headers. `AUTOMOC`, `AUTOUIC`, `AUTORCC` are enabled.
168-
169-
### Naming Conventions
170-
171-
- **Classes**: PascalCase (`BranchOutputFilter`, `AudioCapture`, `FilterVideoCapture`, `OutputTableRow`)
172-
- **Methods**: camelCase (`startOutput`, `stopRecordingOutput`, `onIntervalTimerTimeout`)
173-
- **Constants/Macros**: UPPER_SNAKE_CASE (`MAX_SERVICES`, `FILTER_ID`, `OUTPUT_MAX_RETRIES`)
174-
- **Member variables**: camelCase, no prefix (`filterSource`, `videoEncoder`, `recordingActive`)
175-
- **Static callbacks**: camelCase with descriptive prefix (`onEnableFilterHotkeyPressed`, `audioFilterCallback`)
176-
177-
### Ternary Operators
178-
179-
- **Avoid multi-line ternary expressions** — clang-format cannot reliably format nested or multi-line ternary operators (`a ? b : c`) in a readable way. Use `if`/`else if`/`else` statements instead when the expression would span multiple lines.
180-
181-
### Error Handling
182-
183-
- Use `obs_log(LOG_ERROR, ...)` for errors, `LOG_WARNING` for warnings, `LOG_INFO` for lifecycle events, `LOG_DEBUG` for verbose tracing.
184-
- Check return values from OBS API calls; handle gracefully (do not crash OBS).
185-
- Never throw C++ exceptions across OBS API boundaries.
186-
187-
### Localization
188-
189-
- All user-facing strings must use `obs_module_text("Key")` or the `QTStr("Key")` helper.
190-
- Add new keys to `data/locale/en-US.ini` (primary) and ideally to `ja-JP.ini`.
191-
- Locale files use INI format: `Key="Value"`.
192-
193-
---
194-
195-
## Testing & Validation
196-
197-
- There are no automated unit tests in this repository currently.
198-
- **Manual testing** is required: load the plugin in OBS Studio, add the "Branch Output" filter, and verify streaming/recording behavior.
199-
- Test on all supported platforms when possible (Windows x64, macOS, Linux).
200-
- Verify Studio Mode compatibility (Branch Output ignores studio mode's program and outputs from preview).
201-
- Verify that enabling and then disabling the Branch Output filter does not cause a crash.
202-
- Verify that shutting down OBS does not crash when the Branch Output filter is inactive or active, respectively.
203-
- Verify that switching scene collections does not crash when the Branch Output filter is inactive or active, respectively.
204-
- Verify that no memory leaks are logged on OBS shutdown.
205-
- Verify that mutex does not cause deadlocks.
206-
- Verify that blanking mode does not leak video or audio unintentionally — when the source is not visible in the main output (Program) and blanking is enabled, the Branch Output must emit a black frame (no source imagery). If audio muting is also enabled, all audio tracks (including master track) must be silenced.
207-
208-
---
209-
210-
## CI / CD
211-
212-
| Workflow | Trigger | Purpose |
213-
|----------|---------|---------|
214-
| `push.yaml` | Push to `master`/`main`/`dev`/`release/**`, tags | Format check + build + release creation |
215-
| `build-project.yaml` | Called by push/PR workflows | Multi-platform build (Windows, macOS, Linux) |
216-
| `check-format.yaml` | Called by push/PR workflows | clang-format and cmake-format validation |
217-
| `pr-pull.yaml` | Pull requests to `master`/`main`/`dev` | Format check + build validation |
218-
| `dispatch.yaml` | Manual dispatch | On-demand builds |
219-
220-
Release tags follow semver: `X.Y.Z` for stable, `X.Y.Z-beta`/`X.Y.Z-rc` for pre-releases.
221-
222-
---
223-
224-
## Common Tasks for AI Agents
225-
226-
### Adding a New Setting / Feature
227-
228-
1. Add the setting key to `BranchOutputFilter::getDefaults()` in `plugin-main.cpp`.
229-
2. Add UI controls in `plugin-ui.cpp` (use OBS properties API: `obs_properties_add_*`).
230-
3. Handle the setting in `startOutput()` / `stopOutput()` / `updateCallback()` as needed.
231-
4. Add locale strings to **all** locale files under `data/locale/` (`en-US.ini`, `ja-JP.ini`, `zh-CN.ini`, `ko-KR.ini`, `de-DE.ini`, `fr-FR.ini`, `ca-ES.ini`, `ro-RO.ini`, `ru-RU.ini`, `uk-UA.ini`). `en-US.ini` is the primary (required); all others should also be updated.
232-
5. Ensure backward compatibility — existing saved settings must still load correctly.
233-
234-
### Adding a New Streaming Service Slot
235-
236-
- The plugin supports up to `MAX_SERVICES` (8) service slots.
237-
- Each slot has its own `BranchOutputStreamingContext` with output, service, and signals.
238-
- Use `getIndexedPropNameFormat()` for indexed property names.
239-
240-
### Modifying Audio Handling
241-
242-
- Audio capture is abstracted through `AudioCapture` class in `src/audio/`.
243-
- The class manages an audio buffer, supports push/pop patterns, and handles format conversion.
244-
- Audio mixers and encoder binding are managed in `BranchOutputFilter::startOutput()`.
245-
246-
### Modifying the Status Dock
247-
248-
- `BranchOutputStatusDock` in `src/UI/` is a `QFrame`-based dock widget.
249-
- It uses a `QTableWidget` with custom cell classes (`OutputTableCellItem`, `LabelCell`, `FilterCell`, `ReplayBufferOutputCell`).
250-
- Thread-safe updates via `QMetaObject::invokeMethod`.
251-
252-
### Modifying Replay Buffer
253-
254-
- Replay buffer logic is implemented in `src/plugin-replay-buffer.cpp`.
255-
- `BranchOutputFilter` manages replay buffer lifecycle (`createAndStartReplayBuffer`, `stopReplayBufferOutput`, `saveReplayBuffer`).
256-
- Settings creation is handled by `createReplayBufferSettings()`.
257-
- The status dock includes `ReplayBufferOutputCell` with a save button and a global "Save All Replay Buffers" button.
258-
- Replay buffer can be linked to filter activation via `INTERLOCK_TYPE_REPLAY_BUFFER`.
259-
260-
### Modifying Filter Video Capture
261-
262-
- `FilterVideoCapture` in `src/video/` captures the filter's input via GPU `gs_texrender`.
263-
- It provides a private proxy source (`PROXY_SOURCE_ID: osi_branch_output_proxy`) for `obs_view` binding.
264-
- The proxy source renders the captured texrender texture directly on the GPU, avoiding CPU roundtrips and enabling GPU encoder compatibility.
265-
- Lifecycle: created/destroyed in `startOutput()` / `stopOutput()` when filter input mode (`useFilterInput`) is enabled.
266-
- Key methods: `captureFilterInput()` (called from `video_render`), `renderTexture()` (called from proxy source's `video_render`), `drawCapturedTexture()` (passthrough to main output).
267-
268-
---
269-
270-
## Important Warnings
271-
272-
- **Do NOT call `obs_filter_get_parent()` in the `BranchOutputFilter` constructor** — it returns `nullptr` at that point. Use `addCallback()` instead.
273-
- **Private sources** (not visible in frontend) are intentionally excluded from status dock and timer registration.
274-
- **Settings revisions** (`storedSettingsRev` / `activeSettingsRev`) exist to avoid stopping output during reconnect attempts. Do not bypass this mechanism.
275-
- **Encoder compatibility** — The plugin maps "simple" encoder names to actual encoder IDs, with version-specific fallbacks (OBS 30 vs OBS 31). See `getSimpleVideoEncoder()` in `utils.hpp`.
276-
- **Memory management** — Use OBS RAII wrappers. Raw `bfree()` / `obs_data_release()` calls are error-prone.
277-
- **`.gitignore` uses allowlist pattern** — New top-level files/directories must be explicitly un-ignored with `!` prefix.
278-
- **FilterVideoCapture proxy source** — The proxy source type (`osi_branch_output_proxy`) must be registered at module load via `FilterVideoCapture::createProxySourceInfo()`. The proxy source is private and intentionally not visible in the OBS frontend.
3+
This file provides a brief overview for AI agents. For detailed project documentation, coding guidelines, architecture, and build instructions, see **[CLAUDE.md](CLAUDE.md)**.
4+
5+
## Quick Reference
6+
7+
- **Language**: C++17 with Qt 6
8+
- **Build**: CMake ≥ 3.16
9+
- **Target OBS**: ≥ 30.1.0
10+
- **License**: GPLv2+
11+
- **Maintainer**: OPENSPHERE Inc.
12+
13+
## Key Files
14+
15+
| File | Purpose |
16+
|------|---------|
17+
| `src/plugin-main.cpp` | Plugin entry point, `BranchOutputFilter` core logic |
18+
| `src/plugin-main.hpp` | `BranchOutputFilter` class declaration |
19+
| `src/plugin-ui.cpp` | OBS properties UI, `getDefaults()`, filter settings |
20+
| `src/plugin-streaming.cpp` | Streaming output logic |
21+
| `src/plugin-stream-recording.cpp` | Recording output logic |
22+
| `src/plugin-replay-buffer.cpp` | Replay buffer output logic |
23+
| `src/utils.cpp` / `.hpp` | Utility functions, encoder helpers |
24+
| `src/audio/audio-capture.cpp` | Audio capture abstraction |
25+
| `src/video/filter-video-capture.cpp` | Filter input video capture (GPU texrender proxy) |
26+
| `src/UI/output-status-dock.cpp` | Status dock widget |
27+
| `data/locale/*.ini` | Locale strings (10 languages) |
28+
29+
## Essential Rules
30+
31+
1. Follow OBS plugin conventions (`obs_log()`, `obs_module_text()`, RAII wrappers)
32+
2. All user-facing strings must be localized in **all** `data/locale/*.ini` files
33+
3. Thread safety required — use mutexes for shared state, `Qt::QueuedConnection` for UI updates
34+
4. Code style enforced by `.clang-format` (120 columns, 4-space indent, C++17)
35+
5. No automated tests — manual testing in OBS Studio required
36+
37+
For full details on architecture, coding guidelines, build instructions, CI/CD, and common tasks, refer to **[CLAUDE.md](CLAUDE.md)**.

0 commit comments

Comments
 (0)