Skip to content

Instantly share code, notes, and snippets.

@mahmoud
Last active September 19, 2025 23:32
Show Gist options
  • Save mahmoud/e50740d8f28d67c0b037ae58aabfc1fd to your computer and use it in GitHub Desktop.
Save mahmoud/e50740d8f28d67c0b037ae58aabfc1fd to your computer and use it in GitHub Desktop.
Using a Fuji XT-4 as a webcam for streaming/recording. Working in 2025 on a Thinkpad T14s Ryzen via USB-C, running Ubuntu 22.04 LTS. Read the comments for details. Latency is reasonably low, better than the cheap capture cards often recommended online.
#!/bin/bash -xe
# --- Fuji XT-4 Webcam Startup Script ---
# This script sets up the Fujifilm XT-4 as a webcam from a clean slate.
#
# USAGE:
# ./fuji_xt4_ubuntu.sh (Starts in default mode, full visible frame)
# ./fuji_xt4_ubuntu.sh --letterbox (Starts in 16:9 letterbox mode)
#
# hit ctrl-c to restart the stream (and refocus), hit ctrl-c twice in quick succession to stop the stream.
#
# Required packages:
# sudo apt install gphoto2 ffmpeg v4l2loopback-dkms git build-essential linux-headers-$(uname -r) v4l-utils vlc cpufrequtils
# --- Script Configuration ---
FULL_TOP_CROP=40 # by default the liveview stream has black bars on top and bottom
FULL_BOTTOM_CROP=40
RESTART_DELAY_MS=2000 # milliseconds to wait between stream restarts, seems to need at least 2 seconds to restart cleanly
# --- Common gphoto2 Parameters ---
# These can be modified to customize camera behavior
GPHOTO2_STDOUT="--stdout"
GPHOTO2_FOCUS_MODE="--set-config /main/capturesettings/focusmode=2"
GPHOTO2_LIVEVIEW_SIZE="--set-config /main/capturesettings/liveviewsize=0"
GPHOTO2_CAPTURE_MOVIE="--capture-movie"
# Autofocus settings - continuous autofocus eludes me at this point. Only thing I haven't tried is a firmware update bc I'm already on firmware 2.01.
GPHOTO2_AUTOFOCUS_DRIVE="--set-config /main/actions/autofocusdrive=1"
GPHOTO2_FOCUS_METERING="--set-config /main/capturesettings/focusmetermode=0" # Single-area AF
# Additional common parameters (commented out - uncomment and modify as needed)
# GPHOTO2_ISO="--set-config /main/imgsettings/iso=800" # ISO values: 80-51200
# GPHOTO2_APERTURE="--set-config /main/capturesettings/f-number=f/2.8" # f/1.2 to f/22 (lens dependent)
# GPHOTO2_SHUTTER_SPEED="--set-config /main/capturesettings/shutterspeed=1/60" # 1/8000 to 15m + bulb
# GPHOTO2_WHITE_BALANCE="--set-config /main/imgsettings/whitebalance=0" # 0=Auto, 1=Daylight, 2=Tungsten, etc.
# GPHOTO2_METERING_MODE="--set-config /main/capturesettings/exposuremetermode=2" # 0=Average, 1=Center, 2=Multi, 3=Spot
# GPHOTO2_FILM_SIMULATION="--set-config /main/imgsettings/filmsimulation=0" # 0=PROVIA, 1=Velvia, 2=ASTIA, etc.
# GPHOTO2_FOCUS_METERING="--set-config /main/capturesettings/focusmetermode=0" # 0=Single-area, 1=Dynamic, 2=Group
# GPHOTO2_IMAGE_FORMAT="--set-config /main/imgsettings/imageformat=1" # 0=RAW, 1=JPEG Fine, 2=JPEG Normal, etc.
echo "πŸ“Έ Starting Fuji Webcam..."
# Check for aspect ratio flag
MODE="full"
if [ "$1" == "--letterbox" ]; then
MODE="letterbox"
fi
echo "Aspect Ratio Mode: $MODE"
# 1. Prompt for administrator password upfront
sudo -v
# 2. Kill any conflicting processes that might be holding the camera
echo "Checking for conflicting processes..."
killall gvfs-gphoto2-volume-monitor > /dev/null 2>&1 || true
# 3. Ensure v4l2loopback is ready (reload for a clean slate)
echo "Reloading virtual camera module..."
sudo modprobe -r v4l2loopback
sleep 1
sudo modprobe v4l2loopback exclusive_caps=1 card_label="Fuji XT-4"
sleep 1
# 4. Find the virtual video device dynamically
echo "Detecting available video devices..."
# First, check if v4l2-ctl command is available and working
if ! command -v v4l2-ctl &> /dev/null; then
echo "❌ Error: v4l2-ctl command not found. Please install v4l-utils package:"
echo " sudo apt install v4l-utils"
exit 1
fi
# Get the list of devices, with error handling
DEVICE_LIST=$(v4l2-ctl --list-devices 2>&1)
if [ $? -ne 0 ]; then
echo "❌ Error: Failed to list video devices:"
echo "$DEVICE_LIST"
exit 1
fi
# Try to find the Fuji XT-4 device
VIDEO_DEVICE=$(echo "$DEVICE_LIST" | grep -A 1 'Fuji XT-4' | grep -o '/dev/video[0-9]*')
if [ -z "$VIDEO_DEVICE" ]; then
echo "❌ Error: Could not find the 'Fuji XT-4' virtual webcam."
echo ""
echo "Available video devices:"
echo "======================="
echo "$DEVICE_LIST"
echo ""
echo "πŸ’‘ Troubleshooting tips:"
echo " 1. Make sure the v4l2loopback module loaded correctly"
echo " 2. Check if the card_label matches exactly: 'Fuji XT-4'"
echo " 3. Try rerunning the script to reload the module"
exit 1
fi
echo "βœ… Virtual webcam found at $VIDEO_DEVICE"
# 5. Start the stream with continuous autofocus based on the selected mode
echo "πŸš€ Starting video stream... (Press Ctrl+C to restart)"
# Function to start the stream based on mode
start_stream() {
if [ "$MODE" == "full" ]; then
# --- FULL MODE ---
# Crop pixels from top and bottom to remove native letterboxing.
gphoto2 $GPHOTO2_STDOUT $GPHOTO2_FOCUS_MODE $GPHOTO2_LIVEVIEW_SIZE $GPHOTO2_FOCUS_METERING $GPHOTO2_AUTOFOCUS_DRIVE $GPHOTO2_CAPTURE_MOVIE | \
ffmpeg -fflags nobuffer -i - -s 1152x768 \
-vf "crop=in_w:in_h-$((FULL_TOP_CROP + FULL_BOTTOM_CROP)):0:$FULL_TOP_CROP" \
-vcodec rawvideo -pix_fmt yuv420p -preset ultrafast -tune zerolatency \
-f v4l2 "$VIDEO_DEVICE"
else
# --- LETTERBOX MODE (Default) ---
# Crop the stream to fill a 16:9 frame
gphoto2 $GPHOTO2_STDOUT $GPHOTO2_FOCUS_MODE $GPHOTO2_LIVEVIEW_SIZE $GPHOTO2_FOCUS_METERING $GPHOTO2_AUTOFOCUS_DRIVE $GPHOTO2_CAPTURE_MOVIE | \
ffmpeg -fflags nobuffer -i - \
-vf "scale=1280:720:force_original_aspect_ratio=increase,crop=1280:720" \
-vcodec rawvideo -pix_fmt yuv420p -preset ultrafast -tune zerolatency \
-f v4l2 "$VIDEO_DEVICE"
fi
}
# Main restart loop - just restart whenever the stream exits
while true; do
echo "▢️ Starting stream..."
# Run stream and capture exit code (don't let -e kill the script)
start_stream || STREAM_EXIT_CODE=$?
# Kill any remaining gphoto2 and gvfs processes (including slaves/children)
pkill -f "gphoto2" 2>/dev/null || true
pkill -f "gvfs-gphoto2" 2>/dev/null || true
# Stream ended for any reason, wait and restart
echo "⏸️ Stream stopped (exit code: ${STREAM_EXIT_CODE:-0}). Waiting ${RESTART_DELAY_MS}ms before restart..."
# Convert milliseconds to seconds for sleep
RESTART_DELAY_SEC=$(echo "scale=3; $RESTART_DELAY_MS / 1000" | bc -l 2>/dev/null || echo "0.2")
sleep "$RESTART_DELAY_SEC"
echo "πŸ”„ Restarting stream..."
STREAM_EXIT_CODE=0 # Reset for next iteration
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment