Skip to content

Instantly share code, notes, and snippets.

@mahmoud
Last active September 19, 2025 23:32
Show Gist options
  • Select an option

  • Save mahmoud/e50740d8f28d67c0b037ae58aabfc1fd to your computer and use it in GitHub Desktop.

Select an option

Save mahmoud/e50740d8f28d67c0b037ae58aabfc1fd to your computer and use it in GitHub Desktop.

Revisions

  1. mahmoud revised this gist Sep 19, 2025. 1 changed file with 49 additions and 21 deletions.
    70 changes: 49 additions & 21 deletions fuji_xt4_ubuntu.sh
    Original file line number Diff line number Diff line change
    @@ -4,15 +4,18 @@
    # This script sets up the Fujifilm XT-4 as a webcam from a clean slate.
    #
    # USAGE:
    # ./start-camera.sh (Starts in default mode, full visible frame)
    # ./start-camera.sh --letterbox (Starts in 16:9 letterbox mode)
    # ./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
    @@ -95,22 +98,47 @@ 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 in this window to stop)"

    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
    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
  2. mahmoud revised this gist Sep 18, 2025. No changes.
  3. mahmoud revised this gist Sep 18, 2025. No changes.
  4. mahmoud revised this gist Sep 18, 2025. No changes.
  5. mahmoud revised this gist Sep 18, 2025. No changes.
  6. mahmoud created this gist Sep 18, 2025.
    116 changes: 116 additions & 0 deletions fuji_xt4_ubuntu.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,116 @@
    #!/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:
    # ./start-camera.sh (Starts in default mode, full visible frame)
    # ./start-camera.sh --letterbox (Starts in 16:9 letterbox mode)
    #
    # 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

    # --- 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 in this window to stop)"

    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