Last active
          September 19, 2025 23:32 
        
      - 
      
- 
        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.
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | #!/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