Two corrections and one promotion, all from evidence rather than inference.
1. The "macOS packaging gap (known, unfixed)" section was WRONG, and it
contradicted the release notes for the same build. It claimed the artifact
is "a bare streamer-tools-camera.so" with a relative libobs install name
that "will not load in OBS.app as it stands". Downloading and inspecting
the shipped streamer-tools-camera-v0.1.0-macos.zip shows otherwise:
- a proper streamer-tools-camera.plugin bundle -- Contents/MacOS/<name> is
Mach-O MH_BUNDLE (what OBS loads), with Info.plist (BNDL, correct
CFBundleExecutable), Contents/Resources/locale/en-US.ini, and both
LiveKit dylibs in Contents/Frameworks/
- install names are right: the module loads
@rpath/libobs.framework/Versions/A/libobs and carries
LC_RPATH @executable_path/../Frameworks, which inside OBS.app resolves to
OBS.app/Contents/Frameworks; @rpath/liblivekit.dylib resolves through
LC_RPATH @loader_path/../Frameworks to the bundle's own copy, and
liblivekit.dylib finds liblivekit_ffi.dylib through LC_RPATH @loader_path.
Nothing points into a build tree.
- all three binaries carry LC_CODE_SIGNATURE, which is not optional:
arm64 macOS refuses to load unsigned code.
The real macOS limitation is different and now stated: the bundle is
arm64-only (no x86_64 slice), macOS 13+. Nobody has still ever opened it in
OBS.app -- well formed and signed is a prior, not a load.
2. Linux and Windows are confirmed working in the OBS GUI: video and audio
both arrive and hold up across a session, Linux by the project owner and
Windows by two directors independently, and the plugin carried a live show
on 2026-09-07. Since listing cameras requires an API call, that also
retires "the WinHTTP backend has never run against a real streamer-tools
server".
Scoped, not inflated: MEASURED A/V sync and latency against the egress path
are still unverified (no drift reported is not a measurement), as is
mid-show publisher restart. The "Not verified anywhere" list is now
deduplicated and says exactly that.
3. The CI table's Windows row still said "Failing, fix pushed and awaiting a
completed run". It is green, after the WinHTTP deadline fix.
Release notes template updated to match, and v0.1.0's published notes have
been regenerated through it so the public page stops repeating the bare-.so
claim.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AzGnvQ6wfD7bw7PZN35ft9
138 lines
5.9 KiB
Bash
Executable File
138 lines
5.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Creates a published Gitea Release for the tag that triggered
|
|
# .gitea/workflows/release.yml, and uploads every archive in $DIST_DIR as a
|
|
# release asset.
|
|
#
|
|
# This used to create a DRAFT, on the grounds that nobody had run the plugin
|
|
# in the OBS GUI on any platform. That stopped being true on 2026-09-09, when
|
|
# the v0.1.0 Windows artifact loaded into OBS 32.2.2 on Windows 11 -- so the
|
|
# release publishes directly and the per-platform table below carries the
|
|
# remaining caveats instead. Assets upload AFTER the release row is created
|
|
# either way, so a release is briefly visible with no files attached; that is
|
|
# the tradeoff for not needing a human click.
|
|
#
|
|
# Required env: GITEA_TOKEN, SERVER, OWNER, REPO, TAG, SHA, DIST_DIR
|
|
# Optional env: MACOS_BUNDLE_FOUND ("true"/"false", default "false")
|
|
set -euo pipefail
|
|
|
|
: "${GITEA_TOKEN:?}"
|
|
: "${SERVER:?}"
|
|
: "${OWNER:?}"
|
|
: "${REPO:?}"
|
|
: "${TAG:?}"
|
|
: "${SHA:?}"
|
|
: "${DIST_DIR:?}"
|
|
MACOS_BUNDLE_FOUND="${MACOS_BUNDLE_FOUND:-false}"
|
|
|
|
if [ "${MACOS_BUNDLE_FOUND}" = "true" ]; then
|
|
MACOS_NOTE="This archive contains a \`.plugin\` bundle (verified on v0.1.0: MH_BUNDLE + Info.plist, libobs via \`@rpath\` + \`@executable_path/../Frameworks\`, LiveKit dylibs bundled, all three binaries code-signed). **arm64 only -- no Intel slice**, macOS 13+. Never yet loaded in OBS.app by a human."
|
|
else
|
|
MACOS_NOTE="This archive is packaged as a bare \`streamer-tools-camera.so\` (the layout \`build/package/\` currently produces on macOS), **not** an OBS.app-loadable \`.plugin\` bundle. It will not load in the OBS GUI as-is -- see the \"macOS packaging gap\" section of \`README.md\`."
|
|
fi
|
|
|
|
NOTES_FILE="$(mktemp)"
|
|
cat > "${NOTES_FILE}" <<EOF
|
|
# streamer-tools Camera Plugin -- ${TAG}
|
|
|
|
Built from commit \`${SHA}\`.
|
|
|
|
**Confirmed working on Linux and Windows, including a live show.** The plugin
|
|
carried a real broadcast on 2026-09-07. Video and audio both arrive and hold
|
|
up across a session: Linux verified by the project owner, Windows by two
|
|
directors independently.
|
|
|
|
Still unverified: **macOS in the OBS GUI** (nobody has opened it -- see the
|
|
table), **measured** A/V sync and end-to-end latency against the existing
|
|
egress path (no drift reported, but nothing measured), and whether a publisher
|
|
restarting mid-show recovers cleanly on screen. See "What is verified, and
|
|
how" in \`README.md\` for what is backed by automated tests versus a human
|
|
watching OBS.
|
|
|
|
| Platform | Archive | Notes |
|
|
|---|---|---|
|
|
| Linux (x64) | \`streamer-tools-camera-${TAG}-linux-x64.zip\` | Functionally complete, verified end to end against a real LiveKit server and a real libobs (see README), and confirmed working in the OBS GUI |
|
|
| Windows (x64) | \`streamer-tools-camera-${TAG}-windows-x64.zip\` | Built and tested by this workflow's Windows job, and confirmed working in the OBS GUI by two directors independently (first load: OBS 32.2.2 / Windows 11) -- which also exercises the WinHTTP backend against a real streamer-tools server |
|
|
| macOS | \`streamer-tools-camera-${TAG}-macos.zip\` | Built and tested by this workflow's macOS job. ${MACOS_NOTE} |
|
|
|
|
## Installing
|
|
|
|
Extract the archive into your OBS plugins folder. **The directory is not the
|
|
same shape on every platform, and picking the wrong one fails silently -- OBS
|
|
logs nothing at all for a plugin it never finds:**
|
|
|
|
| Platform | Extract into |
|
|
|---|---|
|
|
| Windows | \`C:\\ProgramData\\obs-studio\\plugins\\\` -- **not** \`%APPDATA%\\obs-studio\\\`, which is where OBS keeps its config and is never scanned for plugins |
|
|
| macOS | \`~/Library/Application Support/obs-studio/plugins/\` |
|
|
| Linux | \`~/.config/obs-studio/plugins/\` |
|
|
|
|
Each archive's top-level folder already matches the shape OBS expects, so
|
|
extracting is the whole install step -- but check the result is exactly one
|
|
folder deep. Windows Explorer's "Extract All..." adds a folder named after the
|
|
zip unless you clear it from the destination box, which nests it one level too
|
|
far and is equally silent. On Windows the finished path must be:
|
|
|
|
\`\`\`
|
|
C:\\ProgramData\\obs-studio\\plugins\\streamer-tools-camera\\bin\\64bit\\streamer-tools-camera.dll
|
|
\`\`\`
|
|
|
|
To confirm it loaded, restart OBS and check Help -> Log Files -> View Current
|
|
Log for \`streamer-tools-camera\` under "Loaded Modules". Then in OBS: Sources -> \`+\` ->
|
|
"streamer-tools Camera" -> fill in the server URL, room slug and read key
|
|
from the room's settings page -> "Refresh camera list" -> pick a camera.
|
|
|
|
## What this is
|
|
|
|
Native OBS Studio source plugin that pulls streamer-tools camera feeds
|
|
directly from LiveKit over WebRTC. See \`README.md\` in the repository for
|
|
the full design, what is and is not verified, and current CI status.
|
|
EOF
|
|
|
|
echo "----- release notes -----"
|
|
cat "${NOTES_FILE}"
|
|
echo "--------------------------"
|
|
|
|
BODY_JSON="$(python3 - "$TAG" "$NOTES_FILE" <<'PYEOF'
|
|
import json, sys
|
|
tag, notes_file = sys.argv[1], sys.argv[2]
|
|
with open(notes_file) as f:
|
|
notes = f.read()
|
|
print(json.dumps({
|
|
"tag_name": tag,
|
|
"name": tag,
|
|
"body": notes,
|
|
"draft": False,
|
|
"prerelease": False,
|
|
}))
|
|
PYEOF
|
|
)"
|
|
|
|
echo "Creating release for tag ${TAG} ..."
|
|
RESP="$(curl -sS -f -X POST \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "${BODY_JSON}" \
|
|
"${SERVER}/api/v1/repos/${OWNER}/${REPO}/releases")"
|
|
|
|
RELEASE_ID="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"${RESP}")"
|
|
echo "Created release id ${RELEASE_ID} (published; assets upload next)."
|
|
|
|
shopt -s nullglob
|
|
ASSETS=("${DIST_DIR}"/*)
|
|
if [ "${#ASSETS[@]}" -eq 0 ]; then
|
|
echo "::error::no archives found in ${DIST_DIR} -- nothing to upload."
|
|
exit 1
|
|
fi
|
|
|
|
for f in "${ASSETS[@]}"; do
|
|
name="$(basename "${f}")"
|
|
echo "Uploading ${name} ..."
|
|
curl -sS -f -X POST \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
-F "attachment=@${f}" \
|
|
"${SERVER}/api/v1/repos/${OWNER}/${REPO}/releases/${RELEASE_ID}/assets?name=${name}" \
|
|
> /dev/null
|
|
done
|
|
|
|
echo "Done. Release: ${SERVER}/${OWNER}/${REPO}/releases/${RELEASE_ID}"
|