Skip to content

Portable ffmpeg linkage + build.sh for releases#43

Merged
kixelated merged 4 commits into
mainfrom
claude/portable-ffmpeg-and-build
Jun 19, 2026
Merged

Portable ffmpeg linkage + build.sh for releases#43
kixelated merged 4 commits into
mainfrom
claude/portable-ffmpeg-and-build

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Makes the plugin link the ffmpeg that the obs-deps bundle ships (the same one OBS uses at runtime) on macOS and Windows, so release binaries are portable, and adds a build.sh for release/CI.

Why

On macOS the build used pkg-config, which reached for Homebrew ffmpeg at an absolute /opt/homebrew path (ffmpeg 8 / libavcodec.62) — the artifact would not load without that exact brew install. On Windows, the obs-deps ship ffmpeg with no pkg-config .pc files, so pkg_check_modules could not find it at all. obs-deps actually bundles ffmpeg (libavcodec.61 = ffmpeg 7, what OBS ships).

Changes

  • CMakeLists.txt: use find_package(FFmpeg) on WIN32 OR APPLE via a vendored FindFFmpeg.cmake that searches the obs-deps prefix directly. Linux keeps pkg-config (system ffmpeg).
  • cmake/common/FindFFmpeg.cmake: vendored OBS-style finder, no pkg-config dependency.
  • cmake/common/bootstrap.cmake: honor -DPLUGIN_VERSION_OVERRIDE so release CI can version the plugin to match the libmoq release it links.
  • cmake/macos/buildspec.cmake: make the xattr quarantine strip non-fatal (it errors on build outputs with no quarantine attribute).
  • build.sh: per-platform configure/build/package. --libmoq-release <ver> fetches a published libmoq release (no local cargo build) and versions the plugin to match; otherwise the buildspec MOQ_VERSION is used. macOS ships the .plugin (zip); Linux/Windows ship the portable layout (obs-moq/bin/64bit/<lib> + data), archived with cmake -E tar.

Context

This repo is canonical for the OBS plugin (an attempt to vendor it into the moq monorepo, moq-dev/moq#1787, was reverted to keep that repo permissively licensed). These fixes were developed and verified there and are ported here.

Test plan

  • macOS arm64: ./build.sh --target aarch64-apple-darwin builds against obs-deps and links @rpath/libavcodec.dylib (ffmpeg 7), not /opt/homebrew. Produces obs-moq-<version>-aarch64-apple-darwin.zip.
  • Windows x64: find_package(FFmpeg) path mirrors the verified macOS path; needs a Windows build to confirm.
  • Linux: pkg-config path unchanged.

Follow-up (not in this PR)

A release workflow that builds against each published libmoq release and a cross-repo trigger from moq-dev/moq (repository_dispatch, needs a PAT) — so the plugin ships with every libmoq release.

(Written by Claude)

Make the plugin link the ffmpeg the obs-deps bundle ships (the same one OBS
uses at runtime) on macOS and Windows, so release binaries are portable:

- CMakeLists: use find_package(FFmpeg) on WIN32 OR APPLE via a vendored
  FindFFmpeg.cmake that searches the obs-deps prefix (obs-deps ship ffmpeg
  with no pkg-config .pc files). Linux keeps pkg-config. Previously macOS used
  pkg-config (reached for Homebrew ffmpeg at an absolute path) and Windows
  couldn't find ffmpeg at all.
- cmake/common/FindFFmpeg.cmake: vendored finder (no pkg-config dependency).
- bootstrap.cmake: honor -DPLUGIN_VERSION_OVERRIDE so release CI can version
  the plugin to match the libmoq release it links.
- cmake/macos/buildspec.cmake: make the xattr quarantine strip non-fatal (it
  errors on build outputs that carry no quarantine attribute).
- build.sh: drive per-platform configure/build/package. --libmoq-release
  fetches a published libmoq release (no local cargo build) and versions the
  plugin to match; otherwise the buildspec MOQ_VERSION is used.

Verified on macOS arm64: links @rpath/libavcodec.dylib (obs-deps ffmpeg 7),
not Homebrew.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kixelated, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 52 minutes and 43 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c70fd830-34eb-4152-9b2a-baee8cb282d6

📥 Commits

Reviewing files that changed from the base of the PR and between 2e8d598 and 2a8a24c.

📒 Files selected for processing (3)
  • .github/scripts/.Aptfile
  • .github/scripts/build-macos
  • cmake/common/FindFFmpeg.cmake

Walkthrough

The PR adds cmake/common/FindFFmpeg.cmake, a full cross-platform CMake find module that creates imported targets (FFmpeg::avcodec, FFmpeg::avutil, FFmpeg::swscale, FFmpeg::swresample, and others) using pkg-config on Linux/FreeBSD, direct library/DLL search on Windows, and binary probing on macOS. CMakeLists.txt is updated to use this module via find_package on Windows and Apple, while falling back to pkg_check_modules on other platforms. cmake/common/bootstrap.cmake gains a PLUGIN_VERSION_OVERRIDE conditional to replace the buildspec-derived version before PLUGIN_VERSION_* variables are set. cmake/macos/buildspec.cmake changes the xattr quarantine-strip step from fatal to best-effort. A new build.sh script is added to configure, build, stage, and archive the plugin as .tar.gz or .zip depending on the target platform.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: portable ffmpeg linkage for Windows/macOS and a build.sh script for releases, matching the core objectives.
Description check ✅ Passed The description comprehensively explains the problem, solution, and all key changes made across multiple files, directly supporting the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/portable-ffmpeg-and-build

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmake/common/bootstrap.cmake (1)

58-71: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate override version shape before deriving PLUGIN_VERSION_*.

The override path assumes at least three dot-separated parts; malformed input crashes at list(GET ...) with an opaque error instead of a clear message.

Suggested fix
 string(REPLACE "." ";" _version_canonical "${_version}")
+list(LENGTH _version_canonical _version_parts_len)
+if(_version_parts_len LESS 3)
+  message(FATAL_ERROR "Invalid plugin version '${_version}'. Expected at least MAJOR.MINOR.PATCH.")
+endif()
 list(GET _version_canonical 0 PLUGIN_VERSION_MAJOR)
 list(GET _version_canonical 1 PLUGIN_VERSION_MINOR)
 list(GET _version_canonical 2 PLUGIN_VERSION_PATCH)
+unset(_version_parts_len)
 unset(_version_canonical)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmake/common/bootstrap.cmake` around lines 58 - 71, Add validation of the
PLUGIN_VERSION_OVERRIDE value before attempting to parse it into version
components. After setting _version from PLUGIN_VERSION_OVERRIDE, verify that the
string contains at least two dots (to ensure three dot-separated parts for
major.minor.patch format). If validation fails, output a clear error message
indicating the expected format and halt execution using message(FATAL_ERROR...),
preventing the subsequent list(GET...) calls from failing with opaque errors
when attempting to extract PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, and
PLUGIN_VERSION_PATCH.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build.sh`:
- Around line 76-95: The macOS pattern matching in the case statement checking
against TARGET is too restrictive. The pattern `*-apple-darwin` does not match
triples with Darwin version suffixes like `arm64-apple-darwin23.x.y`, causing
valid macOS targets to fall through to the unsupported case. Update the pattern
for the macOS branch to use `*-apple-darwin*` to match triples with optional
Darwin version suffixes following the word "darwin".
- Around line 25-45: The flag option parser for --target, --version, --output,
and --libmoq-release does not validate that their required argument values exist
before accessing $2, which causes unhelpful shell errors when set -u is enabled.
Add validation before each variable assignment (TARGET, VERSION, OUTPUT_DIR,
MOQ_RELEASE) to check that $2 is not empty and is not another flag, and provide
clear error messages indicating which flag is missing its required value.

In `@cmake/common/FindFFmpeg.cmake`:
- Around line 166-169: The undefined variable `_library_var` in the assignment
to `FFmpeg_${component}_LIBRARIES` causes it to resolve to empty, breaking
downstream consumers that depend on proper library linking information. Replace
the reference to the undefined `_library_var` with the correct library variable
`FFmpeg_${component}_LIBRARY` in the set statement at line 166 where
`FFmpeg_${component}_LIBRARIES` is assigned. Apply the same fix to the duplicate
issue mentioned at lines 282-284 where the same undefined variable pattern
occurs.

---

Outside diff comments:
In `@cmake/common/bootstrap.cmake`:
- Around line 58-71: Add validation of the PLUGIN_VERSION_OVERRIDE value before
attempting to parse it into version components. After setting _version from
PLUGIN_VERSION_OVERRIDE, verify that the string contains at least two dots (to
ensure three dot-separated parts for major.minor.patch format). If validation
fails, output a clear error message indicating the expected format and halt
execution using message(FATAL_ERROR...), preventing the subsequent list(GET...)
calls from failing with opaque errors when attempting to extract
PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, and PLUGIN_VERSION_PATCH.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 74da4167-29be-4e3c-8d3c-2a53fc381d33

📥 Commits

Reviewing files that changed from the base of the PR and between 4dbe6bc and 83a2d93.

📒 Files selected for processing (5)
  • CMakeLists.txt
  • build.sh
  • cmake/common/FindFFmpeg.cmake
  • cmake/common/bootstrap.cmake
  • cmake/macos/buildspec.cmake

Comment thread build.sh
Comment on lines +25 to +45
while [[ $# -gt 0 ]]; do
case $1 in
--target)
TARGET="$2"
shift 2
;;
--version)
VERSION="$2"
shift 2
;;
--output)
OUTPUT_DIR="$2"
shift 2
;;
--libmoq-release)
# Link a published libmoq release of this version instead of
# building rs/libmoq from source. CMake fetches the matching
# moq-<version>-<target> archive from the GitHub release and the
# plugin is versioned to match. Used by CI on a libmoq-v* tag.
MOQ_RELEASE="$2"
shift 2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Flag options that are missing a required value.

For --target/--version/--output/--libmoq-release, the parser reads $2 without checking it exists. With set -u, this exits abruptly with an unhelpful shell error.

Suggested fix
+require_value() {
+  if [[ $# -lt 2 || "$2" == --* ]]; then
+    echo "Missing value for $1" >&2
+    exit 1
+  fi
+}
+
 while [[ $# -gt 0 ]]; do
 	case $1 in
 	--target)
+		require_value "$@"
 		TARGET="$2"
 		shift 2
 		;;
 	--version)
+		require_value "$@"
 		VERSION="$2"
 		shift 2
 		;;
 	--output)
+		require_value "$@"
 		OUTPUT_DIR="$2"
 		shift 2
 		;;
 	--libmoq-release)
+		require_value "$@"
 		# Link a published libmoq release of this version instead of
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build.sh` around lines 25 - 45, The flag option parser for --target,
--version, --output, and --libmoq-release does not validate that their required
argument values exist before accessing $2, which causes unhelpful shell errors
when set -u is enabled. Add validation before each variable assignment (TARGET,
VERSION, OUTPUT_DIR, MOQ_RELEASE) to check that $2 is not empty and is not
another flag, and provide clear error messages indicating which flag is missing
its required value.

Comment thread build.sh
Comment on lines +76 to +95
case "$TARGET" in
*-linux-*)
PRESET="ubuntu-x86_64"
BUILD_DIR="$SCRIPT_DIR/build_x86_64"
KIND="unix"
;;
*-apple-darwin)
PRESET="macos"
BUILD_DIR="$SCRIPT_DIR/build_macos"
KIND="macos"
;;
*-windows-*)
PRESET="windows-x64"
BUILD_DIR="$SCRIPT_DIR/build_x64"
KIND="windows"
;;
*)
echo "Unsupported target: $TARGET" >&2
exit 1
;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

macOS auto-detection pattern is too strict and can reject valid triples.

cc -dumpmachine on macOS commonly includes a Darwin version suffix (for example, arm64-apple-darwin23.x.y), which does not match *-apple-darwin and falls into “Unsupported target”.

Suggested fix
-*-apple-darwin)
+*-apple-darwin*)
  PRESET="macos"
  BUILD_DIR="$SCRIPT_DIR/build_macos"
  KIND="macos"
  ;;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case "$TARGET" in
*-linux-*)
PRESET="ubuntu-x86_64"
BUILD_DIR="$SCRIPT_DIR/build_x86_64"
KIND="unix"
;;
*-apple-darwin)
PRESET="macos"
BUILD_DIR="$SCRIPT_DIR/build_macos"
KIND="macos"
;;
*-windows-*)
PRESET="windows-x64"
BUILD_DIR="$SCRIPT_DIR/build_x64"
KIND="windows"
;;
*)
echo "Unsupported target: $TARGET" >&2
exit 1
;;
case "$TARGET" in
*-linux-*)
PRESET="ubuntu-x86_64"
BUILD_DIR="$SCRIPT_DIR/build_x86_64"
KIND="unix"
;;
*-apple-darwin*)
PRESET="macos"
BUILD_DIR="$SCRIPT_DIR/build_macos"
KIND="macos"
;;
*-windows-*)
PRESET="windows-x64"
BUILD_DIR="$SCRIPT_DIR/build_x64"
KIND="windows"
;;
*)
echo "Unsupported target: $TARGET" >&2
exit 1
;;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build.sh` around lines 76 - 95, The macOS pattern matching in the case
statement checking against TARGET is too restrictive. The pattern
`*-apple-darwin` does not match triples with Darwin version suffixes like
`arm64-apple-darwin23.x.y`, causing valid macOS targets to fall through to the
unsupported case. Update the pattern for the macOS branch to use
`*-apple-darwin*` to match triples with optional Darwin version suffixes
following the word "darwin".

Comment on lines +166 to +169
if(FFmpeg_${component}_LIBRARY AND FFmpeg_${component}_INCLUDE_DIR)
set(FFmpeg_${component}_FOUND TRUE)
set(FFmpeg_${component}_LIBRARIES ${${_library_var}})
set(FFmpeg_${component}_INCLUDE_DIRS ${FFmpeg_${component}_INCLUDE_DIR})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Undefined _library_var empties component library outputs and breaks variable-based consumers.

FFmpeg_${component}_LIBRARIES is assigned from an undefined _library_var, so it resolves empty. Downstream, FFmpeg_LIBRARIES is built from FFmpeg_${component}_LIBRARY (DLL path on Windows), which is not the right link artifact for variable-based linking.

Suggested fix
@@
   if(FFmpeg_${component}_LIBRARY AND FFmpeg_${component}_INCLUDE_DIR)
     set(FFmpeg_${component}_FOUND TRUE)
-    set(FFmpeg_${component}_LIBRARIES ${${_library_var}})
+    if(CMAKE_HOST_SYSTEM_NAME MATCHES "Windows" AND FFmpeg_${component}_IMPLIB)
+      set(FFmpeg_${component}_LIBRARIES "${FFmpeg_${component}_IMPLIB}")
+    else()
+      set(FFmpeg_${component}_LIBRARIES "${FFmpeg_${component}_LIBRARY}")
+    endif()
     set(FFmpeg_${component}_INCLUDE_DIRS ${FFmpeg_${component}_INCLUDE_DIR})
@@
   if(FFmpeg_${component}_FOUND)
-    list(APPEND FFmpeg_LIBRARIES ${FFmpeg_${component}_LIBRARY})
+    list(APPEND FFmpeg_LIBRARIES ${FFmpeg_${component}_LIBRARIES})
     list(APPEND FFmpeg_DEFINITIONS ${FFmpeg_${component}_DEFINITIONS})
     list(APPEND FFmpeg_INCLUDE_DIRS ${FFmpeg_${component}_INCLUDE_DIR})
   endif()

Also applies to: 282-284

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmake/common/FindFFmpeg.cmake` around lines 166 - 169, The undefined variable
`_library_var` in the assignment to `FFmpeg_${component}_LIBRARIES` causes it to
resolve to empty, breaking downstream consumers that depend on proper library
linking information. Replace the reference to the undefined `_library_var` with
the correct library variable `FFmpeg_${component}_LIBRARY` in the set statement
at line 166 where `FFmpeg_${component}_LIBRARIES` is assigned. Apply the same
fix to the duplicate issue mentioned at lines 282-284 where the same undefined
variable pattern occurs.

kixelated and others added 3 commits June 19, 2026 11:05
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two pre-existing CI gaps that blocked the plugin build:

- Ubuntu: the .Aptfile installed no ffmpeg dev libraries, so the Linux
  pkg_check_modules(FFMPEG ...) failed at configure. Add libav{codec,util}-dev
  and libsw{scale,resample}-dev.
- macOS: build-macos forced a universal (arm64 + x86_64) build, but the plugin
  links a published libmoq release, which ships per-architecture (no universal
  archive). The x86_64 slice couldn't link the arm64 libmoq. Build arm64 only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Local nix gersemi (0.23.2) formats differently than CI's pinned 0.21.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kixelated kixelated merged commit 0fa282c into main Jun 19, 2026
6 checks passed
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