Files
shadowdaoandClaude Opus 5 ab455a9cfe docs: the macOS bundle was fine all along, and record the GUI verification
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
2026-09-10 05:31:09 -07:00

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}"