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

128 lines
6.9 KiB
Bash
Raw Permalink 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
# =============================================================================
# 把环境改造成"项目内自包含"——Python、第三方库、模型全放进项目目录,
# 之后整个项目文件夹可以【随便放到任意路径、任意用户】,不再依赖 /root 或固定路径。
#
# 在【能联网】的机器上跑一次(已 setup_server.sh 装好 .venv 后):
# bash make_portable.sh # 默认 ffmpeg 用 GPU 版(BtbN,含 cuda/nvdec,也兼容 CPU)
# # 需运行机 glibc>=2.28;GPU 机器自动硬解、CPU 机器软解
# FFMPEG_VARIANT=cpu bash make_portable.sh # ffmpeg 用 CPU 版(johnvansickle,全静态,老 glibc 也能跑)
# 之后:
# bash pack.sh # 打包(此时只需打项目目录,已自包含)
# # 离线机解压到【任意路径】即可: source activate.sh && python main.py ...
# =============================================================================
set -euo pipefail
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PROJ="$(pwd)"
PIP_MIRROR="${PIP_MIRROR:-https://repo.huaweicloud.com/repository/pypi/simple}"
log() { echo -e "\n\033[1;32m==> $*\033[0m"; }
warn() { echo -e "\033[1;33m[警告] $*\033[0m"; }
have() { command -v "$1" >/dev/null 2>&1; }
[ -d .venv ] || { echo "没有 .venv,请先 bash setup_server.sh"; exit 1; }
# shellcheck disable=SC1091
source .venv/bin/activate
have uv || { echo "需要 uv(本机改造用,离线机不需要)"; exit 1; }
# 0) 确保 bin/ffmpeg 可用。两种变体(用环境变量 FFMPEG_VARIANT 选):
# cpu (默认) = johnvansickle 静态版,含 dav1d(CPU 软解 AV1),兼容老 glibc,哪都能跑。
# gpu = BtbN 版,含 cuda/nvdec(GPU 硬解),给 Ampere+ 卡的机器用;需较新 glibc(>=2.28)。
# 用法: FFMPEG_VARIANT=gpu bash make_portable.sh
# 默认下 GPU 版(含 cuda/nvdec,也兼容 CPU 软解)。要纯 CPU 版用 FFMPEG_VARIANT=cpu。
FFMPEG_VARIANT="${FFMPEG_VARIANT:-gpu}"
FF_NEED=1
if [ "$FFMPEG_VARIANT" = "gpu" ]; then
FF_URL="${FFMPEG_GPU_URL:-https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz}"
# GPU 版(BtbN)要 glibc>=2.28 才能运行;本机若太老仅警告(可能在给别的新机器打包)
# 注意:管道里 head 会让前面命令 SIGPIPE,配合 set -e/pipefail 会误退出,故加 || true
_glibc="$(ldd --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+' | head -1 || true)"
if [ -n "$_glibc" ] && [ "$(printf '%s\n2.28\n' "$_glibc" | sort -V | head -1)" != "2.28" ]; then
warn "本机 glibc=$_glibc < 2.28,GPU 版 ffmpeg 在本机跑不了(GLIBC_2.28 not found)。"
warn "仅当打包给 glibc>=2.28 的机器时才用 GPU 版;本机自用请: FFMPEG_VARIANT=cpu bash make_portable.sh"
fi
# 用 grep(不带 -q)读完整输出再判,避免 grep -q 提前关管道触发 SIGPIPE+pipefail 误判
if [ -x "$PROJ/bin/ffmpeg" ] && "$PROJ/bin/ffmpeg" -hide_banner -hwaccels 2>/dev/null | grep -i cuda >/dev/null; then
log "[0/4] bin/ffmpeg 已是 GPU 版(支持 cuda),跳过"; FF_NEED=0
else
log "[0/4] 下载 GPU 版 ffmpeg (BtbN,含 cuda/nvdec) → ./bin"
fi
else
FF_URL="${FFMPEG_STATIC_URL:-https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz}"
if [ -x "$PROJ/bin/ffmpeg" ] && "$PROJ/bin/ffmpeg" -hide_banner -decoders 2>/dev/null | grep -iE 'dav1d|av1' >/dev/null; then
log "[0/4] bin/ffmpeg 已支持 AV1(CPU 版),跳过"; FF_NEED=0
else
log "[0/4] 下载 CPU 版 ffmpeg (johnvansickle,含 dav1d) → ./bin"
fi
fi
if [ "$FF_NEED" -eq 1 ]; then
mkdir -p "$PROJ/bin"; tmp="$(mktemp -d)"
if have curl; then curl -L "$FF_URL" -o "$tmp/ff.tar.xz"; else wget -O "$tmp/ff.tar.xz" "$FF_URL"; fi
tar xf "$tmp/ff.tar.xz" -C "$tmp"
# johnvansickle 在 <dir>/ffmpeg,BtbN 在 <dir>/bin/ffmpeg —— 直接全树找,两种都兼容
ffb="$(find "$tmp" -type f -name ffmpeg | head -1)"
ffp="$(find "$tmp" -type f -name ffprobe | head -1)"
if [ -n "$ffb" ] && [ -n "$ffp" ]; then
cp "$ffb" "$ffp" "$PROJ/bin/" && chmod +x "$PROJ/bin/ffmpeg" "$PROJ/bin/ffprobe"
log "ffmpeg/ffprobe 已装到 ./bin($FFMPEG_VARIANT 版)"
else
echo "解压后没找到 ffmpeg/ffprobe,请手动放入 ./bin"; exit 1
fi
rm -rf "$tmp"
fi
# 1) 把 uv 的独立 Python 复制进项目 ./python(python-build-standalone 本身可重定位)
log "[1/4] 复制独立 Python 到项目内 ./python"
PYSRC="$(dirname "$(dirname "$(readlink -f .venv/bin/python)")")"
echo " 源: $PYSRC"
rm -rf "$PROJ/python"
cp -a "$PYSRC" "$PROJ/python"
PYBIN="$PROJ/python/bin/python3"
[ -x "$PYBIN" ] || PYBIN="$(ls "$PROJ"/python/bin/python3.* | head -1)"
echo " 项目内 Python: $PYBIN"
# 去掉 uv 的"外部管理(EXTERNALLY-MANAGED)"标记,否则 uv 拒绝往这个 Python 里装包
rm -f "$PROJ"/python/lib/python3.*/EXTERNALLY-MANAGED
# 2) 把依赖装进这个项目内 Python(脱离 .venv,用 headless opencv)
log "[2/4] 安装依赖到项目内 Python"
uv pip uninstall --python "$PYBIN" --break-system-packages opencv-python 2>/dev/null || true
uv pip install --python "$PYBIN" --break-system-packages -r requirements.txt --index-url "$PIP_MIRROR" \
--only-binary numpy,scipy,scikit-learn,opencv-python-headless,Pillow,av \
|| uv pip install --python "$PYBIN" --break-system-packages -r requirements.txt --index-url "$PIP_MIRROR"
# WHISPER_GPU=1:把 CUDA 库(cublas/cudnn)也装进项目 Python,这样目标机【无需系统 CUDA】
# 也能让 whisper 跑 GPU(ctranslate2 从 venv 里找库;activate.sh 会把它加进 LD_LIBRARY_PATH)。
if [ "${WHISPER_GPU:-0}" = "1" ]; then
log " 额外装 CUDA 库(nvidia-cublas/cudnn-cu12)给 whisper GPU 用"
uv pip install --python "$PYBIN" --break-system-packages \
nvidia-cublas-cu12 nvidia-cudnn-cu12 --index-url "$PIP_MIRROR" \
|| warn "CUDA 库安装失败;目标机若无系统 CUDA,whisper 将回退 CPU"
fi
# 3) 把 whisper 模型缓存搬进项目 ./models(用 HF_HOME 指过去)
log "[3/4] 预下载 whisper 模型到项目内 ./models"
MODEL="$(python -c "import json;print(json.load(open('config/config.json'))['audio'].get('model_size','small'))")"
mkdir -p "$PROJ/models"
HF_HOME="$PROJ/models" HF_HUB_OFFLINE=0 TRANSFORMERS_OFFLINE=0 \
HF_ENDPOINT="${HF_ENDPOINT:-https://hf-mirror.com}" \
HF_HUB_DISABLE_XET=1 \
"$PYBIN" -c "from faster_whisper import WhisperModel; WhisperModel('$MODEL',device='cpu',compute_type='int8'); print('模型已放进 ./models')"
# 4) 删掉旧 .venv(不再需要,已被项目内 Python 取代)
log "[4/4] 清理旧 .venv"
deactivate 2>/dev/null || true
rm -rf "$PROJ/.venv"
cat <<EOF
完成 ✅ 现在项目已自包含,可随便放任意路径/用户:
./python/ 独立 Python + 所有第三方库
./bin/ ffmpeg / ffprobe
./models/ whisper 模型
coscli-linux-amd64
验证: source activate.sh && python -c "import cv2,faster_whisper;print('OK')"
打包: bash pack.sh
EOF