From b0489fee3a869aafa10b8a709fb894dd65c0121e Mon Sep 17 00:00:00 2001 From: streamer45 Date: Sat, 27 Jun 2026 12:49:20 +0200 Subject: [PATCH 01/26] feat(examples): add web-capture gateway (clip/cast.streamkit.dev) A thin Go gateway, mirroring examples/speech-gateway, that renders any web page to video via the Servo plugin: clip.* returns a finite MP4 (oneshot), cast.* serves a live WebM stream (dynamic session). The target URL is the verbatim path suffix, with an optional comma-separated options segment (dur=, vp=) and strong 720p30 defaults. Worth a reviewer's attention: - cast owns session lifetime (the engine does not auto-stop a pipeline when its MSE viewers disconnect): one shared session per URL+viewport, viewer refcount, idle + max-lifetime reaper, and graceful-shutdown teardown. The MSE proxy closes the upstream body on client disconnect so a read parked between frames can't keep the viewer count pinned. - SSRF guard (public/URL-only): blocks loopback/private/link-local/CGNAT/ cloud-metadata targets. - Encoders are swappable profiles, software by default (runs GPU-free), hardware opt-in via --clip-encoder/--cast-encoder. - Prometheus metrics incl. active sessions/viewers and reap counters. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: streamer45 --- examples/web-capture/.gitignore | 1 + examples/web-capture/.golangci.yml | 26 ++ examples/web-capture/Dockerfile | 13 + examples/web-capture/Makefile | 20 ++ examples/web-capture/README.md | 124 +++++++ examples/web-capture/cmd/gateway/capture.go | 247 +++++++++++++ .../web-capture/cmd/gateway/capture_test.go | 133 +++++++ examples/web-capture/cmd/gateway/encoders.go | 95 +++++ .../web-capture/cmd/gateway/encoders_test.go | 64 ++++ examples/web-capture/cmd/gateway/main.go | 327 +++++++++++++++++ examples/web-capture/cmd/gateway/metrics.go | 172 +++++++++ .../web-capture/cmd/gateway/metrics_test.go | 44 +++ examples/web-capture/cmd/gateway/pipelines.go | 140 ++++++++ .../web-capture/cmd/gateway/pipelines_test.go | 71 ++++ examples/web-capture/cmd/gateway/proxy.go | 264 ++++++++++++++ examples/web-capture/cmd/gateway/sessions.go | 338 ++++++++++++++++++ .../web-capture/cmd/gateway/sessions_test.go | 183 ++++++++++ examples/web-capture/go.mod | 18 + examples/web-capture/go.sum | 46 +++ 19 files changed, 2326 insertions(+) create mode 100644 examples/web-capture/.gitignore create mode 100644 examples/web-capture/.golangci.yml create mode 100644 examples/web-capture/Dockerfile create mode 100644 examples/web-capture/Makefile create mode 100644 examples/web-capture/README.md create mode 100644 examples/web-capture/cmd/gateway/capture.go create mode 100644 examples/web-capture/cmd/gateway/capture_test.go create mode 100644 examples/web-capture/cmd/gateway/encoders.go create mode 100644 examples/web-capture/cmd/gateway/encoders_test.go create mode 100644 examples/web-capture/cmd/gateway/main.go create mode 100644 examples/web-capture/cmd/gateway/metrics.go create mode 100644 examples/web-capture/cmd/gateway/metrics_test.go create mode 100644 examples/web-capture/cmd/gateway/pipelines.go create mode 100644 examples/web-capture/cmd/gateway/pipelines_test.go create mode 100644 examples/web-capture/cmd/gateway/proxy.go create mode 100644 examples/web-capture/cmd/gateway/sessions.go create mode 100644 examples/web-capture/cmd/gateway/sessions_test.go create mode 100644 examples/web-capture/go.mod create mode 100644 examples/web-capture/go.sum diff --git a/examples/web-capture/.gitignore b/examples/web-capture/.gitignore new file mode 100644 index 000000000..9064eb2e2 --- /dev/null +++ b/examples/web-capture/.gitignore @@ -0,0 +1 @@ +/gateway diff --git a/examples/web-capture/.golangci.yml b/examples/web-capture/.golangci.yml new file mode 100644 index 000000000..e4ee18ab0 --- /dev/null +++ b/examples/web-capture/.golangci.yml @@ -0,0 +1,26 @@ +version: "2" +linters: + default: none + enable: + - bidichk + - errcheck + - govet + - ineffassign + - makezero + - misspell + - revive + - staticcheck + - unconvert + - unqueryvet + - unused + - whitespace + settings: + govet: + disable: + - fieldalignment + enable-all: true + unqueryvet: + check-sql-builders: true +issues: + max-issues-per-linter: 0 # no maximum + max-same-issues: 0 # no maximum diff --git a/examples/web-capture/Dockerfile b/examples/web-capture/Dockerfile new file mode 100644 index 000000000..8c86de183 --- /dev/null +++ b/examples/web-capture/Dockerfile @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: © 2025 StreamKit Contributors +# +# SPDX-License-Identifier: MPL-2.0 + +FROM golang:1.24-bookworm AS build +WORKDIR /src +COPY . . +RUN CGO_ENABLED=0 go build -o /gateway ./cmd/gateway + +FROM gcr.io/distroless/static-debian12 +COPY --from=build /gateway /gateway +EXPOSE 8080 +ENTRYPOINT ["/gateway"] diff --git a/examples/web-capture/Makefile b/examples/web-capture/Makefile new file mode 100644 index 000000000..e0e112efb --- /dev/null +++ b/examples/web-capture/Makefile @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: © 2025 StreamKit Contributors +# +# SPDX-License-Identifier: MPL-2.0 + +.PHONY: build lint test run clean + +build: + go build -o gateway ./cmd/gateway + +lint: + golangci-lint run + +test: + go test ./... + +run: + go run ./cmd/gateway + +clean: + rm -f gateway diff --git a/examples/web-capture/README.md b/examples/web-capture/README.md new file mode 100644 index 000000000..414bddc9e --- /dev/null +++ b/examples/web-capture/README.md @@ -0,0 +1,124 @@ + + +# Web Capture Gateway + +Render any web page to video through a StreamKit backend. Paste a target URL straight onto the gateway host — no flags, no JSON: + +- **clip** — a finite MP4 file (oneshot pipeline): `clip.streamkit.dev/example.com` +- **cast** — a live WebM stream (dynamic session): `cast.streamkit.dev/example.com` + +The page is rendered by the [Servo](../../plugins/native/servo) browser engine on the backend. The gateway turns a pasteable URL into the multipart oneshot / dynamic-session calls the backend expects and, for `cast`, owns the session lifetime so abandoned streams are torn down instead of leaking a renderer. + +## URL shape + +``` +{host}/[options/]{target-url} +``` + +The target URL is taken **verbatim** as the path suffix (scheme optional, `https` assumed), so its own query string just rides along — no percent-encoding: + +``` +clip.streamkit.dev/grafana.example/d/abc?panel=3&from=now-6h +``` + +Options are an optional comma-separated `key=value` **first segment**: + +``` +clip.streamkit.dev/dur=30s/example.com # clip length (default 10s, capped at 60s) +cast.streamkit.dev/res=2560x1440/example.com # capture resolution (default 1920x1080) +clip.streamkit.dev/res=1280x720,dur=15s/example.com # combine, comma-separated +``` + +`dur` is `clip`-only; `res` (the capture resolution) applies to both modes. + +Locally there are no subdomains, so select the mode with a path prefix instead: + +``` +http://127.0.0.1:8080/clip/dur=5s/example.com +http://127.0.0.1:8080/cast/example.com +``` + +The same URL serves a browser and a player/script: an address-bar visit (`Accept: text/html`) to a `cast` URL returns a tiny autoplay page; anything else (a `