Skip to content

Commit f9a2238

Browse files
hanatyan128claude
andcommitted
refactor: Compress verbose comments and API documentation
Trim review-round artifacts where long justification comments were added to defend design choices. Replace prose with concise FIXMEs where the root-cause fix is deferred. Drop internal-implementation details from API.md / API_ja.md that callers do not need. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a6bfed3 commit f9a2238

9 files changed

Lines changed: 162 additions & 316 deletions

API.md

Lines changed: 24 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ This feature was implemented in response to production requirements where record
1616

1717
All procedures in this API follow these threading rules:
1818

19-
- **Thread safety**: The override procedures may be called from any thread. Internally they acquire the filter's `outputMutex` and, depending on the current recording state, may call `obs_output_update()` (when file splitting is enabled) or defer work to the next interval-timer tick (which performs a recording restart). The call is therefore not guaranteed to be constant-time — avoid calling it on latency-sensitive hot paths.
20-
- **Callback safety**: These procedures **must not be called from OBS signal callbacks (`obs_source_signal`, `obs_output_signal`, etc.) or from frontend event callbacks (`obs_frontend_event_callback`)**. Such callbacks may already hold — or may indirectly acquire — the filter's `outputMutex` or a libobs output lock, which would cause a deadlock against the locks taken inside the override procedure. Prefer calling them from script timer callbacks, hotkey handlers, or UI event handlers instead. The "hotkey handlers" guidance here assumes **scripting hotkeys registered via `obslua` / `obspython`** (which dispatch from a safe context without holding any filter-side locks). If you are calling these procedures from a **native plugin** hotkey callback registered with `obs_hotkey_register_*`, be aware that such callbacks may be dispatched from threads that already hold locks unrelated to Branch Output; verify that your hotkey-dispatch context does not hold a lock that the Branch Output `outputMutex` or the Qt UI thread might need. The same restriction applies to `osi_branch_output_get_filter_list` below, which synchronizes with the Qt UI thread via a blocking queued connection and will deadlock if the caller holds a lock that the UI thread is waiting on.
21-
- **Deferred application during pending states**: When recording is in `recordingPending` or a split/restart is in progress, the override is stored and applied on the next interval tick (via the internal 1-second `intervalTimer`) rather than taking effect synchronously. The proc call still returns immediately; there is no indication when the new filename format becomes active.
19+
- **Thread safety**: Callable from any thread. The call is not guaranteed to be constant-time (a recording restart or file split may occur), so avoid latency-sensitive hot paths.
20+
- **Callback safety**: Do not call from OBS signal callbacks (`obs_source_signal`, `obs_output_signal`, etc.) or frontend event callbacks (`obs_frontend_event_callback`) — a deadlock may occur. Safe callers: script timers, hotkey handlers, UI event handlers. When calling from a hotkey callback, ensure your own code does not hold a Branch Output lock at the call site. The same restriction applies to `osi_branch_output_get_filter_list` below.
21+
- **Deferred application**: If recording is transitioning (pending, split, or restart in progress), the override is stored and applied with up to about 1 second of delay rather than synchronously. The proc call returns immediately and there is no notification when the new format becomes active.
2222

2323
### Obtaining the Filter Source
2424

@@ -56,7 +56,7 @@ A procedure registered on the Branch Output filter source that overrides the out
5656
```python
5757
import obspython as obs
5858

59-
# Get the target Branch Output filter by UUID
59+
# Get the target Branch Output filter by UUID. Pass "" as format to clear.
6060
bo_filter = obs.obs_get_source_by_uuid(filter_uuid)
6161
if bo_filter:
6262
ph = obs.obs_source_get_proc_handler(bo_filter)
@@ -65,24 +65,14 @@ if bo_filter:
6565
obs.proc_handler_call(ph, "override_recording_filename_format", cd)
6666
obs.calldata_free(cd)
6767
obs.obs_source_release(bo_filter)
68-
69-
# Clear the override (pass an empty string)
70-
bo_filter = obs.obs_get_source_by_uuid(filter_uuid)
71-
if bo_filter:
72-
ph = obs.obs_source_get_proc_handler(bo_filter)
73-
cd = obs.calldata_create()
74-
obs.calldata_set_string(cd, "format", "")
75-
obs.proc_handler_call(ph, "override_recording_filename_format", cd)
76-
obs.calldata_free(cd)
77-
obs.obs_source_release(bo_filter)
7868
```
7969

8070
**Lua sample code**
8171

8272
```lua
8373
local obs = obslua
8474

85-
-- Get the target Branch Output filter by UUID
75+
-- Get the target Branch Output filter by UUID. Pass "" as format to clear.
8676
local bo_filter = obs.obs_get_source_by_uuid(filter_uuid)
8777
if bo_filter ~= nil then
8878
local ph = obs.obs_source_get_proc_handler(bo_filter)
@@ -92,17 +82,6 @@ if bo_filter ~= nil then
9282
obs.calldata_free(cd)
9383
obs.obs_source_release(bo_filter)
9484
end
95-
96-
-- Clear the override (pass an empty string)
97-
local bo_filter = obs.obs_get_source_by_uuid(filter_uuid)
98-
if bo_filter ~= nil then
99-
local ph = obs.obs_source_get_proc_handler(bo_filter)
100-
local cd = obs.calldata_create()
101-
obs.calldata_set_string(cd, "format", "")
102-
obs.proc_handler_call(ph, "override_recording_filename_format", cd)
103-
obs.calldata_free(cd)
104-
obs.obs_source_release(bo_filter)
105-
end
10685
```
10786

10887
### Overriding the Replay Buffer Save Filename Format
@@ -124,7 +103,7 @@ The overridden filename format takes effect on the next replay buffer save. The
124103
```python
125104
import obspython as obs
126105

127-
# Get the target Branch Output filter by UUID
106+
# Get the target Branch Output filter by UUID. Pass "" as format to clear.
128107
bo_filter = obs.obs_get_source_by_uuid(filter_uuid)
129108
if bo_filter:
130109
ph = obs.obs_source_get_proc_handler(bo_filter)
@@ -133,24 +112,14 @@ if bo_filter:
133112
obs.proc_handler_call(ph, "override_replay_buffer_filename_format", cd)
134113
obs.calldata_free(cd)
135114
obs.obs_source_release(bo_filter)
136-
137-
# Clear the override (pass an empty string)
138-
bo_filter = obs.obs_get_source_by_uuid(filter_uuid)
139-
if bo_filter:
140-
ph = obs.obs_source_get_proc_handler(bo_filter)
141-
cd = obs.calldata_create()
142-
obs.calldata_set_string(cd, "format", "")
143-
obs.proc_handler_call(ph, "override_replay_buffer_filename_format", cd)
144-
obs.calldata_free(cd)
145-
obs.obs_source_release(bo_filter)
146115
```
147116

148117
**Lua sample code**
149118

150119
```lua
151120
local obs = obslua
152121

153-
-- Get the target Branch Output filter by UUID
122+
-- Get the target Branch Output filter by UUID. Pass "" as format to clear.
154123
local bo_filter = obs.obs_get_source_by_uuid(filter_uuid)
155124
if bo_filter ~= nil then
156125
local ph = obs.obs_source_get_proc_handler(bo_filter)
@@ -160,17 +129,6 @@ if bo_filter ~= nil then
160129
obs.calldata_free(cd)
161130
obs.obs_source_release(bo_filter)
162131
end
163-
164-
-- Clear the override (pass an empty string)
165-
local bo_filter = obs.obs_get_source_by_uuid(filter_uuid)
166-
if bo_filter ~= nil then
167-
local ph = obs.obs_source_get_proc_handler(bo_filter)
168-
local cd = obs.calldata_create()
169-
obs.calldata_set_string(cd, "format", "")
170-
obs.proc_handler_call(ph, "override_replay_buffer_filename_format", cd)
171-
obs.calldata_free(cd)
172-
obs.obs_source_release(bo_filter)
173-
end
174132
```
175133

176134
### Retrieving the Branch Output Filter List
@@ -187,10 +145,11 @@ A global procedure that returns the list of Branch Output filters currently load
187145

188146
**Notes**
189147

190-
- The procedure is registered during `obs_module_post_load()`. Calling it before this point (e.g., during early OBS Studio module load) returns an empty list.
148+
- The procedure is registered during `obs_module_post_load()`. Calling it before registration completes fails: `proc_handler_call()` returns `false` and the `out string json` parameter is not written. Always check the return value before reading `json`.
191149
- Filters applied to **private sources** (sources not visible in the OBS frontend) are intentionally excluded from the returned list, matching the Status Dock's visibility rules.
192-
- The returned list is a snapshot taken at call time. Callers that need to react to filter additions/removals should poll periodically or refresh on demand.
193-
- **Thread safety**: This procedure may be called from any thread. Internally, the implementation reads the Status Dock's filter table, which must be accessed from the Qt UI thread. When called from a non-UI thread (e.g., an obs-websocket worker), the read is dispatched to the UI thread via a blocking queued connection. When called from the UI thread itself (e.g., a frontend plugin or a hotkey handler), the read is performed directly to avoid self-deadlock. Callers do not need to worry about the distinction.
150+
- The returned list is a snapshot taken at call time. Poll periodically or refresh on demand if you need to react to filter additions/removals.
151+
- **Thread safety**: Callable from any thread.
152+
- **Lifetime**: Do not call after `obs_module_unload()` — behavior is undefined.
194153

195154
**Returned JSON structure**
196155

@@ -292,50 +251,29 @@ Two variants of each sample script are bundled with the plugin: **Python** (`.py
292251

293252
On Windows, the scripts are installed at `data\obs-plugins\osi-branch-output\scripts\` under the OBS installation path. On macOS and Linux, the path follows the standard OBS plugin data directory convention.
294253

295-
#### recording-filename-from-text.py / recording-filename-from-text.lua
254+
#### Common setup
296255

297-
A script that reads the value of a text input and applies it to the stream recording filename format.
256+
Both sample scripts share the same setup flow:
298257

299258
1. Open OBS menu → Tools → Scripts
300259
2. Click the plus (+) button at the bottom of the Scripts dialog
301260
3. Select the script file — either the `.py` or the `.lua` variant.
302-
4. When you select the script in Loaded Scripts, various settings can be configured in the Description panel:
261+
4. In the Description panel, configure:
303262
- **Text Source** — Select the text input
304263
- **Branch Output Filter** — Select the Branch Output filter to override
305-
- **Base Filename Format** — Specify the base filename format. This format is appended to the end of the filename.
306-
5. The override becomes active as soon as you configure these settings. Click Script Log to see the script's activity.
307-
Example: `[recording-filename-from-text.lua] Recording filename format updated: test %CCYY-%MM-%DD %hh-%mm-%ss`
308-
6. When recording with the override active, the overridden filename takes precedence.
309-
7. To disable the override, remove the script from Loaded Scripts using the trash button.
310-
311-
> **Behavior during recording**
312-
>
313-
> - Before recording starts: The overridden filename format is used when recording begins
314-
> - During recording (with file splitting enabled): A file split is triggered immediately when the filename format changes
315-
> - During recording (without file splitting): Recording is restarted with the new filename format
316-
317-
**Note:** While the override is active, the filename setting in the filter properties is not used.
318-
319-
**Throttling:** To avoid excessive file splits or recording restarts when the text source changes rapidly, the sample script throttles updates to at most one apply per 30 seconds per distinct text value. As a result, a change in the text source may take up to 30 seconds to be reflected in the recording filename. Edit the `THROTTLE_SECONDS` constant at the top of the script to tune this interval.
264+
- **Base Filename Format** — The base format appended to the end of the filename.
265+
5. The override becomes active immediately. Check **Script Log** to confirm activity.
266+
6. The overridden filename takes precedence over the filter's own setting while the script is loaded; the filter property value is not used.
267+
7. To disable the override, remove the script via the trash button.
320268

321-
**Windows + "Read from file" — Lua limitation:** When the text source is configured to read from a file, the Lua variant uses `io.open()`, which on Windows calls the CRT `fopen()` and interprets the path in the system ANSI code page (e.g. CP932 on Japanese locale). Paths containing characters that are not representable in the ANSI code page may therefore fail to open from the Lua script. The Python variant is not affected because CPython uses wide-character Windows APIs internally. Use the Python variant when you need full UTF-8 path support on Windows.
269+
**Windows + "Read from file" — Lua limitation:** When the text source reads from a file, the Lua variant uses `io.open()`, which on Windows interprets the path in the system ANSI code page (e.g. CP932 on Japanese locale). Paths containing characters outside that code page may therefore fail to open. The Python variant is not affected (CPython uses wide-character Windows APIs). Use the Python variant if full UTF-8 path support is required on Windows.
322270

323-
#### replay-buffer-filename-from-text.py / replay-buffer-filename-from-text.lua
271+
#### recording-filename-from-text
324272

325-
A script that reads the value of a text input and applies it to the replay buffer save filename format.
273+
Reads the value of a text input and applies it to the stream recording filename format. See [behavior during recording](#overriding-the-stream-recording-filename-format) above for how the override is applied in each recording state.
326274

327-
1. Open OBS menu → Tools → Scripts
328-
2. Click the plus (+) button at the bottom of the Scripts dialog
329-
3. Select the script file — either the `.py` or the `.lua` variant.
330-
4. When you select the script in Loaded Scripts, various settings can be configured in the Description panel:
331-
- **Text Source** — Select the text input
332-
- **Branch Output Filter** — Select the Branch Output filter to override
333-
- **Base Filename Format** — Specify the base filename format. This format is appended to the end of the filename.
334-
5. The override becomes active as soon as you configure these settings. Click Script Log to see the script's activity.
335-
Example: `[replay-buffer-filename-from-text.lua] Replay buffer filename format updated: test %CCYY-%MM-%DD %hh-%mm-%ss`
336-
6. When saving with the override active, the overridden filename takes precedence.
337-
7. To disable the override, remove the script from Loaded Scripts using the trash button.
275+
**Throttling:** To avoid excessive file splits or recording restarts when the text source changes rapidly, the script throttles updates to one apply per 30 seconds per distinct text value. A text change may therefore take up to 30 seconds to be reflected. Adjust `THROTTLE_SECONDS` at the top of the script to tune this interval.
338276

339-
**Note:** While the override is active, the filename setting in the filter properties is not used.
277+
#### replay-buffer-filename-from-text
340278

341-
**Windows + "Read from file" — Lua limitation:** The same limitation noted for `recording-filename-from-text.lua` applies here. When the text source reads from a file whose path contains non-ANSI characters, the Lua variant may fail to open the file on Windows. Use the Python variant if full UTF-8 path support is required.
279+
Reads the value of a text input and applies it to the replay buffer save filename format. The override takes effect on the next save; the replay buffer itself is not restarted.

0 commit comments

Comments
 (0)