feat(cli): add webcam-gb capture script with palette options - webgbcam - [fork] gameboy webcam
HTML git clone git://src.adamsgaard.dk/webgbcam
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
DIR commit 629639e1bd6fd47632d7fceee831587def57d552
DIR parent e49591b1954c1eae6ff4fe280185a475bb8180e2
HTML Author: Anders Damsgaard <anders@adamsgaard.dk>
Date: Sat, 14 Feb 2026 16:49:40 +0100
feat(cli): add webcam-gb capture script with palette options
Diffstat:
A webcam-gb | 249 +++++++++++++++++++++++++++++++
1 file changed, 249 insertions(+), 0 deletions(-)
---
DIR diff --git a/webcam-gb b/webcam-gb
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+set -eu
+
+usage() {
+ cat <<'EOF'
+Usage: ./webcam-gb [options]
+
+Capture webcam video with a webgbcam-like filter.
+
+Options:
+ -o, --output FILE Output file (default: webgbcam-YYYYmmdd-HHMMSS.mp4)
+ -d, --device DEVICE Video device
+ macOS: avfoundation input (default: 0:none)
+ Linux: v4l2 path (default: /dev/video0)
+ -t, --duration SECONDS Record duration (default: until interrupted)
+ -p, --palette NAME Color palette (default: crtgb)
+ --fps FPS Capture framerate (default: 30)
+ --input-size WxH Camera capture size (default: 640x480)
+ --scale N Integer upscale factor from 128x112 (default: 6)
+ --list-palettes List available palettes and exit
+ --list-devices List available camera devices and exit
+ -h, --help Show this help and exit
+EOF
+}
+
+need_arg() {
+ if [ $# -lt 2 ]; then
+ echo "missing value for $1" >&2
+ exit 1
+ fi
+}
+
+require_ffmpeg() {
+ if ! command -v ffmpeg >/dev/null 2>&1; then
+ echo "ffmpeg not found in PATH" >&2
+ exit 1
+ fi
+}
+
+list_palettes() {
+ cat <<'EOF'
+Available palettes:
+ crtgb
+ amber-crtgb
+ ayy4
+ spacehaze
+ kirby-sgb
+ cherrymelon
+ pumpkin-gb
+ purpledawn
+ royal4
+ grayscale
+ virtual
+ love-love
+EOF
+}
+
+set_palette() {
+ case "$1" in
+ crtgb)
+ C0=0x060601 C1=0x0B3E08 C2=0x489A0D C3=0xDAF222
+ ;;
+ amber-crtgb)
+ C0=0x0D0405 C1=0x5E1210 C2=0xD35600 C3=0xFED018
+ ;;
+ ayy4)
+ C0=0x00303B C1=0xFF7777 C2=0xFFCE96 C3=0xF1F2DA
+ ;;
+ spacehaze)
+ C0=0x0B0630 C1=0x6B1FB1 C2=0xCC3495 C3=0xF8E3C4
+ ;;
+ kirby-sgb)
+ C0=0x2C2C96 C1=0x7733E7 C2=0xE78686 C3=0xF7BEF7
+ ;;
+ cherrymelon)
+ C0=0x012824 C1=0x265935 C2=0xFF4D6D C3=0xFCDEEA
+ ;;
+ pumpkin-gb)
+ C0=0x142B23 C1=0x19692C C2=0xF46E16 C3=0xF7DB7E
+ ;;
+ purpledawn)
+ C0=0x001B2E C1=0x2D757E C2=0x9A7BBC C3=0xEEFDED
+ ;;
+ royal4)
+ C0=0x521296 C1=0x8A1FAC C2=0xD4864A C3=0xEBDB5E
+ ;;
+ grayscale)
+ C0=0x282828 C1=0x686868 C2=0xA8A8A8 C3=0xFCFCFC
+ ;;
+ virtual)
+ C0=0x020000 C1=0x410000 C2=0x7F0000 C3=0xFF0000
+ ;;
+ love-love)
+ C0=0xB01030 C1=0xFF60B0 C2=0xFFB8E8 C3=0xFFFFFF
+ ;;
+ *)
+ echo "unknown palette: $1" >&2
+ list_palettes >&2
+ exit 1
+ ;;
+ esac
+}
+
+FPS=30
+INPUT_SIZE=640x480
+SCALE=6
+DURATION=
+LIST_DEVICES=0
+LIST_PALETTES=0
+PALETTE=crtgb
+OUTPUT_FILE="webgbcam-$(date +%Y%m%d-%H%M%S).mp4"
+DEVICE=
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -o|--output)
+ need_arg "$@"
+ OUTPUT_FILE=$2
+ shift 2
+ ;;
+ -d|--device)
+ need_arg "$@"
+ DEVICE=$2
+ shift 2
+ ;;
+ -t|--duration)
+ need_arg "$@"
+ DURATION=$2
+ shift 2
+ ;;
+ -p|--palette)
+ need_arg "$@"
+ PALETTE=$2
+ shift 2
+ ;;
+ --fps)
+ need_arg "$@"
+ FPS=$2
+ shift 2
+ ;;
+ --input-size)
+ need_arg "$@"
+ INPUT_SIZE=$2
+ shift 2
+ ;;
+ --scale)
+ need_arg "$@"
+ SCALE=$2
+ shift 2
+ ;;
+ --list-palettes)
+ LIST_PALETTES=1
+ shift
+ ;;
+ --list-devices)
+ LIST_DEVICES=1
+ shift
+ ;;
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ *)
+ echo "unknown option: $1" >&2
+ usage >&2
+ exit 1
+ ;;
+ esac
+done
+
+if [ "$LIST_PALETTES" -eq 1 ]; then
+ list_palettes
+ exit 0
+fi
+
+set_palette "$PALETTE"
+
+OS_NAME=$(uname -s)
+
+case "$OS_NAME" in
+ Darwin)
+ INPUT_FORMAT=avfoundation
+ if [ -z "$DEVICE" ]; then
+ DEVICE="0:none"
+ fi
+ ;;
+ Linux)
+ INPUT_FORMAT=v4l2
+ if [ -z "$DEVICE" ]; then
+ DEVICE="/dev/video0"
+ fi
+ ;;
+ *)
+ echo "unsupported OS: $OS_NAME (expected Darwin or Linux)" >&2
+ exit 1
+ ;;
+esac
+
+if [ "$LIST_DEVICES" -eq 1 ]; then
+ case "$INPUT_FORMAT" in
+ avfoundation)
+ require_ffmpeg
+ ffmpeg -hide_banner -f avfoundation -list_devices true -i "" 2>&1 || true
+ ;;
+ v4l2)
+ if command -v v4l2-ctl >/dev/null 2>&1; then
+ v4l2-ctl --list-devices
+ else
+ ls /dev/video* 2>/dev/null || echo "no /dev/video* devices found"
+ fi
+ ;;
+ esac
+ exit 0
+fi
+
+FILTER_GRAPH='
+[0:v]fps='"$FPS"',scale=128:112:flags=lanczos,format=gray,eq=contrast=1.5:gamma=1.0,unsharp=5:5:0.75:3:3:0.0[cam];
+color=c='"$C0"':s=4x1:d=1[pal0];
+[pal0]drawbox=x=1:y=0:w=1:h=1:color='"$C1"':t=fill,drawbox=x=2:y=0:w=1:h=1:color='"$C2"':t=fill,drawbox=x=3:y=0:w=1:h=1:color='"$C3"':t=fill,scale=256:1:flags=neighbor,format=rgb24[pal];
+[cam][pal]paletteuse=dither=bayer:bayer_scale=2,scale=iw*'"$SCALE"':ih*'"$SCALE"':flags=neighbor,setsar=1
+'
+
+set -- -hide_banner -y
+
+case "$INPUT_FORMAT" in
+ avfoundation)
+ set -- "$@" -f avfoundation -framerate "$FPS" -video_size "$INPUT_SIZE" -i "$DEVICE"
+ ;;
+ v4l2)
+ set -- "$@" -f v4l2 -framerate "$FPS" -video_size "$INPUT_SIZE" -i "$DEVICE"
+ ;;
+esac
+
+if [ -n "$DURATION" ]; then
+ set -- "$@" -t "$DURATION"
+fi
+
+set -- "$@" \
+ -an \
+ -filter_complex "$FILTER_GRAPH" \
+ -c:v libx264 \
+ -pix_fmt yuv420p \
+ -movflags +faststart \
+ "$OUTPUT_FILE"
+
+require_ffmpeg
+echo "recording from $INPUT_FORMAT device '$DEVICE' with palette '$PALETTE' -> $OUTPUT_FILE"
+ffmpeg "$@"