Skip to content

dwot/growcast

Repository files navigation

Growcast

CI Docker Hub (CPU) Docker Hub (GPU)

OBS-free, 24/7 multi-camera livestream orchestrator driven by FFmpeg and ZMQ.


About

Growcast is a personal experiment in running a continuous livestream without OBS. It was built to monitor a grow tent and stream around the clock to platforms like Twitch and Owncast — with automated scene rotation, live sensor overlays, and radio audio — all from a Docker container on a Linux host.

You can see it running at grow.dwot.io.

Deployment note: Growcast is designed for LAN use only. Do not expose the container or API port directly to the internet. There is no TLS, no rate limiting, and API authentication is optional. Keep it behind a firewall or VPN.


Features

  • Multi-camera compositing — single, picture-in-picture, and 2×2 grid layouts
  • Seamless scene switching — ZMQ-driven streamselect filter; the output stream never reconnects or drops frames on a scene change
  • Auto-rotation — cycles through a configurable scene order on a timer
  • Rich overlay — live sensor stats, rotating plant ticker, and now-playing track info
  • Optional audio source — attach any HTTP audio stream (e.g. an Azuracast radio station)
  • Multi-destination fan-out — encode once, stream to multiple RTMP/RTMPS endpoints via the tee muxer
  • NVIDIA NVENC/CUVID acceleration — optional GPU encode and decode paths
  • Camera health monitoring — background health checks with automatic status tracking
  • REST API — scene control and status endpoints, with optional API key authentication

Integrations

Isley

Growcast's overlay system is designed to work with Isley, a grow management app that exposes sensor readings (temperature, humidity, VPD), plant data, and moisture levels via a JSON API. Set stats_api_url in the overlay config to point to your Isley instance and the overlay will display live sensor data and a rotating plant ticker automatically.

Azuracast

Set audio_source.url to an Azuracast (or any HTTP) audio stream to add a live audio track to the output. Optionally enable now_playing_enabled to poll the Azuracast NowPlaying API and display the current artist and track title as a stacked overlay element.


Security & Deployment Notes

  • Growcast has no TLS and no rate limiting. It is not designed for direct internet exposure.
  • The API is unauthenticated by default. Set api_key in config.yaml if you need to restrict access — all endpoints except /health will then require an X-API-Key header.
  • For Docker deployments, host: "0.0.0.0" is correct — external access is controlled by the ports: mapping in docker-compose.yml.
  • For bare-metal deployments, set host: "localhost" to restrict the API to the local machine.

Prerequisites

Note: The Docker image bundles FFmpeg built with ZMQ support. No separate FFmpeg installation is required.

For bare-metal builds, FFmpeg 6.0+ must be in PATH and built with --enable-libzmq.


Quick Start

1. Copy the example config and fill in your values:

cp config.example.yaml config.yaml

Edit config.yaml with your camera URLs, stream destinations, and scene definitions. See Configuration below.

2. Start with Docker Compose:

docker compose up -d

The container mounts ./config.yaml read-only and persists overlay temp files in a named Docker volume.

3. Check that it's running:

curl http://localhost:8080/health
# {"status":"ok"}

curl http://localhost:8080/status

Configuration

All configuration lives in config.yaml. The full annotated reference is in config.example.yaml.

Minimal Configuration

Only cameras, scenes, and outputs are required. Everything else is optional.

cameras:
  cam1:
    name: "Camera 1"
    rtsps_url: "rtsp://192.168.1.100:554/stream"
    enabled: true

scenes:
  scene1:
    name: "Scene 1"
    cameras: [cam1]
    layout: "single"

outputs:
  out1:
    name: "Stream"
    type: "rtmp"
    url: "rtmp://your-server/live"
    key: "your-stream-key"
    enabled: true

Cameras

cameras:
  cam1:
    id: cam1
    name: "Grow Tent - Front"
    rtsps_url: "rtsps://192.168.1.100:7441/stream/unicam/xxxxxx?enableSrtp"
    username: "user"
    password: "password"
    enabled: true
    width: 1920
    height: 1080

Scenes

scenes:
  single_cam1:
    id: single_cam1
    name: "Single - Front"
    cameras: [cam1]
    layout: "single"

  pip_view:
    id: pip_view
    name: "PiP View"
    cameras: [cam1, cam2]   # first camera is primary
    layout: "pip"

  quad_view:
    id: quad_view
    name: "Quad View"
    cameras: [cam1, cam2, cam3, cam4]
    layout: "grid"

Layouts:

Layout Cameras Description
single 1 Full-frame single camera
pip 2 Primary camera full-frame with second camera inset
grid 2–4 2×2 grid (unused slots are black)

Outputs

outputs:
  twitch:
    id: twitch
    name: "Twitch"
    type: "rtmps"
    url: "rtmps://live-iad.twitch.tv:443/app"
    key: "your-stream-key"
    enabled: true

Multiple outputs are supported. FFmpeg encodes once and fans out to all enabled destinations via the tee muxer.

Encoding

encoding:
  bitrate: "5000k"
  preset: "veryfast"      # x264: ultrafast…veryslow  |  NVENC: p1 (fast) … p7 (quality)
  keyframe_seconds: 1     # Streamplace requires ≤7s (1s recommended)
  x264_params: "bframes=0"  # Ignored when NVENC is active
Field Default Notes
bitrate 5000k Target output bitrate
preset veryfast Encoder speed/quality tradeoff
keyframe_seconds 2 Keyframe interval in seconds
x264_params (empty) Extra x264 options; ignored with NVENC

Hardware Acceleration

Optional NVIDIA GPU encode/decode. Requires the GPU Docker image (see Docker).

hardware_acceleration:
  enabled: false
  type: "nvenc"   # Only "nvenc" is supported
  device: "0"     # GPU device index (0 = first GPU)
  decode: false   # Also decode camera streams on GPU (h264_cuvid)
  • Encode path (enabled: true): uses h264_nvenc with CPU-side yuv420p frames. No hwupload_cuda filter — the encoder handles the CPU→GPU upload internally.
  • Decode path (decode: true): uses h264_cuvid per camera input, then downloads frames back to system memory before the software filter graph runs. Requires FFmpeg built with --enable-cuvid.
  • On the host, nvidia-smi must be working and nvidia-container-toolkit must be installed.

Overlay

Growcast supports two overlay modes. Set stats_api_url to use API mode (recommended with Isley); otherwise set stats_file_path for file mode.

API mode (Isley):

overlay:
  enabled: true
  position: "top-right"      # top-left | top-right | bottom-left | bottom-right
  font_size: 26
  font_color: "white"
  shadow_color: "black"
  font_file: "./fonts/Anton-Regular.ttf"  # bundled; mount your own at this path to override

  stats_api_url: "https://your-isley-host/api/overlay"
  stats_api_interval: 15s
  stats_api_key: "your_api_key_here"

  temp_sensor_id: 1      # sensor ID for temperature
  humidity_sensor_id: 2  # sensor ID for relative humidity
  vpd_sensor_id: 3       # sensor ID for VPD

  plant_ticker_enabled: true
  plant_ticker_interval: 10s
  moisture_sensor_source: ""  # e.g. "192.168.1.21" — appends soil % to ticker

Now-playing overlay (Azuracast):

overlay:
  # ...
  now_playing_enabled: false
  now_playing_api_url: "https://radio.example.com/api/nowplaying/station_slug"
  now_playing_api_key: ""           # Required if station is not public
  now_playing_interval: 15s
  now_playing_position: "bottom-left"

File mode (legacy):

overlay:
  enabled: true
  stats_file_path: "/var/growcast/stats.txt"
  refresh_interval: 5s

Anton-Regular.ttf is bundled in the repo and baked into the Docker image. To use a different font, mount it over the bundled one: - ./fonts:/app/fonts:ro.

Audio Source

audio_source:
  enabled: true
  url: "http://your-azuracast-server:8000/radio.mp3"
  volume: 1.0
  reconnect: true
  reconnect_delay: 2

Scene Switching

scene_switch:
  enabled: true
  default_scene: "single_cam1"
  scene_order:
    - single_cam1
    - pip_view
    - quad_view
  interval: 5m

Auto-rotation cycles through scene_order on the configured interval. Scene switching can also be triggered via the API at any time.

API

api:
  enabled: true
  host: "0.0.0.0"  # correct for Docker; use "localhost" for bare-metal
  port: 8080
  # api_key: ""    # Optional. If set, all endpoints (except /health) require X-API-Key header.

REST API

Method Endpoint Description
GET /health Health check (always unauthenticated)
GET /status Current scene, camera states, timestamp
GET /scene/current Active scene ID
GET /cameras All camera configurations and states
GET /cameras/{id} Single camera
POST /scene/{id}/start Switch to a scene immediately
POST /camera/{id}/restart Restart the current scene (reconnects all cameras)
GET /info App version and camera count

Without API key (default):

curl -X POST http://localhost:8080/scene/pip_view/start

With API key configured:

curl -X POST http://localhost:8080/scene/pip_view/start \
  -H "X-API-Key: your-key-here"

The API has no authentication by default. To restrict access, set api_key in config.yaml — all endpoints except /health will then require an X-API-Key header.


Docker

CPU (default)

Pre-built image from Docker Hub:

docker pull dwot/growcast:latest
docker compose up -d

GPU (NVENC/CUVID)

Pre-built image from Docker Hub:

docker pull dwot/growcast:gpu-latest

Prerequisites on the Docker host:

  1. NVIDIA driver installed (nvidia-smi should work)
  2. nvidia-container-toolkit installed and configured
  3. Docker daemon configured to use the NVIDIA runtime

Run with GPU compose file:

docker compose -f docker-compose.gpu.yml up -d

Then enable hardware_acceleration in config.yaml.

Build locally

# CPU
docker compose up --build -d

# GPU
docker compose -f docker-compose.gpu.yml up --build -d

Applying config changes

Growcast has no hot-reload. After editing config.yaml, restart with:

docker compose up -d

Use up -d, not restartrestart only restarts the container process but does not re-mount the config or rebuild the FFmpeg filter graph.

View logs

docker compose logs -f

Building from Source

Requires Go 1.24+ and FFmpeg 6.0+ with --enable-libzmq in PATH.

go build -o growcast ./cmd/growcast/...
./growcast -config config.yaml
./growcast -config config.yaml -log-level debug

Tests:

go test ./...

License

MIT

About

An experimental livestream designed for a 24/7 grow tent channel.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors