From f86bfce23e34ea20f7a64dac2512be7c5627846a Mon Sep 17 00:00:00 2001 From: Jon McMillan Date: Thu, 9 Apr 2026 10:25:25 +0200 Subject: [PATCH 1/3] Add gstreamer-publisher image --- .github/workflows/ci.yml | 3 +- README.md | 1 + gstreamer-publisher/.dockerignore | 2 + gstreamer-publisher/Containerfile | 55 ++++++++++++++ gstreamer-publisher/README.md | 17 +++++ gstreamer-publisher/docker-bake.hcl | 74 +++++++++++++++++++ .../test/controls/gstreamer_publisher.rb | 4 + 7 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 gstreamer-publisher/.dockerignore create mode 100644 gstreamer-publisher/Containerfile create mode 100644 gstreamer-publisher/README.md create mode 100644 gstreamer-publisher/docker-bake.hcl create mode 100644 gstreamer-publisher/test/controls/gstreamer_publisher.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08248577..9bb8cc42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,7 @@ jobs: github-cli: 'github-cli/**' glab: 'glab/**' go2chef: 'go2chef/**' + gstreamer-publisher: 'gstreamer-publisher/**' golang/1.23/noble: 'golang/1.23/noble/**' golang/1.24/noble: 'golang/1.24/noble/**' grafana/grafana-oss: 'grafana/grafana-oss/**' @@ -171,4 +172,4 @@ jobs: run-test-stage: ${{ env.run-test-stage }} test-entrypoint: ${{ env.test-entrypoint }} dockerhub-username: ${{ secrets.CONTAINER_REGISTRY_USERNAME }} - dockerhub-password: ${{ secrets.CONTAINER_DESCRIPTION_PASSWORD }} \ No newline at end of file + dockerhub-password: ${{ secrets.CONTAINER_DESCRIPTION_PASSWORD }} diff --git a/README.md b/README.md index f7ea24f6..676db78a 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ The images are pubished to https://hub.docker.com/u/polymathrobotics | [gh](https://hub.docker.com/r/polymathrobotics/gh) | The GitHub CLI tool | [github-cli](https://github.com/polymathrobotics/oci/tree/main/github-cli) | | [glab](https://hub.docker.com/r/polymathrobotics/glab) | The GitLab CLI tool | [glab](https://github.com/polymathrobotics/oci/tree/main/glab) | | [go2chef](https://hub.docker.com/r/polymathrobotics/go2chef) | A Golang tool to bootstrap a system from zero so that it's able to run Chef | [go2chef](https://github.com/polymathrobotics/oci/tree/main/go2chef) | +| [gstreamer-publisher](https://hub.docker.com/r/polymathrobotics/gstreamer-publisher) | Command-line app that publishes any GStreamer pipeline to LiveKit | [gstreamer-publisher](https://github.com/polymathrobotics/oci/tree/main/gstreamer-publisher) | | [golang](https://hub.docker.com/r/polymathrobotics/golang) | Go (golang) is a general purpose, higher-level, imperative programming language. | [golang](https://github.com/polymathrobotics/oci/tree/main/golang) | | [grafana-oss](https://hub.docker.com/r/polymathrobotics/grafana-oss) | Grafana - the open observability platform | [grafana/grafana-oss](https://github.com/polymathrobotics/oci/tree/main/grafana/grafana-oss) | | [hadolint](https://hub.docker.com/r/polymathrobotics/hadolint) | Containerfile/Dockerfile linter | [hadolint](https://github.com/polymathrobotics/oci/tree/main/hadolint) | diff --git a/gstreamer-publisher/.dockerignore b/gstreamer-publisher/.dockerignore new file mode 100644 index 00000000..87ae0952 --- /dev/null +++ b/gstreamer-publisher/.dockerignore @@ -0,0 +1,2 @@ +README.md +test/ diff --git a/gstreamer-publisher/Containerfile b/gstreamer-publisher/Containerfile new file mode 100644 index 00000000..436c57b0 --- /dev/null +++ b/gstreamer-publisher/Containerfile @@ -0,0 +1,55 @@ +# syntax=docker/dockerfile:1 +# Arguments used in FROM need to be defined before the first build stage +ARG BUILD_IMAGE=docker.io/polymathrobotics/ros:humble-builder-ubuntu +ARG GOLANG_IMAGE=docker.io/polymathrobotics/golang:1.24-noble +ARG RUNTIME_BASE_IMAGE=docker.io/ubuntu:jammy-20260217 + +FROM ${GOLANG_IMAGE} AS golang-toolchain + +FROM ${BUILD_IMAGE} AS build + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +ARG GSTREAMER_PUBLISHER_REF + +ENV DEBIAN_FRONTEND=noninteractive +ENV GOTOOLCHAIN=local +ENV GOPATH=/go +ENV PATH=/go/bin:/usr/local/go/bin:${PATH} + +WORKDIR /tmp + +COPY --from=golang-toolchain /usr/local/go /usr/local/go + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + git \ + libgstreamer-plugins-base1.0-dev \ + libgstreamer1.0-dev \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* + +RUN git init /tmp/gstreamer-publisher \ + && git -C /tmp/gstreamer-publisher remote add origin https://github.com/livekit/gstreamer-publisher \ + && git -C /tmp/gstreamer-publisher fetch --depth 1 origin "${GSTREAMER_PUBLISHER_REF}" \ + && git -C /tmp/gstreamer-publisher checkout FETCH_HEAD \ + && GOFLAGS='-p=1' go build -C /tmp/gstreamer-publisher -o /usr/local/bin/gstreamer-publisher . + +FROM ${RUNTIME_BASE_IMAGE} + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + gstreamer1.0-plugins-base \ + gstreamer1.0-plugins-good \ + gstreamer1.0-tools \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=build --chmod=755 /usr/local/bin/gstreamer-publisher /usr/local/bin/gstreamer-publisher + +ENTRYPOINT ["/usr/local/bin/gstreamer-publisher"] +CMD ["--help"] diff --git a/gstreamer-publisher/README.md b/gstreamer-publisher/README.md new file mode 100644 index 00000000..840f6759 --- /dev/null +++ b/gstreamer-publisher/README.md @@ -0,0 +1,17 @@ +# gstreamer-publisher + +`gstreamer-publisher` publishes video and audio from a GStreamer pipeline to +LiveKit. + +This image builds pinned revisions from +https://github.com/livekit/gstreamer-publisher + +Image source: https://github.com/polymathrobotics/oci/tree/main/gstreamer-publisher + +Use the `jammy` tag for ROS Humble-compatible builds and `noble` for newer ROS +distributions. + +```bash +docker container run --rm \ + docker.io/polymathrobotics/gstreamer-publisher:jammy --help +``` diff --git a/gstreamer-publisher/docker-bake.hcl b/gstreamer-publisher/docker-bake.hcl new file mode 100644 index 00000000..9ae1d800 --- /dev/null +++ b/gstreamer-publisher/docker-bake.hcl @@ -0,0 +1,74 @@ +variable "TAG_PREFIX" { + default = "docker.io/polymathrobotics/gstreamer-publisher" +} + +variable "LOCAL_PLATFORM" { + default = regex_replace("${BAKE_LOCAL_PLATFORM}", "^(darwin)", "linux") +} + +variable "DISTRO" { + default = [ + { + name = "jammy" + build_image = "docker.io/polymathrobotics/ros:humble-builder-ubuntu" + runtime_base_image = "docker.io/ubuntu:jammy-20260217" + gstreamer_publisher_ref = "8825fee6f40ff51f2cf9347892f6fbc08eeb1f2e" + }, + { + name = "noble" + build_image = "docker.io/polymathrobotics/ros:rolling-builder-ubuntu" + runtime_base_image = "docker.io/ubuntu:noble-20260217" + gstreamer_publisher_ref = "407891dbdca2ad3113270fbeb350ab9f47615917" + }, + ] +} + +target "_common" { + dockerfile = "Containerfile" + labels = { + "org.opencontainers.image.source" = "https://github.com/polymathrobotics/oci/tree/main/gstreamer-publisher" + "org.opencontainers.image.licenses" = "Apache-2.0" + "org.opencontainers.image.description" = "Command-line app that publishes any GStreamer pipeline to LiveKit." + "org.opencontainers.image.title" = "${TAG_PREFIX}" + "org.opencontainers.image.created" = "${timestamp()}" + "dev.polymathrobotics.image.readme-filepath" = "gstreamer-publisher/README.md" + } +} + +target "local" { + inherits = ["_common"] + matrix = { + distro = DISTRO + } + name = "local-${distro.name}" + tags = [ + "${TAG_PREFIX}:${distro.name}", + "${TAG_PREFIX}:${distro.name}-${distro.gstreamer_publisher_ref}", + ] + args = { + BUILD_IMAGE = distro.build_image + GOLANG_IMAGE = "docker.io/polymathrobotics/golang:1.24-noble" + GSTREAMER_PUBLISHER_REF = distro.gstreamer_publisher_ref + RUNTIME_BASE_IMAGE = distro.runtime_base_image + } + platforms = ["${LOCAL_PLATFORM}"] +} + +target "default" { + inherits = ["_common"] + matrix = { + distro = DISTRO + } + name = "default-${distro.name}" + tags = [ + "${TAG_PREFIX}:${distro.name}", + "${TAG_PREFIX}:${distro.name}-${distro.gstreamer_publisher_ref}", + ] + args = { + BUILD_IMAGE = distro.build_image + GOLANG_IMAGE = "docker.io/polymathrobotics/golang:1.24-noble" + GSTREAMER_PUBLISHER_REF = distro.gstreamer_publisher_ref + RUNTIME_BASE_IMAGE = distro.runtime_base_image + } + platforms = ["linux/amd64"] +} diff --git a/gstreamer-publisher/test/controls/gstreamer_publisher.rb b/gstreamer-publisher/test/controls/gstreamer_publisher.rb new file mode 100644 index 00000000..1ca75b5f --- /dev/null +++ b/gstreamer-publisher/test/controls/gstreamer_publisher.rb @@ -0,0 +1,4 @@ +describe command('gstreamer-publisher --help') do + its('exit_status') { should eq 0 } + its('stdout') { should match(/Publish video\/audio from a GStreamer pipeline to LiveKit/) } +end From 1b2dfc8ce3cc3317121d0f87bb0d2d8bd6f3ad72 Mon Sep 17 00:00:00 2001 From: Jon McMillan Date: Thu, 9 Apr 2026 10:50:21 +0200 Subject: [PATCH 2/3] Restore x264 and multi-arch publish for gstreamer-publisher Add gstreamer1.0-plugins-ugly to the runtime image so upstream H.264 pipelines can resolve x264enc again. Restore linux/arm64/v8 to the default bake target so published jammy and noble tags are multi-arch like the rest of the repo. Add an InSpec regression test that checks gst-inspect-1.0 x264enc succeeds. Verified with: - docker buildx bake local --load - bin/test.sh docker.io/polymathrobotics/gstreamer-publisher:jammy /bin/bash - bin/test.sh docker.io/polymathrobotics/gstreamer-publisher:noble /bin/bash - docker buildx bake default --print --- gstreamer-publisher/Containerfile | 1 + gstreamer-publisher/docker-bake.hcl | 2 +- gstreamer-publisher/test/controls/gstreamer_publisher.rb | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/gstreamer-publisher/Containerfile b/gstreamer-publisher/Containerfile index 436c57b0..8545abf8 100644 --- a/gstreamer-publisher/Containerfile +++ b/gstreamer-publisher/Containerfile @@ -46,6 +46,7 @@ RUN apt-get update \ ca-certificates \ gstreamer1.0-plugins-base \ gstreamer1.0-plugins-good \ + gstreamer1.0-plugins-ugly \ gstreamer1.0-tools \ && rm -rf /var/lib/apt/lists/* diff --git a/gstreamer-publisher/docker-bake.hcl b/gstreamer-publisher/docker-bake.hcl index 9ae1d800..b9042f8c 100644 --- a/gstreamer-publisher/docker-bake.hcl +++ b/gstreamer-publisher/docker-bake.hcl @@ -70,5 +70,5 @@ target "default" { GSTREAMER_PUBLISHER_REF = distro.gstreamer_publisher_ref RUNTIME_BASE_IMAGE = distro.runtime_base_image } - platforms = ["linux/amd64"] + platforms = ["linux/amd64", "linux/arm64/v8"] } diff --git a/gstreamer-publisher/test/controls/gstreamer_publisher.rb b/gstreamer-publisher/test/controls/gstreamer_publisher.rb index 1ca75b5f..6698c66e 100644 --- a/gstreamer-publisher/test/controls/gstreamer_publisher.rb +++ b/gstreamer-publisher/test/controls/gstreamer_publisher.rb @@ -2,3 +2,7 @@ its('exit_status') { should eq 0 } its('stdout') { should match(/Publish video\/audio from a GStreamer pipeline to LiveKit/) } end + +describe command('gst-inspect-1.0 x264enc') do + its('exit_status') { should eq 0 } +end From adceaa3bf3dd18ebb01c1ed7f2a6af138b2cfe2a Mon Sep 17 00:00:00 2001 From: Jon McMillan Date: Thu, 9 Apr 2026 11:16:39 +0200 Subject: [PATCH 3/3] Restore advertised pipelines for gstreamer-publisher Add hadolint DL3006 ignores to the ARG-driven build and runtime FROM stages so the repository lint gate passes for this image. Install gstreamer1.0-plugins-bad and gstreamer1.0-x in the runtime image so the upstream example pipelines can resolve h264parse and clockoverlay on both jammy and noble tags. Expand the InSpec regression test to check the documented GStreamer elements used by the shipped examples instead of only x264enc. Verified with: - ../bin/lint.sh - docker buildx bake local - ../bin/test.sh docker.io/polymathrobotics/gstreamer-publisher:jammy - ../bin/test.sh docker.io/polymathrobotics/gstreamer-publisher:noble --- gstreamer-publisher/Containerfile | 5 +++++ .../test/controls/gstreamer_publisher.rb | 13 +++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/gstreamer-publisher/Containerfile b/gstreamer-publisher/Containerfile index 8545abf8..c31d9f37 100644 --- a/gstreamer-publisher/Containerfile +++ b/gstreamer-publisher/Containerfile @@ -4,8 +4,10 @@ ARG BUILD_IMAGE=docker.io/polymathrobotics/ros:humble-builder-ubuntu ARG GOLANG_IMAGE=docker.io/polymathrobotics/golang:1.24-noble ARG RUNTIME_BASE_IMAGE=docker.io/ubuntu:jammy-20260217 +# hadolint ignore=DL3006 FROM ${GOLANG_IMAGE} AS golang-toolchain +# hadolint ignore=DL3006 FROM ${BUILD_IMAGE} AS build SHELL ["/bin/bash", "-o", "pipefail", "-c"] @@ -35,6 +37,7 @@ RUN git init /tmp/gstreamer-publisher \ && git -C /tmp/gstreamer-publisher checkout FETCH_HEAD \ && GOFLAGS='-p=1' go build -C /tmp/gstreamer-publisher -o /usr/local/bin/gstreamer-publisher . +# hadolint ignore=DL3006 FROM ${RUNTIME_BASE_IMAGE} SHELL ["/bin/bash", "-o", "pipefail", "-c"] @@ -44,10 +47,12 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ + gstreamer1.0-plugins-bad \ gstreamer1.0-plugins-base \ gstreamer1.0-plugins-good \ gstreamer1.0-plugins-ugly \ gstreamer1.0-tools \ + gstreamer1.0-x \ && rm -rf /var/lib/apt/lists/* COPY --from=build --chmod=755 /usr/local/bin/gstreamer-publisher /usr/local/bin/gstreamer-publisher diff --git a/gstreamer-publisher/test/controls/gstreamer_publisher.rb b/gstreamer-publisher/test/controls/gstreamer_publisher.rb index 6698c66e..68fd0aa8 100644 --- a/gstreamer-publisher/test/controls/gstreamer_publisher.rb +++ b/gstreamer-publisher/test/controls/gstreamer_publisher.rb @@ -3,6 +3,15 @@ its('stdout') { should match(/Publish video\/audio from a GStreamer pipeline to LiveKit/) } end -describe command('gst-inspect-1.0 x264enc') do - its('exit_status') { should eq 0 } +%w[ + clockoverlay + decodebin3 + h264parse + opusenc + vp9enc + x264enc +].each do |plugin| + describe command("gst-inspect-1.0 #{plugin}") do + its('exit_status') { should eq 0 } + end end