Files
ocr_data/scripts/setup_server.sh
2026-09-28 16:09:31 +08:00

245 lines
11 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# =============================================================================
# 视频字幕提取系统 — 服务器一键环境配置脚本
#
# 安装/配置:gcc 编译工具链、ffmpeg(含 AV1/dav1d 解码)、uv、Python 虚拟环境、
# 项目依赖,并处理常见环境坑(opencv libGL、UTF-8 locale、HF/pip 镜像、
# OMP 线程、文件句柄上限)。
#
# 用法(在项目根目录):
# bash setup_server.sh
# 跑完后:
# source activate.sh # 激活环境(设置 venv + 各种环境变量)
# python main.py --source-list ./text2 --max-workers 12
#
# 幂等:可重复运行,已装的会跳过。
# =============================================================================
set -euo pipefail
# ----------------------------- 可调参数 --------------------------------------
PYTHON_VERSION="${PYTHON_VERSION:-3.11}"
PIP_MIRROR="${PIP_MIRROR:-https://repo.huaweicloud.com/repository/pypi/simple}"
HF_ENDPOINT_URL="${HF_ENDPOINT_URL:-https://hf-mirror.com}"
FFMPEG_STATIC_URL="${FFMPEG_STATIC_URL:-https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz}"
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VENV_DIR="$PROJECT_DIR/.venv"
cd "$PROJECT_DIR"
# 让本脚本(及自检)优先用项目 bin/ 里的 ffmpeg(我们装的静态版)
export PATH="$PROJECT_DIR/bin:$PATH"
# ----------------------------- 工具函数 --------------------------------------
log() { echo -e "\n\033[1;32m==> $*\033[0m"; }
warn() { echo -e "\033[1;33m[警告] $*\033[0m"; }
err() { echo -e "\033[1;31m[错误] $*\033[0m" >&2; }
have() { command -v "$1" >/dev/null 2>&1; }
# root 则不用 sudo,否则用 sudo
if [ "$(id -u)" -eq 0 ]; then SUDO=""; else SUDO="sudo"; fi
# 检测包管理器
PKG=""
if have dnf; then PKG="dnf"
elif have yum; then PKG="yum"
elif have apt-get; then PKG="apt"
else warn "未识别的包管理器,跳过系统包安装(请确保已手动装好 gcc/ffmpeg 等)"; fi
# 解压后 coscli/bin 里的二进制常丢失 +x,先无条件加执行权限(放最前,确保一定执行)
for b in coscli-linux-amd64 coscli bin/ffmpeg bin/ffprobe; do
[ -f "$PROJECT_DIR/$b" ] && chmod +x "$PROJECT_DIR/$b" 2>/dev/null || true
done
log "已给 coscli / bin 二进制加执行权限"
# =============================================================================
log "[1/7] 安装系统依赖(gcc 工具链 + opencv/运行库)"
# =============================================================================
# 关键环境坑:
# - opencv-python 在无图形界面的服务器上 import 会报 libGL.so.1 缺失 → 装 mesa-libGL/libgl1
# - 还需要 glib2(libgthread-2.0) 否则 opencv 也可能报错
# - gcc/gcc-c++/make:少数包没有 wheel 时要本地编译
case "$PKG" in
dnf|yum)
# openblas-devel:万一 scipy/numpy 走源码编译时需要(优先还是用 wheel)
$SUDO $PKG install -y gcc gcc-c++ gcc-gfortran make git tar xz which \
mesa-libGL glib2 openblas-devel || warn "部分系统包安装失败,继续"
;;
apt)
$SUDO apt-get update -y || true
$SUDO apt-get install -y build-essential gfortran git tar xz-utils curl \
libgl1 libglib2.0-0 libopenblas-dev || warn "部分系统包安装失败,继续"
;;
esac
# =============================================================================
log "[2/7] 安装 ffmpeg(含 AV1/dav1d 解码) → 项目目录 bin/"
# =============================================================================
# 直接下载静态构建(johnvansickle,自带 dav1d)到项目的 bin/,代码会自动优先用它
# (config_utils 启动时把 bin/ 提到 PATH 最前)。不依赖系统 ffmpeg,也不用 sudo。
# 注意:CPU 版(无 NVDEC);要 GPU 硬解需另装 cuda 版 ffmpeg。
FFMPEG_DIR="$PROJECT_DIR/bin"
LOCAL_FFMPEG="$FFMPEG_DIR/ffmpeg"
need_ffmpeg=1
if [ -x "$LOCAL_FFMPEG" ] && "$LOCAL_FFMPEG" -hide_banner -decoders 2>/dev/null | grep -qiE 'dav1d|av1'; then
log "项目自带 ffmpeg 已存在且支持 AV1: $LOCAL_FFMPEG,跳过下载"
need_ffmpeg=0
fi
if [ "$need_ffmpeg" -eq 1 ]; then
if have curl || have wget; then
mkdir -p "$FFMPEG_DIR"
tmp="$(mktemp -d)"
log "下载 ffmpeg 静态包到 $FFMPEG_DIR ..."
if have curl; then curl -L "$FFMPEG_STATIC_URL" -o "$tmp/ff.tar.xz"
else wget -O "$tmp/ff.tar.xz" "$FFMPEG_STATIC_URL"; fi
tar -xf "$tmp/ff.tar.xz" -C "$tmp"
ffdir="$(find "$tmp" -maxdepth 1 -type d -name 'ffmpeg-*' | head -1)"
if [ -n "$ffdir" ]; then
cp "$ffdir/ffmpeg" "$ffdir/ffprobe" "$FFMPEG_DIR/"
chmod +x "$FFMPEG_DIR/ffmpeg" "$FFMPEG_DIR/ffprobe"
log "ffmpeg/ffprobe 已装到 $FFMPEG_DIR"
else
err "解压后未找到 ffmpeg 目录,请手动安装 ffmpeg"
fi
rm -rf "$tmp"
else
err "没有 curl/wget,无法下载 ffmpeg,请手动安装"
fi
fi
# =============================================================================
log "[3/7] 安装 uv"
# =============================================================================
if ! have uv; then
curl -LsSf https://astral.sh/uv/install.sh | sh
# uv 默认装到 ~/.local/bin 或 ~/.cargo/bin
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
fi
have uv || { err "uv 安装失败,请检查网络"; exit 1; }
log "uv 版本: $(uv --version)"
# =============================================================================
log "[4/7] 用 uv 安装 Python ${PYTHON_VERSION}"
# =============================================================================
uv python install "$PYTHON_VERSION" || warn "uv python install 失败(可能已装)"
# =============================================================================
log "[5/7] 创建虚拟环境并安装依赖"
# =============================================================================
# 优先用 pyproject.toml + uv.lock(规范、可复现);没有则回退 requirements.txt。
# pyproject 的 [tool.uv] 已配置:禁止 av/numpy/sklearn 本地编译 + 清华镜像。
# 用 uv venv + uv pip install(现代解析器)。--only-binary 让它自动挑有 wheel 的版本,
# 避免某些受限镜像把新版本只给源码包导致本地编译/报错(不像 uv sync 的 no-build 会硬失败)。
[ -d "$VENV_DIR" ] || uv venv --python "$PYTHON_VERSION" "$VENV_DIR"
# shellcheck disable=SC1091
source "$VENV_DIR/bin/activate"
if [ -f requirements.txt ]; then
uv pip install -r requirements.txt --index-url "$PIP_MIRROR" \
--only-binary numpy,scipy,scikit-learn,opencv-python-headless,Pillow,av \
|| uv pip install -r requirements.txt --index-url "$PIP_MIRROR"
else
warn "未找到 requirements.txt,跳过依赖安装"
fi
# 激活 venv 供后续自检使用
# shellcheck disable=SC1091
[ -f "$VENV_DIR/bin/activate" ] && source "$VENV_DIR/bin/activate"
log "Python: $(python --version 2>&1) 位置: $(command -v python)"
# =============================================================================
log "[6/7] 写出环境变量脚本 activate.sh"
# =============================================================================
# 常见环境坑统一在这里处理,跑项目前 source 一下即可
# 用单引号 heredoc 让脚本体保持字面(不在生成时展开),实现"自动定位所在目录",
# 这样整个项目可以搬到任意路径,activate.sh 仍能找到自己的 .venv / bin。
cat > "$PROJECT_DIR/activate.sh" <<'EOF'
#!/usr/bin/env bash
# 跑项目前先: source activate.sh —— 自动定位项目目录,项目可放任意路径
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Python:优先项目内自包含的 ./python(make_portable.sh 产物,可随便放);否则用 .venv
if [ -x "$HERE/python/bin/python3" ]; then
export PATH="$HERE/bin:$HERE/python/bin:$PATH"
else
export VIRTUAL_ENV="$HERE/.venv"
export PATH="$HERE/bin:$VIRTUAL_ENV/bin:$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
fi
# UTF-8:项目有中文文件名(数据.jsonl)。优先选系统存在的 UTF-8 locale,避免 setlocale 警告;
# 并强制 Python 用 UTF-8(即使系统没有 UTF-8 locale 也能正确处理中文文件名)。
if locale -a 2>/dev/null | grep -qiE '^C\.(utf-?8|UTF-?8)$'; then
export LANG=C.UTF-8 LC_ALL=C.UTF-8
elif locale -a 2>/dev/null | grep -qiE 'en_US\.(utf-?8|UTF-?8)'; then
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
fi
export PYTHONUTF8=1
export PYTHONIOENCODING=utf-8
# HuggingFace 镜像(联网下模型时用)
export HF_ENDPOINT=__HF_ENDPOINT__
# 模型缓存:项目内有 ./models 就用它(自包含包),否则用默认 ~/.cache/huggingface
[ -d "$HERE/models" ] && export HF_HOME="$HERE/models"
# 只在模型【确实已缓存】时才强制离线,否则允许联网下载(避免"没模型又禁网"的死锁)
if ls "${HF_HOME:-$HOME/.cache/huggingface}"/hub/models--*faster-whisper* >/dev/null 2>&1; then
export HF_HUB_OFFLINE=1
export TRANSFORMERS_OFFLINE=1
fi
# 若项目自带 CUDA 库(WHISPER_GPU=1 打包),加进 LD_LIBRARY_PATH,让 ctranslate2 找到(whisper GPU)
_nvlibs="$(ls -d "$HERE"/python/lib/python*/site-packages/nvidia/*/lib 2>/dev/null | tr '\n' ':')"
[ -n "$_nvlibs" ] && export LD_LIBRARY_PATH="${_nvlibs}${LD_LIBRARY_PATH:-}"
# 限制数值库线程:防止 whisper/numpy/openblas 每个都抢满核(多视频并发时互相踩)
export OMP_NUM_THREADS=2
export OPENBLAS_NUM_THREADS=2
export MKL_NUM_THREADS=2
echo "环境已激活: $(python --version 2>&1) | $HERE"
EOF
sed -i "s|__HF_ENDPOINT__|$HF_ENDPOINT_URL|" "$PROJECT_DIR/activate.sh"
chmod +x "$PROJECT_DIR/activate.sh"
# 提高文件句柄上限(高并发 + 海量小帧文件),当前会话生效;永久需改 /etc/security/limits.conf
ulimit -n 65535 2>/dev/null || warn "ulimit 提升失败(非致命),如需永久生效请改 /etc/security/limits.conf"
# =============================================================================
log "[7/7] 环境自检"
# =============================================================================
ok=1
check() { if eval "$2" >/dev/null 2>&1; then echo " ✓ $1"; else echo " ✗ $1"; ok=0; fi; }
check "gcc" "gcc --version"
check "ffmpeg" "ffmpeg -version"
check "ffprobe" "ffprobe -version"
check "ffmpeg 支持 AV1" "ffmpeg -hide_banner -decoders | grep -iE 'dav1d|av1'"
check "uv" "uv --version"
check "python" "python --version"
check "import cv2" "python -c 'import cv2'"
check "import numpy" "python -c 'import numpy'"
check "import sklearn" "python -c 'import sklearn'"
check "import av(PyAV)" "python -c 'import av'"
check "import faster_whisper" "python -c 'import faster_whisper'"
check "import qcloud_cos" "python -c 'import qcloud_cos'"
# GPU 检测(可选,有则抽帧能硬解加速)
if have nvidia-smi && nvidia-smi >/dev/null 2>&1; then
echo " ✓ 检测到 NVIDIA GPU(抽帧可硬解加速)"
else
echo " · 未检测到 GPU(抽帧用 CPU,正常)"
fi
echo
if [ "$ok" -eq 1 ]; then
log "全部就绪 ✅"
else
warn "有项目未通过(见上面 ✗),请按提示排查"
fi
cat <<EOF
下一步:
source activate.sh
python main.py --source-list ./text2 --max-workers 12
EOF