最新 FFmpeg 安装脚本:Linux x86_64

bash 复制代码
# 安装所需依赖
  sudo apt-get update
  sudo apt-get install -y ca-certificates curl python3 tar xz-utils coreutils

  # 先检查下载、校验和实际运行是否正常
  bash install-ffmpeg.sh --check-only

  # 检查通过后安装
  bash install-ffmpeg.sh

install-ffmpeg.sh

bash 复制代码
#!/usr/bin/env bash
# FFmpeg 安装脚本:Linux x86_64,需要 Bash。
# 来源:BtbN/FFmpeg-Builds;默认选择其提供的最高稳定分支。
# 稳定分支构建可能包含正式版本之后的修复,不由 apt 管理。

set -Eeuo pipefail
umask 022

readonly RELEASE_API="https://api.github.com/repos/BtbN/FFmpeg-Builds/releases/latest"
readonly INSTALL_ROOT="/opt/ffmpeg"
readonly COMMAND_DIR="/usr/local/bin"

# 运行状态,分别由参数解析、下载选择和权限检查填充。
branch=""
check_only=false
work=""
archive=""
source_dir=""
selected=()
priv=()

# ── 通用工具 ────────────────────────────────────────────────────────

log() {
    printf '[FFmpeg] %s\n' "$*"
}

fail() {
    printf '[错误] %s\n' "$1" >&2
    exit "${2:-1}"
}

show_help() {
    cat <<'HELP'
用法:bash install-ffmpeg.sh [选项]

选项:
  --branch MAJOR.MINOR   指定稳定分支,例如 9.0
  --check-only          仅下载、校验和测试,不安装
  -h, --help            显示帮助

示例:
  bash install-ffmpeg.sh
  bash install-ffmpeg.sh --branch 9.0
  bash install-ffmpeg.sh --check-only

安装目录:/opt/ffmpeg/;命令入口:/usr/local/bin/
重新运行即可更新,旧版本目录会保留。请使用 bash,不要使用 sh。
HELP
}

cleanup() {
    if [[ -n "$work" && -d "$work" ]]; then
        rm -rf -- "$work"
    fi
}

fetch() {
    local url="$1"
    local output_path="$2"

    curl --fail --location --silent --show-error \
        --retry 3 \
        --connect-timeout 20 \
        --max-time 600 \
        "$url" --output "$output_path"
}

# ── 参数和环境检查 ──────────────────────────────────────────────────

parse_arguments() {
    while (($#)); do
        case "$1" in
            --branch)
                if [[ $# -lt 2 || ! "$2" =~ ^[0-9]+\.[0-9]+$ ]]; then
                    fail '--branch 需要 MAJOR.MINOR 格式的参数,例如 9.0' 2
                fi
                branch="$2"
                shift 2
                ;;
            --check-only)
                check_only=true
                shift
                ;;
            -h|--help)
                show_help
                exit 0
                ;;
            *)
                fail "未知参数:$1(使用 --help 查看帮助)" 2
                ;;
        esac
    done
}

check_environment() {
    local tool

    if [[ $(uname -s) != Linux || $(uname -m) != x86_64 ]]; then
        fail '目前仅支持 Linux x86_64。'
    fi

    for tool in curl python3 tar xz sha256sum mktemp; do
        if ! command -v "$tool" >/dev/null; then
            fail "缺少依赖:$tool"
        fi
    done
}

# ── 选择构建和下载 ──────────────────────────────────────────────────

select_build() {
    log "查询稳定分支构建......"
    fetch "$RELEASE_API" "$work/release.json"
    python3 - "$work/release.json" "$branch" > "$work/selected" <<'PY'
import json
import re
import sys

release = json.load(open(sys.argv[1]))
choices = []
for asset in release['assets']:
    match = re.fullmatch(r'ffmpeg-n(\d+)\.(\d+)-latest-linux64-gpl-\1\.\2.tar.xz', asset['name'])
    if match and (not sys.argv[2] or '.'.join(match.groups()) == sys.argv[2]):
        choices.append((tuple(map(int, match.groups())), asset))
if not choices:
    sys.exit('No matching stable build found in BtbN release assets.')
asset = max(choices, key=lambda item: item[0])[1]
checksum = next(a for a in release['assets'] if a['name'] == 'checksums.sha256')
for item in (asset, checksum):
    url = item['browser_download_url']
    if not url.startswith('https://github.com/BtbN/FFmpeg-Builds/releases/download/'):
        sys.exit('Unexpected download URL')
print(asset['name'])
print(asset['browser_download_url'])
print(checksum['browser_download_url'])
PY
    mapfile -t selected < "$work/selected"
    archive=${selected[0]}
    log "已选择:$archive"
}

download_build() {
    log "下载安装包及校验文件......"
    fetch "${selected[2]}" "$work/checksums.sha256"
    fetch "${selected[1]}" "$work/$archive"
}

# ── 完整性检查和安全解压 ──────────────────────────────────────────────

verify_checksum() {
    log "校验 SHA-256......"
    python3 - "$work/checksums.sha256" "$archive" > "$work/verify.sha256" <<'PY'
import re
import sys

matches = []
for line in open(sys.argv[1]):
    parts = line.split()
    if len(parts) == 2 and parts[1].lstrip('*') == sys.argv[2]:
        if not re.fullmatch('[0-9a-fA-F]{64}', parts[0]):
            sys.exit('Invalid SHA-256 checksum')
        matches.append(parts[0] + '  ' + sys.argv[2])
if len(matches) != 1:
    sys.exit('Expected exactly one archive checksum')
print(matches[0])
PY
    (
        cd "$work"
        sha256sum --check --strict verify.sha256
    )
}

extract_build() {
    # 解压前拒绝路径穿越、链接和设备文件。
    python3 - "$work/$archive" "${archive%.tar.xz}" <<'PY'
import pathlib
import sys
import tarfile

with tarfile.open(sys.argv[1]) as archive:
    for member in archive:
        path = pathlib.PurePosixPath(member.name)
        if (path.is_absolute() or '..' in path.parts or not path.parts
                or path.parts[0] != sys.argv[2]
                or not (member.isfile() or member.isdir())):
            sys.exit('Unsafe archive member: ' + member.name)
PY
    tar --extract --xz \
        --file "$work/$archive" \
        --directory "$work" \
        --no-same-owner \
        --no-same-permissions
    source_dir="$work/${archive%.tar.xz}"
}

# ── 实际功能测试 ────────────────────────────────────────────────────

verify_build() {
    log "测试编码、媒体探测和解码......"
    "$source_dir/bin/ffmpeg" -version > "$work/version.txt"
    head -n 1 "$work/version.txt"
    "$source_dir/bin/ffmpeg" -nostdin -hide_banner -loglevel error \
        -f lavfi -i testsrc2=size=128x96:rate=10 \
        -f lavfi -i sine=frequency=1000:sample_rate=48000 \
        -t 1 -threads 1 \
        -c:v libx264 -pix_fmt yuv420p \
        -c:a aac "$work/test.mp4"

    "$source_dir/bin/ffprobe" -v error \
        -show_entries stream=codec_name \
        -of csv=p=0 "$work/test.mp4"

    "$source_dir/bin/ffmpeg" -nostdin -v error -i "$work/test.mp4" -f null -
    log '校验、编码、媒体探测和解码测试通过。'
}

# ── 安装和命令入口 ──────────────────────────────────────────────────

prepare_privileges() {
    if ((EUID != 0)); then
        if ! command -v sudo >/dev/null; then
            fail '安装需要 root 权限或 sudo。'
        fi
        sudo -v
        priv=(sudo)
    fi
}

check_existing_commands() {
    local name target

    # 只替换指向本安装目录的链接,保护其他方式安装的程序。
    for name in ffmpeg ffprobe; do
        target="$COMMAND_DIR/$name"
        if [[ ! -e "$target" && ! -L "$target" ]]; then
            continue
        fi
        if [[ ! -L "$target" || $(readlink "$target") != "$INSTALL_ROOT/"*/bin/"$name" ]]; then
            fail "拒绝覆盖其他安装:$target"
        fi
    done
}

install_build() {
    local destination name

    prepare_privileges
    check_existing_commands

    # 独立目录保留旧版本;复制成功后再更新命令链接。
    "${priv[@]}" install -d -m 755 "$INSTALL_ROOT" "$COMMAND_DIR"
    destination=$("${priv[@]}" mktemp -d "$INSTALL_ROOT/build.XXXXXXXX")
    "${priv[@]}" cp -a "$source_dir/." "$destination/"
    "${priv[@]}" chown -R root:root "$destination"
    "${priv[@]}" chmod 755 "$destination"

    for name in ffmpeg ffprobe; do
        "${priv[@]}" ln -sfnT "$destination/bin/$name" "$COMMAND_DIR/$name"
        "$COMMAND_DIR/$name" -version > "$work/$name-version.txt"
        head -n 1 "$work/$name-version.txt"
    done

    log "安装完成:$destination"
    log "命令入口:$COMMAND_DIR/ffmpeg 和 $COMMAND_DIR/ffprobe"
    log '再次运行本脚本即可更新;旧版本目录已保留。'
}

# ── 主流程 ──────────────────────────────────────────────────────────

main() {
    parse_arguments "$@"
    check_environment

    work=$(mktemp -d -t ffmpeg-install.XXXXXXXX)
    trap cleanup EXIT

    select_build
    download_build
    verify_checksum
    extract_build
    verify_build

    if [[ "$check_only" == true ]]; then
        log '仅检查模式:未修改现有安装。'
        return
    fi

    install_build
}

main "$@"
相关推荐
OH_TPC1 小时前
【鸿蒙优选三方库】@ohos/ffmpeg-kit:把 FFmpeg 的全部能量带进鸿蒙
华为·ffmpeg·harmonyos·鸿蒙
2601_967980111 小时前
Python之wxPython模块的安装
linux·windows·python·wxpython·gui设计·1024程序员节
全栈技术负责人1 小时前
AI 自动化单元测试技术方案
运维·单元测试·自动化
Shadow(⊙o⊙)1 小时前
仿muduo实现高并发服务器——EventLoop.hpp初步设计(未纳入超时管理模块)
运维·服务器
叼烟扛炮2 小时前
Linux 学习笔记(二十二):深入 Ext 文件系统(上)—— 磁盘、块与神秘的 inode
linux·笔记·学习
nlp研究牲2 小时前
Linux 常用命令速查手册:英文全称、中文释义、巧记方法与实战示例
linux·运维·php
小张成长计划..2 小时前
【Linux】28:封装线程,锁,条件变量,日志
linux
众壹新能源科技2 小时前
逆变器半夜停机等备件48小时少发10万度电,备件安全库存怎么算
大数据·运维·安全
广州智造2 小时前
Radioss破坏模型FAILJOHNSON (Johnson-cook) 和 FAILTAB1
运维·数据库·自动化·仿真·建模·后处理·前处理