---
title: "whip-printer-stream.sh (command line WHIP on raspi)"
canonical: "https://openvibe.community/p/teal-dock-83"
updated: null
---

# whip-printer-stream.sh (command line WHIP on raspi)

HTML: [whip-printer-stream.sh (command line WHIP on raspi)](https://openvibe.community/p/teal-dock-83)

# whip-printer-stream.sh (command line WHIP on raspi)

 [![](https://openvibe.media/o/med_01KZK5GRQ088Y3CW1TMEF4Z4RN?w=96) goosely](https://openvibe.live/@goosely) · [Bash / shell](/pastes?lang=bash) · Sat, 25 Apr 2026 01:18 UTC · 86 views · 178 lines 

 A script that captures live video from a camera, encodes it with FFmpeg, and streams it to a WHIP server, with adaptive settings and retry logic.

  [ Raw](/p/teal-dock-83/raw) [ Download](/p/teal-dock-83/download) [ Fork](/new?fork=teal-dock-83) [ Discuss in the forum](/s/discuss?paste=teal-dock-83) 

~~~~
#!/usr/bin/env bash
set -euo pipefail

CAM=/dev/video0
KEY_FILE=/opt/ai/conf/whip-key
START_DELAY=20
CHECK_URL=https://whip.hobostreamer.com/
BACKOFF=5
MAX_BACKOFF=300

choose_video_size() {
  if ! command -v v4l2-ctl >/dev/null 2>&1; then
    printf '%s\n' '640x480'
    return
  fi

  local size
  size=$(v4l2-ctl -d "$CAM" --list-formats-ext 2>/dev/null | awk '
    /Size: Discrete/ {
      sz = $3;
      split(sz, dims, "x");
      width = dims[1] + 0;
      height = dims[2] + 0;
      area = width * height;
      if (width <= 640 && height <= 480) {
        candidate = area "|" sz;
      } else {
        candidate = "";
      }
      next
    }
    /Interval: Discrete .*30(\.000)? fps/ && candidate {
      print candidate;
      candidate = "";
    }
  ' | sort -t'|' -k1,1nr | head -n 1 | cut -d'|' -f2)

  if [[ -z "$size" ]]; then
    printf '%s\n' '640x480'
  else
    printf '%s\n' "$size"
  fi
}

choose_video_bitrate() {
  case "$1" in
    1280x720) printf '%s\n' '1500k' ;;
    1024x576|960x720) printf '%s\n' '1200k' ;;
    640x480) printf '%s\n' '900k' ;;
    640x360) printf '%s\n' '900k' ;;
    *) printf '%s\n' '900k' ;;
  esac
}

choose_buffer_size() {
  case "$1" in
    1500k) printf '%s\n' '3000k' ;;
    1200k) printf '%s\n' '2400k' ;;
    900k) printf '%s\n' '1800k' ;;
    *) printf '%s\n' '1800k' ;;
  esac
}

choose_frame_rate() {
  case "$1" in
    1280x720) printf '%s\n' '15' ;;
    640x480) printf '%s\n' '20' ;;
    *) printf '%s\n' '30' ;;
  esac
}

choose_input_format() {
  local target="$1"
  if ! command -v v4l2-ctl >/dev/null 2>&1; then
    printf '%s\n' 'mjpeg'
    return
  fi

  local format
  format=$(v4l2-ctl -d "$CAM" --list-formats-ext 2>/dev/null | awk -v target="$target" '
    /^[[:space:]]*\[[0-9]+\]:/ {
      line = $0;
      gsub(/^[[:space:]]*\[[0-9]+\]:[[:space:]]*\x27/, "", line);
      gsub(/\x27.*$/, "", line);
      curfmt = line;
      next
    }
    /^[[:space:]]*Size: Discrete/ {
      if ($3 == target) {
        size_ok = 1;
      } else {
        size_ok = 0;
      }
      next
    }
    /^[[:space:]]*Interval: Discrete .*30(\.000)? fps/ && size_ok {
      if (curfmt == "YUYV") {
        print "yuyv422";
        exit
      }
      if (curfmt == "MJPG") {
        print "mjpeg";
        exit
      }
    }
  ')

  if [[ -z "$format" ]]; then
    printf '%s\n' 'mjpeg'
  else
    printf '%s\n' "$format"
  fi
}

check_connectivity() {
  if command -v curl >/dev/null 2>&1; then
    curl -fsS --connect-timeout 10 "$CHECK_URL" >/dev/null 2>&1 && return 0
  elif command -v ping >/dev/null 2>&1; then
    ping -c 1 -W 2 8.8.8.8 >/dev/null 2>&1 && return 0
  fi
  return 1
}

if [[ "$START_DELAY" -gt 0 ]]; then
  echo "$(date) startup delay: waiting $START_DELAY seconds for network"
  sleep "$START_DELAY"
fi

while true; do
  if [[ ! -c "$CAM" ]]; then
    echo "$(date) camera missing: $CAM"
    sleep 10
    continue
  fi

  if [[ ! -f "$KEY_FILE" ]]; then
    echo "$(date) key file missing: $KEY_FILE"
    sleep "$BACKOFF"
    BACKOFF=$(( BACKOFF * 2 ))
    if (( BACKOFF > MAX_BACKOFF )); then BACKOFF=$MAX_BACKOFF; fi
    continue
  fi

  STREAM_KEY="$(tr -d '\r\n ' < "$KEY_FILE")"
  URL="https://whip.hobostreamer.com/whip/50?key=$STREAM_KEY"

  if ! check_connectivity; then
    echo "$(date) network/HoboStreamer unreachable, waiting $BACKOFF seconds"
    sleep "$BACKOFF"
    BACKOFF=$(( BACKOFF * 2 ))
    if (( BACKOFF > MAX_BACKOFF )); then BACKOFF=$MAX_BACKOFF; fi
    continue
  fi
  VIDEO_SIZE="$(choose_video_size)"
  VIDEO_BITRATE="$(choose_video_bitrate "$VIDEO_SIZE")"
  VIDEO_BUFSIZE="$(choose_buffer_size "$VIDEO_BITRATE")"
  VIDEO_FPS="$(choose_frame_rate "$VIDEO_SIZE")"
  VIDEO_FORMAT="$(choose_input_format "$VIDEO_SIZE")"

  echo "$(date) using camera resolution $VIDEO_SIZE at $VIDEO_BITRATE (bufsize=$VIDEO_BUFSIZE) fps=$VIDEO_FPS format=$VIDEO_FORMAT"

  ffmpeg -hide_banner -loglevel info \
    -f v4l2 -input_format "$VIDEO_FORMAT" -video_size "$VIDEO_SIZE" -framerate "$VIDEO_FPS" -i "$CAM" \
    -f alsa -i default \
    -vf format=yuv420p \
    -c:v libx264 -preset ultrafast -tune zerolatency \
    -profile:v baseline -level 3.0 \
    -x264-params "keyint=30:min-keyint=30:scenecut=0:force-cfr=1:repeat-headers=1" \
    -b:v "$VIDEO_BITRATE" -maxrate "$VIDEO_BITRATE" -bufsize "$VIDEO_BUFSIZE" \
    -c:a libopus -b:a 64k -ar 48000 \
    -ts_buffer_size 20M \
    -f whip "$URL"

  echo "$(date) FFmpeg exited. retrying in $BACKOFF seconds."
  sleep "$BACKOFF"
  BACKOFF=$(( BACKOFF * 2 ))
  if (( BACKOFF > MAX_BACKOFF )); then BACKOFF=$MAX_BACKOFF; fi
done
~~~~

## Comments

[Sign in with your OpenVibe account](/auth/login?next=%2Fp%2Fteal-dock-83) to comment.

No comments yet.

## More like this

[Browse ](/pastes?lang=bash)

 [  

### test-microsd.sh

 ](/p/teal-holly-41) 

 [![](https://openvibe.media/o/med_01KZK5GRQ088Y3CW1TMEF4Z4RN?w=96) goosely](https://openvibe.live/@goosely) [Bash / shell](/pastes?lang=bash) 138 1mo ago 

 [  

### hobostreamer\_control\_example.py

 ](/p/opal-blade-40) 

 [![](https://openvibe.media/o/med_01KZK5GRQ088Y3CW1TMEF4Z4RN?w=96) goosely](https://openvibe.live/@goosely) [Bash / shell](/pastes?lang=bash) 60 6mo ago 

 [  

### hobostreamer\_cozmo.py

 ](/p/gold-dock-58) 

 [![](https://openvibe.media/o/med_01KZK5GRQ088Y3CW1TMEF4Z4RN?w=96) goosely](https://openvibe.live/@goosely) [Bash / shell](/pastes?lang=bash) 55 6mo ago 

 [  

### yo

 ](/p/fast-bay-50) 

 [![](https://openvibe.media/o/med_01M2MPJRFRA9XH2JK6CJ8YKNXH?w=96) Maticus](https://openvibe.live/@Maticus) [Markdown](/pastes?lang=markdown) 11 1d ago 

 [  

### OpenVibe Node v0.18.0 — Windows Deadline Protection Follow-Up

 ](/p/lit-gem-67) 

 [![](https://openvibe.media/o/med_01M2MPJRFRA9XH2JK6CJ8YKNXH?w=96) Maticus](https://openvibe.live/@Maticus) [Markdown](/pastes?lang=markdown) 33 5d ago 

 [  

### Maticus Reply

 ](/p/icy-moon-90) 

 Anonymous [Plain text](/pastes?lang=text) 35 6d ago
