build: assemble pinned OBS 32.0.4 runtimes for Linux and Windows
Linux builds libobs and an allowlist of official plugins from source in OBS's relocatable layout. Windows copies the official release byte-for-byte so the Game Capture hook keeps its signature. Adds a headless Wayland rig for PipeWire/portal capture tests on machines without a display.
This commit is contained in:
Executable
+68
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Assemble the pinned OBS runtime for Windows x64 from the official release zip.
|
||||||
|
#
|
||||||
|
# Files are copied byte-for-byte from the OBS release so the signed Game
|
||||||
|
# Capture hook (graphics-hook64.dll, inject-helper, get-graphics-offsets) keeps
|
||||||
|
# its original signature. Runs on Linux or Git Bash; needs curl, unzip and
|
||||||
|
# sha256sum.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=versions.env
|
||||||
|
source "${script_dir}/versions.env"
|
||||||
|
|
||||||
|
cache_dir="${CLIPBIN_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/clipbin}"
|
||||||
|
zip_name="OBS-Studio-${OBS_VERSION}-Windows-x64.zip"
|
||||||
|
zip_path="${cache_dir}/${zip_name}"
|
||||||
|
extract_dir="${cache_dir}/obs-windows-${OBS_VERSION}"
|
||||||
|
bundle_dir="${CLIPBIN_OBS_RUNTIME:-${script_dir}/../../target/obs-runtime/windows-x86_64}"
|
||||||
|
|
||||||
|
# Official plugins the recorder uses: Game/Window Capture, WASAPI audio,
|
||||||
|
# hardware encoders (AMF lives in obs-ffmpeg) and the FFmpeg muxer.
|
||||||
|
plugins=(win-capture win-wasapi obs-ffmpeg obs-nvenc obs-qsv11)
|
||||||
|
# OBS front-end, scripting and software-encoder files Clipbin does not use.
|
||||||
|
# Subdirectories of bin/64bit are Qt plugins and are skipped too.
|
||||||
|
excluded_bin='^(obs64\.exe|obs-frontend-api\.dll|obs-scripting\.dll|lua51\.dll|Qt6.*\.dll|datachannel\.dll|libx264-.*\.dll|.*\.pdb)$'
|
||||||
|
|
||||||
|
mkdir -p "${cache_dir}"
|
||||||
|
if [[ ! -f "${zip_path}" ]]; then
|
||||||
|
curl -fL --retry 3 -o "${zip_path}.partial" \
|
||||||
|
"https://github.com/obsproject/obs-studio/releases/download/${OBS_VERSION}/${zip_name}"
|
||||||
|
mv "${zip_path}.partial" "${zip_path}"
|
||||||
|
fi
|
||||||
|
echo "${OBS_WINDOWS_X64_SHA256} ${zip_path}" | sha256sum --check --quiet
|
||||||
|
|
||||||
|
rm -rf "${extract_dir}"
|
||||||
|
unzip -q "${zip_path}" -d "${extract_dir}"
|
||||||
|
|
||||||
|
# Keep the OBS install layout: OBS looks for obs-ffmpeg-mux.exe next to the
|
||||||
|
# running executable, so Clipbin's executable lives in bin/64bit.
|
||||||
|
rm -rf "${bundle_dir}"
|
||||||
|
mkdir -p "${bundle_dir}/bin/64bit" "${bundle_dir}/obs-plugins/64bit" "${bundle_dir}/data/obs-plugins"
|
||||||
|
for file in "${extract_dir}"/bin/64bit/*; do
|
||||||
|
name="$(basename "${file}")"
|
||||||
|
if [[ -f "${file}" && ! "${name}" =~ ${excluded_bin} ]]; then
|
||||||
|
cp -a "${file}" "${bundle_dir}/bin/64bit/"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
cp -a "${extract_dir}/data/libobs" "${bundle_dir}/data/libobs"
|
||||||
|
for plugin in "${plugins[@]}"; do
|
||||||
|
cp -a "${extract_dir}/obs-plugins/64bit/${plugin}.dll" "${bundle_dir}/obs-plugins/64bit/"
|
||||||
|
if [[ -d "${extract_dir}/data/obs-plugins/${plugin}" ]]; then
|
||||||
|
cp -a "${extract_dir}/data/obs-plugins/${plugin}" "${bundle_dir}/data/obs-plugins/"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
find "${bundle_dir}" -name '*.pdb' -delete
|
||||||
|
|
||||||
|
# Record the signed capture-hook files so a Windows check can verify them
|
||||||
|
# against Get-AuthenticodeSignature before packaging.
|
||||||
|
(cd "${bundle_dir}" && sha256sum data/obs-plugins/win-capture/*.exe data/obs-plugins/win-capture/*.dll) \
|
||||||
|
> "${bundle_dir}/SIGNED-HOOKS.sha256"
|
||||||
|
|
||||||
|
cat > "${bundle_dir}/VERSION" <<EOF
|
||||||
|
obs=${OBS_VERSION}
|
||||||
|
source=${zip_name}
|
||||||
|
sha256=${OBS_WINDOWS_X64_SHA256}
|
||||||
|
plugins=${plugins[*]}
|
||||||
|
EOF
|
||||||
|
echo "OBS ${OBS_VERSION} Windows runtime assembled in ${bundle_dir}"
|
||||||
Executable
+97
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build the pinned OBS runtime for Linux and assemble the bundle Clipbin ships.
|
||||||
|
#
|
||||||
|
# Only libobs and an allowlist of official OBS plugins enter the bundle. The
|
||||||
|
# Windows bundle uses the official OBS release binaries instead (see
|
||||||
|
# scripts/obs/fetch-windows.ps1) so the signed Game Capture hooks stay intact.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=versions.env
|
||||||
|
source "${script_dir}/versions.env"
|
||||||
|
|
||||||
|
cache_dir="${CLIPBIN_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/clipbin}"
|
||||||
|
source_dir="${cache_dir}/obs-studio-${OBS_VERSION}"
|
||||||
|
build_dir="${cache_dir}/obs-build-${OBS_VERSION}"
|
||||||
|
stage_dir="${cache_dir}/obs-stage-${OBS_VERSION}"
|
||||||
|
bundle_dir="${CLIPBIN_OBS_RUNTIME:-${script_dir}/../../target/obs-runtime/linux-x86_64}"
|
||||||
|
|
||||||
|
# Official plugins the recorder uses: capture, audio, hardware encoders and the
|
||||||
|
# FFmpeg muxer. obs-outputs (RTMP, native MP4) is built but not shipped.
|
||||||
|
plugins=(linux-capture linux-pipewire linux-pulseaudio obs-ffmpeg obs-nvenc)
|
||||||
|
|
||||||
|
# OBS 32.0 does not build against mbedtls 4. Arch ships 3.x as mbedtls3.
|
||||||
|
extra_cmake_args=()
|
||||||
|
if [[ -d /usr/lib/mbedtls3/cmake/MbedTLS ]]; then
|
||||||
|
extra_cmake_args+=(-DMbedTLS_DIR=/usr/lib/mbedtls3/cmake/MbedTLS)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d "${source_dir}" ]]; then
|
||||||
|
git clone --depth 1 --branch "${OBS_VERSION}" --recurse-submodules --shallow-submodules \
|
||||||
|
https://github.com/obsproject/obs-studio.git "${source_dir}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
actual_commit="$(git -C "${source_dir}" rev-parse HEAD)"
|
||||||
|
if [[ "${actual_commit}" != "${OBS_COMMIT}" ]]; then
|
||||||
|
echo "OBS source is at ${actual_commit}, expected ${OBS_COMMIT} (${OBS_VERSION})" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmake -S "${source_dir}" -B "${build_dir}" -G Ninja \
|
||||||
|
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="${stage_dir}" \
|
||||||
|
-DENABLE_RELOCATABLE=ON \
|
||||||
|
-DENABLE_PORTABLE_CONFIG=ON \
|
||||||
|
-DENABLE_FRONTEND=OFF \
|
||||||
|
-DENABLE_UI=OFF \
|
||||||
|
-DENABLE_BROWSER=OFF \
|
||||||
|
-DENABLE_WEBSOCKET=OFF \
|
||||||
|
-DENABLE_WEBRTC=OFF \
|
||||||
|
-DENABLE_SCRIPTING=OFF \
|
||||||
|
-DENABLE_VLC=OFF \
|
||||||
|
-DENABLE_AJA=OFF \
|
||||||
|
-DENABLE_DECKLINK=OFF \
|
||||||
|
-DENABLE_VST=OFF \
|
||||||
|
-DENABLE_QSV11=OFF \
|
||||||
|
-DENABLE_LIBFDK=OFF \
|
||||||
|
-DENABLE_JACK=OFF \
|
||||||
|
-DENABLE_SNDIO=OFF \
|
||||||
|
-DENABLE_OSS=OFF \
|
||||||
|
-DENABLE_V4L2=OFF \
|
||||||
|
-DENABLE_VIRTUALCAM=OFF \
|
||||||
|
-DENABLE_NEW_MPEGTS_OUTPUT=OFF \
|
||||||
|
-DENABLE_PIPEWIRE=ON \
|
||||||
|
-DENABLE_WAYLAND=ON \
|
||||||
|
-DENABLE_PULSEAUDIO=ON \
|
||||||
|
-DENABLE_NVENC=ON \
|
||||||
|
-DENABLE_HEVC=ON \
|
||||||
|
"${extra_cmake_args[@]}"
|
||||||
|
cmake --build "${build_dir}"
|
||||||
|
rm -rf "${stage_dir}"
|
||||||
|
cmake --install "${build_dir}"
|
||||||
|
|
||||||
|
# The bundle keeps OBS's relocatable install layout: binaries in bin/ (OBS
|
||||||
|
# looks for obs-ffmpeg-mux next to the running executable), libraries in lib/,
|
||||||
|
# and plugins in lib/obs-plugins/ so their $ORIGIN/.. runpath resolves libobs.
|
||||||
|
rm -rf "${bundle_dir}"
|
||||||
|
mkdir -p "${bundle_dir}/bin" "${bundle_dir}/lib/obs-plugins" "${bundle_dir}/lib/pkgconfig" \
|
||||||
|
"${bundle_dir}/share/obs/obs-plugins"
|
||||||
|
cp -a "${stage_dir}/bin/obs-ffmpeg-mux" "${bundle_dir}/bin/"
|
||||||
|
cp -a "${stage_dir}"/lib/libobs.so* "${stage_dir}"/lib/libobs-opengl.so* "${bundle_dir}/lib/"
|
||||||
|
cp -a "${stage_dir}/share/obs/libobs" "${bundle_dir}/share/obs/libobs"
|
||||||
|
for plugin in "${plugins[@]}"; do
|
||||||
|
cp -a "${stage_dir}/lib/obs-plugins/${plugin}.so" "${bundle_dir}/lib/obs-plugins/"
|
||||||
|
if [[ -d "${stage_dir}/share/obs/obs-plugins/${plugin}" ]]; then
|
||||||
|
cp -a "${stage_dir}/share/obs/obs-plugins/${plugin}" "${bundle_dir}/share/obs/obs-plugins/"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# Lets the libobs crate link against this runtime instead of a system OBS.
|
||||||
|
sed -e 's|^prefix=.*|prefix=${pcfiledir}/../..|' \
|
||||||
|
"${stage_dir}/lib/pkgconfig/libobs.pc" > "${bundle_dir}/lib/pkgconfig/libobs.pc"
|
||||||
|
|
||||||
|
cat > "${bundle_dir}/VERSION" <<EOF
|
||||||
|
obs=${OBS_VERSION}
|
||||||
|
commit=${OBS_COMMIT}
|
||||||
|
plugins=${plugins[*]}
|
||||||
|
EOF
|
||||||
|
echo "OBS ${OBS_VERSION} runtime assembled in ${bundle_dir}"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# The tested OBS version set. The libobs crate's headers must match OBS_VERSION.
|
||||||
|
OBS_VERSION=32.0.4
|
||||||
|
OBS_COMMIT=dcdbd2e9048ae66237fdab32dbba72b745b1b9c5
|
||||||
|
# Official release zip; the digest is the one GitHub publishes for the asset.
|
||||||
|
OBS_WINDOWS_X64_SHA256=fd367a7f156319426f1a600720c1288ad0011da168dff41e0f94dbb18cc1dc0f
|
||||||
Executable
+94
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run a command inside a disposable headless Wayland desktop for capture tests.
|
||||||
|
#
|
||||||
|
# scripts/rig/headless-wayland.sh <command> [args...]
|
||||||
|
#
|
||||||
|
# Starts a private D-Bus session with PipeWire, WirePlumber, headless Sway and
|
||||||
|
# xdg-desktop-portal-wlr (auto-selecting the only output), plus a 60 FPS test
|
||||||
|
# pattern standing in for a game. Everything is torn down when the command exits.
|
||||||
|
#
|
||||||
|
# This rig exercises the PipeWire/portal path on a machine without a display.
|
||||||
|
# It does not replace validation on real hardware (Hyprland, X11, Windows).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
echo "usage: $0 <command> [args...]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${CLIPBIN_RIG_INNER:-}" ]]; then
|
||||||
|
exec env CLIPBIN_RIG_INNER=1 dbus-run-session -- "$0" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rig_dir="$(mktemp -d "${TMPDIR:-/tmp}/clipbin-rig.XXXXXX")"
|
||||||
|
pids=()
|
||||||
|
cleanup() {
|
||||||
|
for pid in "${pids[@]}"; do kill "${pid}" 2>/dev/null || true; done
|
||||||
|
wait 2>/dev/null || true
|
||||||
|
# The D-Bus-activated document portal mounts a FUSE filesystem here.
|
||||||
|
fusermount3 -u "${XDG_RUNTIME_DIR}/doc" 2>/dev/null || true
|
||||||
|
rm -rf "${rig_dir}"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
export XDG_RUNTIME_DIR="${rig_dir}/runtime"
|
||||||
|
export XDG_CONFIG_HOME="${rig_dir}/config"
|
||||||
|
export XDG_CURRENT_DESKTOP=sway
|
||||||
|
export XDG_SESSION_TYPE=wayland
|
||||||
|
mkdir -p "${XDG_RUNTIME_DIR}" "${XDG_CONFIG_HOME}/xdg-desktop-portal-wlr" "${XDG_CONFIG_HOME}/sway"
|
||||||
|
chmod 700 "${XDG_RUNTIME_DIR}"
|
||||||
|
|
||||||
|
width="${CLIPBIN_RIG_WIDTH:-1920}"
|
||||||
|
height="${CLIPBIN_RIG_HEIGHT:-1080}"
|
||||||
|
|
||||||
|
cat > "${XDG_CONFIG_HOME}/sway/config" <<EOF
|
||||||
|
output HEADLESS-1 resolution ${width}x${height}@60Hz
|
||||||
|
default_border none
|
||||||
|
EOF
|
||||||
|
cat > "${XDG_CONFIG_HOME}/xdg-desktop-portal-wlr/config" <<EOF
|
||||||
|
[screencast]
|
||||||
|
chooser_type=none
|
||||||
|
output_name=HEADLESS-1
|
||||||
|
max_fps=60
|
||||||
|
EOF
|
||||||
|
|
||||||
|
wait_for() {
|
||||||
|
local what="$1" check="$2"
|
||||||
|
for _ in $(seq 100); do
|
||||||
|
if eval "${check}"; then return 0; fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
echo "rig: ${what} did not start; logs in ${rig_dir}" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pipewire > "${rig_dir}/pipewire.log" 2>&1 & pids+=($!)
|
||||||
|
wait_for pipewire '[[ -S "${XDG_RUNTIME_DIR}/pipewire-0" ]]'
|
||||||
|
wireplumber > "${rig_dir}/wireplumber.log" 2>&1 & pids+=($!)
|
||||||
|
pipewire-pulse > "${rig_dir}/pipewire-pulse.log" 2>&1 & pids+=($!)
|
||||||
|
|
||||||
|
WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 WLR_HEADLESS_OUTPUTS=1 \
|
||||||
|
sway --config "${XDG_CONFIG_HOME}/sway/config" > "${rig_dir}/sway.log" 2>&1 & pids+=($!)
|
||||||
|
wait_for sway 'compgen -G "${XDG_RUNTIME_DIR}/wayland-*" > /dev/null'
|
||||||
|
WAYLAND_DISPLAY="$(basename "$(compgen -G "${XDG_RUNTIME_DIR}/wayland-*" | grep -v lock | head -1)")"
|
||||||
|
export WAYLAND_DISPLAY
|
||||||
|
SWAYSOCK="$(compgen -G "${XDG_RUNTIME_DIR}/sway-ipc.*" | head -1)"
|
||||||
|
export SWAYSOCK
|
||||||
|
|
||||||
|
dbus-update-activation-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_RUNTIME_DIR XDG_CONFIG_HOME SWAYSOCK
|
||||||
|
/usr/lib/xdg-desktop-portal-wlr > "${rig_dir}/xdpw.log" 2>&1 & pids+=($!)
|
||||||
|
/usr/lib/xdg-desktop-portal -r > "${rig_dir}/xdp.log" 2>&1 & pids+=($!)
|
||||||
|
|
||||||
|
# A moving 60 FPS picture with a tone, standing in for a game.
|
||||||
|
SDL_VIDEODRIVER=wayland ffplay -loglevel error -fs -window_title clipbin-test-game \
|
||||||
|
-f lavfi "testsrc2=size=${width}x${height}:rate=60,format=yuv420p" \
|
||||||
|
> "${rig_dir}/game.log" 2>&1 & pids+=($!)
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
echo "rig: ready (WAYLAND_DISPLAY=${WAYLAND_DISPLAY}, logs in ${rig_dir})" >&2
|
||||||
|
status=0
|
||||||
|
"$@" || status=$?
|
||||||
|
if [[ -n "${CLIPBIN_RIG_KEEP_LOGS:-}" ]]; then
|
||||||
|
cp -r "${rig_dir}" "${CLIPBIN_RIG_KEEP_LOGS}"
|
||||||
|
fi
|
||||||
|
exit "${status}"
|
||||||
Reference in New Issue
Block a user