Skip to content
Om Puter

Om Puter

Berbagi Tutorial Coding dan Pemrograman Komputer

Menu
  • Channel YouTube ThirteeNov
  • Channel YouTube Om Puter
Menu

Contoh kode .sh untuk stream layar linux pakai FFMPEG

Posted on 4 Agustus 2026 by OmPuter

Di bawah ini adalah contoh kode .sh untuk stream ke YouTube dari Linux, misal gdm3, dengan pengaturan yang bisa kita sesuaikan.

#!/bin/bash

# ============================================================================
# KONFIGURASI STREAMING
# ============================================================================

# Pengaturan RTMP
readonly RTMP_URL="rtmp://a.rtmp.youtube.com/live2/STREAMKEY"

# Pengaturan Capture Area (area layar yang diambil, sebelum di-scale)
readonly CAPTURE_WIDTH=750
readonly CAPTURE_HEIGHT=500
readonly OFFSET_X=150
readonly OFFSET_Y=100

# Pengaturan Output (HANYA satu resolusi, bebas kamu set sendiri)
# Contoh: mau square? set 512x512. Mau 16:9? set 1280x720. Dst.
readonly OUTPUT_WIDTH=512
readonly OUTPUT_HEIGHT=512

# Pengaturan Video
readonly FRAMERATE=12
readonly BITRATE="1000k"
readonly MAXRATE="1200k"
readonly BUFSIZE="2000k"

# Pengaturan Audio
readonly AUDIO_FILE="/home/habibie/Documents/stream/sbacodes/allsba.MP3"
readonly AUDIO_BITRATE="96k"
readonly AUDIO_SAMPLERATE=44100

# ============================================================================
# VARIABEL GLOBAL
# ============================================================================

DISPLAY_NUM=":0"
FFMPEG_PID=""

# ============================================================================
# FUNGSI UTILITY
# ============================================================================

log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

log_error() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >&2
}

check_dependencies() {
    local missing_deps=()

    if ! command -v ffmpeg &> /dev/null; then
        missing_deps+=("ffmpeg")
    fi

    if ! command -v xdpyinfo &> /dev/null; then
        missing_deps+=("xdpyinfo")
    fi

    if ! command -v xhost &> /dev/null; then
        missing_deps+=("x11-utils")
    fi

    if [ ${#missing_deps[@]} -ne 0 ]; then
        log_error "Missing dependencies: ${missing_deps[*]}"
        log_message "Install with: sudo apt install ${missing_deps[*]}"
        exit 1
    fi

    log_message "All dependencies found"
}

# ============================================================================
# FUNGSI DETEKSI SISTEM
# ============================================================================

detect_display() {
    if [ -n "$DISPLAY" ]; then
        DISPLAY_NUM="$DISPLAY"
        log_message "Using DISPLAY from environment: $DISPLAY_NUM"
    else
        local detected_display=$(who | grep -E "\(:[0-9]+\.[0-9]+\)" | head -1 | sed 's/.*(\(:[0-9]*\.[0-9]*\))/\1/' | cut -d. -f1)
        if [ -n "$detected_display" ]; then
            DISPLAY_NUM="$detected_display"
            log_message "Detected active display: $DISPLAY_NUM"
        else
            DISPLAY_NUM=":0"
            log_message "Using default display: $DISPLAY_NUM"
        fi
    fi

    # PENTING (fix offset): x11grab sering gagal parsing offset "+x,y" kalau
    # display tidak menyertakan nomor screen (mis. ":0" tanpa ".0").
    # Pastikan selalu ada ".0" di belakang, kecuali sudah ada nomor screen.
    if [[ "$DISPLAY_NUM" != *.* ]]; then
        DISPLAY_NUM="${DISPLAY_NUM}.0"
        log_message "Normalized display to include screen number: $DISPLAY_NUM"
    fi

    export DISPLAY="$DISPLAY_NUM"
}

detect_session_type() {
    if [ -n "$XDG_SESSION_TYPE" ]; then
        log_message "Session type: $XDG_SESSION_TYPE"
        if [ "$XDG_SESSION_TYPE" = "wayland" ]; then
            log_error "Wayland detected! x11grab won't work."
            log_message "Please switch to X11 session at login screen."
            log_message "Or use GNOME Screen Cast / PipeWire alternative."
            return 1
        fi
    elif [ -n "$WAYLAND_DISPLAY" ]; then
        log_error "Wayland detected (WAYLAND_DISPLAY set)"
        return 1
    else
        log_message "Session type: X11 (assumed)"
    fi

    return 0
}

check_display_access() {
    log_message "Checking display access..."

    if ! xdpyinfo -display "$DISPLAY_NUM" &>/dev/null; then
        log_error "Cannot access display: $DISPLAY_NUM"
        log_message "Trying to grant access with xhost..."

        if xhost +SI:localuser:$(whoami) 2>/dev/null; then
            log_message "Display access granted"
        else
            log_error "Failed to grant display access"
            log_message "Try running: xhost +local:"
            return 1
        fi
    else
        log_message "Display access OK"
    fi

    return 0
}

check_audio_file() {
    if [ ! -f "$AUDIO_FILE" ]; then
        log_message "WARNING: Audio file not found: $AUDIO_FILE"
        log_message "Streaming will continue without audio"
        return 1
    fi
    log_message "Audio file found: $AUDIO_FILE"
    return 0
}

# ============================================================================
# FUNGSI INTI STREAMING
# ============================================================================

# Filter video: HANYA scale ke resolusi output yang kamu tentukan.
# TIDAK ada setdar dipaksa lagi -> aspect ratio otomatis ngikut OUTPUT_WIDTH x
# OUTPUT_HEIGHT yang kamu set (ini fix untuk masalah #3).
# setsar=1 memastikan pixel aspect ratio kotak/normal, biar tidak ada distorsi
# tambahan dari metadata SAR yang aneh.
build_video_filter() {
    echo "scale=${OUTPUT_WIDTH}:${OUTPUT_HEIGHT}:flags=fast_bilinear,setsar=1"
}

# Build argumen ffmpeg sebagai ARRAY (bukan string + eval).
# Ini fix untuk masalah #1: eval pada string rawan salah parsing quote/offset,
# array jauh lebih aman dan predictable karena tiap argumen dikirim persis
# apa adanya ke ffmpeg, tanpa proses parsing shell tambahan.
build_ffmpeg_args() {
    local has_audio="$1"
    local video_filter
    video_filter=$(build_video_filter)

    # Format input capture: display.screen+offset_x,offset_y
    # Contoh hasil: ":0.0+150,100"
    local capture_input="${DISPLAY_NUM}+${OFFSET_X},${OFFSET_Y}"

    FFMPEG_ARGS=(
        -f x11grab
        -framerate "$FRAMERATE"
        -video_size "${CAPTURE_WIDTH}x${CAPTURE_HEIGHT}"
        -i "$capture_input"
    )

    if [ "$has_audio" = "true" ]; then
        FFMPEG_ARGS+=(-stream_loop -1 -i "$AUDIO_FILE")
        FFMPEG_ARGS+=(-map 0:v -map 1:a)
    else
        FFMPEG_ARGS+=(-map 0:v -an)
    fi

    FFMPEG_ARGS+=(
        -c:v libx264
        -preset ultrafast
        -tune zerolatency
        -b:v "$BITRATE"
        -maxrate "$MAXRATE"
        -bufsize "$BUFSIZE"
        -vf "$video_filter"
        -pix_fmt yuv420p
        -threads 1
    )

    if [ "$has_audio" = "true" ]; then
        FFMPEG_ARGS+=(
            -c:a aac
            -b:a "$AUDIO_BITRATE"
            -ar "$AUDIO_SAMPLERATE"
        )
    fi

    FFMPEG_ARGS+=(-f flv "$RTMP_URL")
}

run_ffmpeg() {
    local has_audio="false"

    if [ -f "$AUDIO_FILE" ]; then
        has_audio="true"
    fi

    log_message "========================================"
    log_message "Starting stream with:"
    log_message "  - Display: $DISPLAY_NUM"
    log_message "  - Capture: ${CAPTURE_WIDTH}x${CAPTURE_HEIGHT} at offset (${OFFSET_X},${OFFSET_Y})"
    log_message "  - Output resolution: ${OUTPUT_WIDTH}x${OUTPUT_HEIGHT}"
    log_message "  - Audio: $( [ "$has_audio" = "true" ] && echo "Enabled" || echo "Disabled" )"
    log_message "========================================"

    build_ffmpeg_args "$has_audio"

    log_message "Command: ffmpeg ${FFMPEG_ARGS[*]}"

    ffmpeg "${FFMPEG_ARGS[@]}"

    local exit_code=$?
    if [ $exit_code -ne 0 ]; then
        log_error "FFmpeg stopped with error code: $exit_code"
        return $exit_code
    else
        log_message "FFmpeg stopped normally"
        return 0
    fi
}

# ============================================================================
# FUNGSI MANAJEMEN STREAM
# ============================================================================

kill_existing_streams() {
    local pids=$(pgrep -f "ffmpeg.*$RTMP_URL")
    if [ -n "$pids" ]; then
        log_message "Killing existing ffmpeg processes: $pids"
        kill -9 $pids 2>/dev/null
        sleep 2
    fi
}

cleanup() {
    log_message "Received exit signal. Cleaning up..."
    kill_existing_streams
    xhost -SI:localuser:$(whoami) 2>/dev/null
    log_message "Cleanup complete. Exiting."
    exit 0
}

trap cleanup SIGINT SIGTERM

# ============================================================================
# FUNGSI MAIN
# ============================================================================

main() {
    log_message "========================================="
    log_message "Starting YouTube Live Stream Script v4.0"
    log_message "========================================="

    check_dependencies
    detect_display

    if ! detect_session_type; then
        log_message "WARNING: Running on Wayland. Trying X11 capture anyway..."
        log_message "If this fails, please switch to X11 session"
    fi

    if ! check_display_access; then
        log_error "Cannot access display. Exiting."
        exit 1
    fi

    check_audio_file
    kill_existing_streams

    log_message "========================================="
    log_message "Stream URL: $RTMP_URL"
    log_message "Capture Area: ${CAPTURE_WIDTH}x${CAPTURE_HEIGHT} at offset (${OFFSET_X},${OFFSET_Y})"
    log_message "Output Resolution: ${OUTPUT_WIDTH}x${OUTPUT_HEIGHT}"
    log_message "========================================="
    log_message "To stop streaming: Press Ctrl+C"
    log_message "========================================="

    local restart_count=0
    while true; do
        ((restart_count++))
        log_message "Stream attempt #$restart_count"

        run_ffmpeg

        log_message "Stream stopped. Restarting in 10 seconds..."
        sleep 10
    done
}

# ============================================================================
# EKSEKUSI
# ============================================================================

main "$@"
Post Views: 20

Kategori

  • 3D Max
  • Adobe Animate
  • Android
  • c#
  • Cordova
  • HTML5, CSS & JavaScript
  • iOS
  • Lain-lain
  • Photoshop
  • PHP
  • Python
  • Roblox
  • Tak Berkategori
  • Unity
  • WordPress
ciihuy2020
© 2026 Om Puter | Powered by Superbs Personal Blog theme