| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- # 启用bash的扩展功能
- shopt -s extglob
- # 检查参数是否传入
- if [ $# -eq 0 ]; then
- echo "Error: Insufficient parameters"
- echo
- echo "Usage:"
- echo " ./run_train_and_cloneb.sh \"input_video\""
- echo
- echo "Examples:"
- echo " ./run_train_and_cloneb.sh \"path/to/your/video.mp4\""
- exit 1
- fi
- # 定义核心变量
- INPUT_VIDEO="$1"
- # 获取脚本所在目录
- SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/
- cd "$SCRIPT_DIR" || exit 1
- # 使用固定的预生成 avatar 目录(不生成时间戳)
- # 如果需要生成新的,取消下面的注释并注释掉固定名称
- AVATAR_ID="wav2lip256_avatar1"
- # TIMESTAMP=$(date +%Y%m%d_%H%M%S)
- # AVATAR_ID="avatar_${TIMESTAMP}"
- AVATAR_DIR="${SCRIPT_DIR}data/avatars/${AVATAR_ID}"
- # 不自动删除 avatar 目录(因为是预生成的)
- # cleanup_avatar_dir() {
- # if [ -n "${AVATAR_DIR:-}" ] && [ -d "${AVATAR_DIR}" ]; then
- # echo "Cleaning generated avatar directory: ${AVATAR_DIR}"
- # rm -rf -- "${AVATAR_DIR}"
- # fi
- # }
- # trap 'cleanup_avatar_dir' EXIT
- # 默认环境变量(可通过外部传入覆盖)
- : "${CUDA_VISIBLE_DEVICES:=4,5,7}"
- : "${AIOICE_PORT_MIN:=50000}"
- : "${AIOICE_PORT_MAX:=50010}"
- : "${AIOICE_BIND_IP:=192.168.22.9}"
- : "${WEBRTC_NAT_IP:=183.252.196.135}"
- # PyTorch 显存优化
- : "${PYTORCH_CUDA_ALLOC_CONF:=max_split_size_mb:512,expandable_segments:True}"
- # 禁用 TorchDynamo 编译(避免 VoxCPM2 兼容性问题)
- : "${TORCHDYNAMO_DISABLE:=1}"
- # RTMP 推流配置(使用 nginx-rtmp + FFmpeg)
- : "${ENABLE_RTMP:=true}" # 是否启用 RTMP 推流(默认启用)
- : "${RTMP_PORT:=1935}" # RTMP 服务端口
- : "${FFMPEG_PUSH:=true}" # 是否启动本机 FFmpeg 转发
- export CUDA_VISIBLE_DEVICES AIOICE_PORT_MIN AIOICE_PORT_MAX AIOICE_BIND_IP WEBRTC_NAT_IP
- export PYTORCH_CUDA_ALLOC_CONF TORCHDYNAMO_DISABLE
- echo "========================================"
- echo "Digital Human Generation Pipeline (Green Screen)"
- echo "========================================"
- echo
- echo "Input Video: ${INPUT_VIDEO}"
- echo "Avatar ID: ${AVATAR_ID}"
- echo "Timestamp: ${TIMESTAMP}"
- echo "Using env: CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES} AIOICE_BIND_IP=${AIOICE_BIND_IP} WEBRTC_NAT_IP=${WEBRTC_NAT_IP} AIOICE_PORT_MIN=${AIOICE_PORT_MIN} AIOICE_PORT_MAX=${AIOICE_PORT_MAX}"
- echo
- # 检查输入视频是否存在
- if [ ! -f "${INPUT_VIDEO}" ]; then
- echo "Error: Input video not found: ${INPUT_VIDEO}"
- exit 1
- fi
- echo "Current Directory: $(pwd)"
- echo
- # ========================================
- # Step 1: Convert video background to green screen
- # ========================================
- echo
- echo "[Step 1/3] Converting video background to green screen..."
- echo
- # 确保 back/video 目录存在
- mkdir -p "${SCRIPT_DIR}back/video"
- # 生成输出文件名
- INPUT_BASENAME=$(basename "${INPUT_VIDEO}" | sed 's/\.[^.]*$//')
- GREENSCREEN_VIDEO="${SCRIPT_DIR}back/video/${INPUT_BASENAME}_greenscreen.mp4"
- echo "Converting video to green screen..."
- echo " Input: ${INPUT_VIDEO}"
- echo " Output: ${GREENSCREEN_VIDEO}"
- echo
- # 运行绿幕转换脚本
- python back/video_to_greenscreen.py "${INPUT_VIDEO}" -o "${GREENSCREEN_VIDEO}"
- if [ $? -ne 0 ]; then
- echo "Error: Green screen conversion failed!"
- cd "${SCRIPT_DIR}" || exit 1
- exit 1
- fi
- # 检查输出文件是否存在
- if [ ! -f "${GREENSCREEN_VIDEO}" ]; then
- echo "Error: Green screen video not generated!"
- cd "${SCRIPT_DIR}" || exit 1
- exit 1
- fi
- echo
- echo "Green screen conversion completed!"
- echo "Generated Video: ${GREENSCREEN_VIDEO}"
- echo
- # ========================================
- # Step 2: Run genavatar.py for pre-processing
- # ========================================
- echo
- echo "[Step 2/3] Running genavatar.py for pre-processing..."
- echo
- # 进入wav2lip目录
- cd "${SCRIPT_DIR}wav2lip" || exit 1
- echo "Generating avatar data with:"
- echo " Video: ${GREENSCREEN_VIDEO}"
- echo " Avatar ID: ${AVATAR_ID}"
- echo " Image Size: 256"
- echo
- # 运行 genavatar.py
- python genavatar.py --video_path "${GREENSCREEN_VIDEO}" --img_size 256 --avatar_id "${AVATAR_ID}"
- if [ $? -ne 0 ]; then
- echo "Error: genavatar.py failed!"
- cd "${SCRIPT_DIR}" || exit 1
- exit 1
- fi
- cd "${SCRIPT_DIR}" || exit 1
- echo
- echo "Pre-processing completed!"
- echo "Avatar data saved to: data/avatars/${AVATAR_ID}"
- echo
- # ========================================
- # Step 3: 启动 VoxCPM2 API 服务 + 运行 app.py
- # ========================================
- echo
- echo "[Step 3/4] 检查并启动 VoxCPM2 API 服务..."
- echo
- # VoxCPM2 API 服务配置
- VOXCPM2_API_URL="http://localhost:6003"
- VOXCPM2_API_LOG="/tmp/voxcpm2_api.log"
- echo "检查 VoxCPM2 API 服务状态..."
- echo " API 地址:${VOXCPM2_API_URL}"
- # 检查 API 服务是否已在运行
- if curl -s ${VOXCPM2_API_URL}/health > /dev/null 2>&1; then
- echo "✅ VoxCPM2 API 服务已在运行"
- curl -s ${VOXCPM2_API_URL}/health | python -m json.tool 2>/dev/null || true
- else
- echo "🚀 VoxCPM2 API 服务未运行,正在启动..."
- echo
-
- # 检查模型目录是否存在
- if [ ! -d "/mnt/nvme1data/model/VoxCPM2" ]; then
- echo "❌ 错误:VoxCPM2 模型目录不存在:/mnt/nvme1data/model/VoxCPM2"
- echo "请先下载或复制模型文件到该目录"
- exit 1
- fi
-
- # 检查 API 脚本是否存在
- if [ ! -f "/mnt/nvme1data/model/voxcpm2_api.py" ]; then
- echo "❌ 错误:VoxCPM2 API 脚本不存在:/mnt/nvme1data/model/voxcpm2_api.py"
- exit 1
- fi
-
- # 在后台启动 API 服务
- cd /mnt/nvme1data/model
- nohup python voxcpm2_api.py > ${VOXCPM2_API_LOG} 2>&1 &
- API_PID=$!
- echo "✅ VoxCPM2 API 服务已启动"
- echo " PID: ${API_PID}"
- echo " 日志:${VOXCPM2_API_LOG}"
- echo
-
- # 等待服务启动(最多 120 秒)
- echo "等待模型加载(最多120秒)..."
- START_TIME=$(date +%s)
-
- for i in $(seq 1 120); do
- if curl -s ${VOXCPM2_API_URL}/health > /dev/null 2>&1; then
- END_TIME=$(date +%s)
- WAIT_TIME=$((END_TIME - START_TIME))
- echo "✅ VoxCPM2 API 服务已就绪(等待 ${WAIT_TIME} 秒)"
- echo
- # 显示服务信息
- curl -s ${VOXCPM2_API_URL}/health | python -m json.tool 2>/dev/null || true
- break
- fi
-
- # 每 10 秒显示一次进度
- if [ $((i % 10)) -eq 0 ]; then
- echo " 已等待 ${i} 秒..."
- fi
-
- if [ $i -eq 120 ]; then
- echo
- echo "❌ VoxCPM2 API 服务启动超时(120秒)"
- echo
- echo "最后 50 行日志:"
- echo "========================================"
- tail -50 ${VOXCPM2_API_LOG}
- echo "========================================"
- echo
- echo "请检查:"
- echo " 1. 模型文件是否完整:ls -lh /mnt/nvme1data/model/VoxCPM2/"
- echo " 2. voxcpm 包是否安装:python -c 'import voxcpm'"
- echo " 3. GPU 是否可用:nvidia-smi"
- echo
- exit 1
- fi
- sleep 1
- done
-
- cd "${SCRIPT_DIR}"
- fi
- echo
- echo "========================================"
- echo "[Step 4/4] 启动数字人服务(API 调用模式)"
- echo "========================================"
- echo
- echo "Starting digital human server..."
- echo "Avatar ID: ${AVATAR_ID}"
- echo "TTS: voxcpm2api (VoxCPM2 API 调用模式 - 显存隔离)"
- echo "API URL: ${VOXCPM2_API_URL}"
- echo "Model: /mnt/nvme1data/model/VoxCPM2 (独立进程)"
- echo "Transport: ${TRANSPORT_MODE}"
- if [ "${ENABLE_RTMP}" = "true" ]; then
- echo "RTMP Push URL: ${PUSH_URL}"
- echo "Jump Server Command:"
- echo " ffmpeg -i rtmp://127.0.0.1:1935/live/${AVATAR_ID} -c copy -f flv \"rtmp://livepush.ailien.shop/alien/people?auth_key=...\""
- fi
- echo
- echo "优势:"
- echo " ✅ 模型显存独立(~8GB),不影响数字人渲染"
- echo " ✅ 多个数字人实例可共用同一 API 服务"
- echo " ✅ API 崩溃不影响数字人主进程"
- echo " ✅ 更新模型只需重启 API,无需停数字人"
- echo
- echo "访问地址:"
- echo " Web 界面:http://localhost:7868/dashboard.html"
- echo " API 健康检查:${VOXCPM2_API_URL}/health"
- echo
- echo "Press Ctrl+C to stop the server."
- echo
- # 检查并安装pyaudio
- echo "Checking pyaudio installation..."
- python -c "import pyaudio" 2>/dev/null || (echo "Installing pyaudio..." && pip install pyaudio)
- # ========================================
- # Step 3.1: 检查并配置 nginx-rtmp(如果需要)
- # ========================================
- if [ "${ENABLE_RTMP}" = "true" ]; then
- echo
- echo "[Step 3.1/4] Checking nginx-rtmp configuration..."
- echo
-
- # 检查 nginx RTMP 模块是否已安装
- if dpkg -l | grep -q libnginx-mod-rtmp; then
- echo "✅ nginx-rtmp-module is already installed"
- else
- echo "⚠️ nginx-rtmp-module not found. Please run:"
- echo " sudo bash setup_nginx_rtmp.sh"
- echo
- echo "Continuing without RTMP support..."
- ENABLE_RTMP="false"
- fi
-
- if [ "${ENABLE_RTMP}" = "true" ]; then
- # 检查 nginx 是否运行
- if systemctl is-active --quiet nginx; then
- echo "✅ nginx is running"
- else
- echo "Starting nginx..."
- sudo systemctl start nginx
- if [ $? -eq 0 ]; then
- echo "✅ nginx started successfully"
- else
- echo "❌ Failed to start nginx"
- ENABLE_RTMP="false"
- fi
- fi
-
- # 设置推流地址
- # app.py 推送到 nginx-rtmp,跳板机从 nginx-rtmp 拉流
- PUSH_URL="rtmp://127.0.0.1:${RTMP_PORT}/live/${AVATAR_ID}"
- TRANSPORT_MODE="rtcpush"
-
- echo
- echo "RTMP Configuration:"
- echo " Push URL: ${PUSH_URL}"
- echo " Jump Server Pull: ffmpeg -i rtmp://127.0.0.1:1935/live/${AVATAR_ID} -c copy -f flv \"rtmp://cdn...\""
- echo
- fi
- else
- PUSH_URL=""
- TRANSPORT_MODE="webrtc"
- fi
- # 启动 app.py
- # 关键环境变量说明:
- # AIOICE_PORT_MIN/MAX : aiortc 绑定的 UDP 端口范围,必须与防火墙放行范围一致
- # AIOICE_BIND_IP : aiortc 只绑定这一个 IP(服务器有多网卡时,指定浏览器能路由到的那个)
- # WEBRTC_NAT_IP : 如果浏览器用的是跳板机 IP 而非服务器 IP,设置此项做 SDP IP 替换
- #
- # 默认值(可通过外部传入环境变量覆盖):
- # CUDA_VISIBLE_DEVICES=6,7 (使用 GPU 6,7 运行)
- # AIOICE_PORT_MIN=50000 AIOICE_PORT_MAX=50010
- # AIOICE_BIND_IP=192.168.22.9 WEBRTC_NAT_IP=183.252.196.135
- #
- echo "Using env: CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES} AIOICE_BIND_IP=${AIOICE_BIND_IP} WEBRTC_NAT_IP=${WEBRTC_NAT_IP} AIOICE_PORT_MIN=${AIOICE_PORT_MIN} AIOICE_PORT_MAX=${AIOICE_PORT_MAX}"
- CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES}" \
- AIOICE_BIND_IP="${AIOICE_BIND_IP}" \
- WEBRTC_NAT_IP="${WEBRTC_NAT_IP}" \
- AIOICE_PORT_MIN="${AIOICE_PORT_MIN}" \
- AIOICE_PORT_MAX="${AIOICE_PORT_MAX}" \
- python app.py \
- --avatar_id "${AVATAR_ID}" \
- --tts voxcpm2api \
- --VOXCPM2_API_URL ${VOXCPM2_API_URL} \
- --VOXCPM2_REF_WAV voice_output.wav \
- --VOXCPM2_REF_TEXT "你好,买水果,卖水果,新鲜的水果。" \
- --CFG_VALUE 1.5 \
- --INFERENCE_TIMESTEPS 10 \
- --model wav2lip \
- --transport ${TRANSPORT_MODE} \
- ${PUSH_URL:+--push_url "${PUSH_URL}"} \
- --listenport 7868
- echo
- echo "========================================"
- echo "Pipeline completed!"
- echo "========================================"
|