Skip to content

feat: Expose filename-override operations as obs-websocket vendor requests#143

Closed
hanatyan128 wants to merge 17 commits into
devfrom
feat-obs-websocket-vendor-request
Closed

feat: Expose filename-override operations as obs-websocket vendor requests#143
hanatyan128 wants to merge 17 commits into
devfrom
feat-obs-websocket-vendor-request

Conversation

@hanatyan128

Copy link
Copy Markdown
Member

Summary

  • Register osi_branch_output obs-websocket vendor with three requests that mirror the existing proc handler APIs: get_filter_list, override_recording_filename_format, override_replay_buffer_filename_format.
  • Allow external tools, bots, and Stream Deck plugins to drive filename overrides over obs-websocket without running an in-process OBS script.
  • Add lib/obs-websocket as a tag-pinned git submodule (5.7.3) for the vendor API headers only; no runtime linkage.
  • Document the vendor API in API.md / API_ja.md with the same audience as the existing proc handler reference; README.md / README_ja.md now point there.

Vendor requests

Request Request data Response
get_filter_list (none) { success, error?, filters: array }
override_recording_filename_format { filter_uuid, format } { success, error? }
override_replay_buffer_filename_format { filter_uuid, format } { success, error? }
  • filters elements expose source_name, source_uuid, filter_name, filter_uuid, matching the existing osi_branch_output_get_filter_list proc handler.
  • Passing an empty format clears the override (same semantics as the proc handlers).

Input validation

  • filter_uuid must be a 36-character hyphenated UUID. Non-matching shapes return "filter_uuid must be a UUID string".
  • format is capped at 1024 bytes (strnlen early-exit). Oversized inputs return "format too long".
  • The resolved source must be a Branch Output filter (osi_branch_output). Non-matching sources and missing UUIDs both return "Filter not found" to avoid leaking existence information.
  • obs_source_get_id() null-guard added for defensive safety on the ws worker thread path.

Security model

  • Vendor requests are accepted only from clients authenticated with obs-websocket.
  • obs-websocket auth is a single shared password with no per-vendor capability model, so any authenticated client has full access equivalent to the OBS user. The format string is forwarded to libobs's filename expander unchanged and can therefore include subdirectory paths or overwrite existing recordings.
  • API.md / API_ja.md capture this explicitly in the Security section.

Build / CI

  • CMakeLists.txt: FATAL_ERROR guard if the submodule is missing; CMAKE_CURRENT_SOURCE_DIR used for super-build robustness.
  • .gitmodules: tag-pinned operation documented via comment (no branch = entry so --remote cannot drift).
  • build-aux/.run-format.zsh: exclude lib/* so local cmake-format runs do not pick up submodule files.
  • .gitignore: allowlist !/lib to un-ignore the submodule directory.
  • .github/workflows/check-format.yaml: unchanged (submodule checkout not required; format scope is src/ and project CMake files).

Threading / lifecycle caveats

  • Vendor callbacks run on obs-websocket worker threads and block on outputMutex via the underlying proc handlers. A recording transition (split/restart) can pin the caller for up to ~1 s. Documented inline as a FIXME for future rework (in-flight counter + timeout).
  • obs_websocket_vendor_unregister_request() does not wait for in-flight callbacks; documented as a FIXME parallel to the existing statusDock shutdown race.

Test plan

  • Load the plugin with obs-websocket installed; confirm the vendor and three requests are registered (check OBS log).
  • Load the plugin without obs-websocket installed; confirm a warning is logged and the plugin still loads.
  • Call get_filter_list via an obs-websocket client; confirm filters lists every Branch Output filter with correct source_* / filter_* fields.
  • Call override_recording_filename_format with a valid filter UUID and observe the next recording uses the new format; pass "" to clear.
  • Call override_replay_buffer_filename_format with a valid filter UUID and confirm the next replay save uses the new format; pass "" to clear.
  • Send malformed input (short UUID, 2 MB format, non-filter source UUID); confirm each returns the expected success=false + error message.
  • Build on Windows / macOS / Linux with the submodule initialized.
  • Run cmake --fresh without the submodule and confirm the FATAL_ERROR message instructs git submodule update --init --recursive.
  • Run ./build-aux/run-cmake-format --check locally with the submodule present; confirm no submodule files are scanned.

🤖 Generated with Claude Code

@hanatyan128 hanatyan128 self-assigned this Apr 20, 2026
@hanatyan128 hanatyan128 mentioned this pull request Apr 20, 2026
@hanatyan128
hanatyan128 force-pushed the feat-obs-websocket-vendor-request branch 6 times, most recently from d22598e to 6f5a051 Compare April 26, 2026 04:04
@hanatyan128 hanatyan128 added this to the 1.1.0 milestone Apr 26, 2026
@hanatyan128
hanatyan128 changed the base branch from dev to chore-add-lua-sample-codes April 26, 2026 04:51
@hanatyan128
hanatyan128 changed the base branch from chore-add-lua-sample-codes to dev April 26, 2026 04:52
@hanatyan128
hanatyan128 force-pushed the feat-obs-websocket-vendor-request branch 3 times, most recently from ed982fa to 3ea123e Compare May 8, 2026 03:37
hanatyan128 and others added 16 commits May 8, 2026 12:37
Add Lua variants of recording-filename-from-text and
replay-buffer-filename-from-text sample scripts. Both include:
- Robust filename sanitization (control chars, trailing dots/spaces,
  Windows reserved names)
- UTF-8 BOM stripping and UTF-16 detection
- 4 KB read size limit for safety
- Filter id verification before proc calls
- Proper cleanup on filter selection change and script reload

Enhance API.md and API_ja.md with:
- Threading and call context requirements
- Filter source acquisition guide
- osi_branch_output_get_filter_list notes (post_load timing,
  private source exclusion)
- Sample scripts section covering both Python and Lua variants

Update README TIPS sections to mention both Python and Lua variants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align recording/replay-buffer filename-from-text sample scripts so the
Lua and Python variants share the same robustness fixes: version note
corrected to v1.0.9+, override_cleared flag to suppress redundant proc
calls, enhanced sanitize_filename (control chars, trailing dots/spaces,
Windows reserved names), BOM handling with 4KB read limit, old-filter
override clearing on selection change, filter ID validation with proc
return-value logging, and defensive timer_remove in script_load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- onGetFilterList: dispatch to UI thread via QMetaObject::invokeMethod,
  with same-thread direct-call fallback to avoid BlockingQueuedConnection
  self-deadlock; null-out statusDock on module unload
- Sample scripts: reset last_text (and throttle state) on clear_override,
  warn on empty file path, extend Windows reserved-name check to cover
  base names with extensions (e.g. CON.txt)
- API docs: revise threading notes and add call-context guidance for
  osi_branch_output_get_filter_list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…etime

Round 1-3 review findings across sample scripts and related native code:
- Lua sanitize_filename now normalizes NBSP/U+3000/ZWSP/U+2028/U+2029/BOM
- Document Windows ANSI code page limitation of Lua io.open
- statusDock promoted to std::atomic and unpublished before dock teardown
- Clarify callback safety and hotkey context in API docs
- Document lock ordering and proc_handler lifetime in native code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the per-byte-sequence gsub chain in sanitize_filename() with a
utf8.codes() walk driven by a strip/space lookup. Also folds the en/em
space family (U+2000–200A) for parity with the Python variant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Lua: replace Lua 5.3 utf8 library with LuaJIT-compatible manual
  UTF-8 decoder/encoder (utf8_codepoints, cp_to_utf8), removing
  dead code and misleading pcall warning
- C++: change JSON fallback from empty string to '{}' for safer
  downstream parsing
- Python: wrap calldata_create/calldata_free in try/finally to
  prevent leaks on exception
- Python: add MAX_SANITIZED_LENGTH=128 to sanitize_filename to
  avoid exceeding Windows MAX_PATH
Lua/Python scripts:
- Truncate sanitized filenames to 200 bytes with UTF-8 boundary respect
- Call clear_override() when text source is deselected in script_update()
- Validate empty base_format to prevent ambiguous empty overrides (Lua)
- Add group source limitation note to script_description()

C++:
- Use statusDock.store() instead of implicit operator= for consistency
- Fix comment: 'acquire/release semantics' -> 'sequential consistency'
- Clarify Q_DECLARE_METATYPE comment as qRegisterMetaType safety net
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>
- Defer last_text update to proc call success to prevent failed calls
  from being silently swallowed on subsequent ticks
- Fix 4-branch new_format logic in Python to match Lua (trailing space,
  unintended clear when base_format is empty)
- Add UTF-8 validation for non-UTF-8 file content (CP932 etc.)
- Add cp_to_utf8 surrogate-half guard
- Detect and warn on file truncation at MAX_READ_SIZE
- Unify sanitize_filename step order with Python variant
- Move MAX_FILENAME_BYTES to module-level constants
- Log warning on JSON parse failure
- Remove redundant state resets after clear_override()
Expose the existing proc handlers (get_filter_list,
override_recording_filename_format,
override_replay_buffer_filename_format) as obs-websocket vendor
requests under vendor name "osi_branch_output". Vendor request
callbacks delegate to the proc handlers so logic stays in one place.

Add obs-websocket as a git submodule under lib/ for the API header.
- Validate filter_uuid length, format size (strnlen), and filter type
- Return success/error protocol consistently across vendor requests
- Add FIXME for ws worker thread and in-flight unregister race
- Guard missing obs-websocket submodule with CMake FATAL_ERROR
- Document submodule tag-pin policy in .gitmodules
- Exclude lib/ from run-cmake-format local scan
- Relocate the vendor request section from README to API.md / API_ja.md
- README now points to the API reference for details
- Trim internal implementation notes; keep only user-facing limitations
- Move FILTER_ID and PROC_OVERRIDE_* proc names to plugin-main.hpp as shared constants
- Normalize UUID input to lowercase before obs_get_source_by_uuid() lookup
- Move vendor.store() to after all request registrations complete
- Replace locale-dependent std::isxdigit/isalpha with explicit ASCII range checks
- Reorder obs_module_unload() to null statusDock before unregistering vendor requests
- Remove lib/ from CPACK_SOURCE_IGNORE_FILES to preserve obs-websocket-api.h in source tarballs
- Add SYSTEM keyword to obs-websocket include directory in CMakeLists.txt
- Add early exit in build.ps1 on submodule update failure
- Fix cmake-format glob pattern for lib/ submodule exclusion
- Add submodule init instructions to README / README_ja
- Correct error strings in API documentation
Submodule presence is checked only when the option is ON, with FATAL_ERROR
on absence. Includes accumulated input validation, lifetime annotation, and
Security documentation refinements.
@hanatyan128
hanatyan128 force-pushed the feat-obs-websocket-vendor-request branch from 3ea123e to 85e1ec6 Compare May 8, 2026 03:38
@hanatyan128 hanatyan128 closed this May 8, 2026
@hanatyan128
hanatyan128 deleted the feat-obs-websocket-vendor-request branch May 8, 2026 03:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant