Files
clipbin-desktop/scripts/validate/linux-rig.sh
T
harry 435ddf2b17 feat: automatically record Steam game sessions
Detect games with native process exit watchers, own OBS on a background worker, and persist settings and recoverable session timelines. Place segments by split frame counts, recover capture with honest gaps, and finalise on game exit or explicit Quit.

Keep the current recording alive after GPUI closes its last window, then exit when the game ends. Add an end-to-end rig covering recording, recovery, exact packet-count offsets, disk library reload and clean Quit.
2026-10-08 17:30:40 +02:00

125 lines
5.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Runs the recording validation scenarios in the headless rig and reports
# PASS/FAIL for each. Needs the Linux OBS runtime (scripts/obs/build-linux.sh),
# a VA-API or NVENC capable GPU, and the rig packages listed in
# docs/validation.md.
#
# scripts/validate/linux-rig.sh [output-dir]
set -uo pipefail
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
out="${1:-${TMPDIR:-/tmp}/clipbin-validation}"
rig="${repo}/scripts/rig/headless.sh"
probe="${repo}/target/debug/clipbin-probe"
rm -rf "${out}" && mkdir -p "${out}"
cargo build --locked -p clipbin-probe --manifest-path "${repo}/Cargo.toml" || exit 1
failures=0
result() {
local name="$1" ok="$2" detail="${3:-}"
if [[ "${ok}" == 0 ]]; then
echo "PASS ${name} ${detail}"
else
echo "FAIL ${name} ${detail} (log: ${out}/${name}.log)"
failures=$((failures + 1))
fi
}
# has <log> <pattern>: the probe printed a line matching pattern.
has() { grep -qE "$2" "$1"; }
# 1. Hardware encoders: H.264 must be usable; HEVC and AV1 are reported.
log="${out}/encoders.log"
"${rig}" "${probe}" encoders --verify --out "${out}/encoders" > "${log}" 2> "${out}/encoders.obs.log"
result encoders "$?" "$(grep -E '^usable\.' "${log}" | tr '\n' ' ')"
# 2. Wayland monitor capture through the portal: segments, remux, audio.
for codec in h264 hevc; do
if has "${log}" "^usable\.${codec}=none"; then
echo "SKIP wayland-${codec} (no ${codec} hardware encoder)"
continue
fi
name="wayland-${codec}"
dir="${out}/${name}"
"${rig}" "${probe}" record --codec "${codec}" --seconds 9 --out "${dir}" > "${out}/${name}.log" 2>&1 \
&& "${probe}" check --codec "${codec}" --expect-motion --out "${dir}" >> "${out}/${name}.log" 2>&1
ok=$?
volume="$(ffmpeg -v info -i "${dir}/session/segment-000001.mkv" -map 0:a -af volumedetect -f null - 2>&1 \
| awk '/max_volume/ { print $5 }')"
if [[ "${ok}" == 0 ]] && awk -v v="${volume:--999}" 'BEGIN { exit !(v > -40) }'; then ok=0; else ok=1; fi
result "${name}" "${ok}" "audio_max_volume=${volume:-none}dB"
done
# 3. X11 window capture through Xcomposite (Xwayland in the rig).
name=x11-window
"${rig}" --x11 bash -c "'${probe}' record --target window --window \"\${CLIPBIN_RIG_GAME_WINDOW}\" --seconds 9 --out '${out}/${name}'" \
> "${out}/${name}.log" 2>&1 \
&& "${probe}" check --expect-motion --out "${out}/${name}" >> "${out}/${name}.log" 2>&1
result "${name}" "$?"
# expect_failure <name> <reason pattern>: recording started, then failed honestly.
expect_failure() {
local name="$1" reason="$2" log="${out}/$1.log"
if [[ "${probe_exit}" != 0 ]] && has "${log}" "status=Recording" && has "${log}" "status=failed reason=${reason}"; then
result "${name}" 0 "$(grep -m1 -o 'status=failed reason=.*' "${log}")"
else
result "${name}" 1
fi
}
# 4. The PipeWire stream ends mid-recording (portal backend exits).
name=wayland-source-lost
"${rig}" bash -c "'${probe}' record --seconds 15 --out '${out}/${name}' & p=\$!; sleep 5; kill \$(cat \"\${CLIPBIN_RIG_DIR}/xdpw.pid\"); wait \$p" \
> "${out}/${name}.log" 2>&1
probe_exit=$?
expect_failure "${name}" "the capture source stopped"
# 5. The captured X11 window closes mid-recording.
name=x11-window-closed
"${rig}" --x11 bash -c "'${probe}' record --target window --window \"\${CLIPBIN_RIG_GAME_WINDOW}\" --seconds 15 --out '${out}/${name}' & p=\$!; sleep 5; kill \$(cat \"\${CLIPBIN_RIG_DIR}/game.pid\"); wait \$p" \
> "${out}/${name}.log" 2>&1
probe_exit=$?
expect_failure "${name}" "the capture source stopped"
# 6. Segments can no longer be written (directory made read-only).
name=write-failure
"${rig}" bash -c "'${probe}' record --seconds 20 --out '${out}/${name}' & p=\$!; sleep 4; chmod 500 '${out}/${name}/session'; wait \$p" \
> "${out}/${name}.log" 2>&1
probe_exit=$?
chmod 700 "${out}/${name}/session" 2>/dev/null
expect_failure "${name}" "(no footage was written|Error|EncodeError)"
# 7. Free space falls below the safety margin.
name=low-disk-space
"${rig}" bash -c "'${probe}' record --seconds 10 --out '${out}/${name}' --min-free-mb \$(( \$(df --output=avail -B 1000000 '${out}' | tail -1) + 1 ))" \
> "${out}/${name}.log" 2>&1
probe_exit=$?
if [[ "${probe_exit}" != 0 ]] && has "${out}/${name}.log" "status=failed reason=not enough disk space" \
&& ! has "${out}/${name}.log" "status=Recording"; then
result "${name}" 0 "$(grep -m1 -o 'status=failed reason=.*' "${out}/${name}.log")"
else
result "${name}" 1
fi
# 8. The portal refuses the request: an actionable failure, never Recording,
# and no footage written (output starts only once capture has a picture).
name=portal-refused
CLIPBIN_RIG_PORTAL_OUTPUT=NO-SUCH-OUTPUT "${rig}" "${probe}" record --seconds 8 --out "${out}/${name}" \
> "${out}/${name}.log" 2>&1
probe_exit=$?
if [[ "${probe_exit}" != 0 ]] && ! has "${out}/${name}.log" "status=Recording" \
&& has "${out}/${name}.log" "status=failed reason=screen capture was not allowed" \
&& [[ -z "$(ls -A "${out}/${name}/session" 2>/dev/null)" ]]; then
result "${name}" 0 "$(grep -m1 -o 'status=failed reason=.*' "${out}/${name}.log")"
else
result "${name}" 1
fi
echo
if [[ "${failures}" == 0 ]]; then
echo "All scenarios passed. Logs in ${out}"
else
echo "${failures} scenario(s) failed. Logs in ${out}"
fi
exit $(( failures > 0 ))