#!/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 在 /ffmpeg,BtbN 在 /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 <