run_train_and_cloneb.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. # 启用bash的扩展功能
  2. shopt -s extglob
  3. # 检查参数是否传入
  4. if [ $# -eq 0 ]; then
  5. echo "Error: Insufficient parameters"
  6. echo
  7. echo "Usage:"
  8. echo " ./run_train_and_cloneb.sh \"input_video\""
  9. echo
  10. echo "Examples:"
  11. echo " ./run_train_and_cloneb.sh \"path/to/your/video.mp4\""
  12. exit 1
  13. fi
  14. # 定义核心变量
  15. INPUT_VIDEO="$1"
  16. # 获取脚本所在目录
  17. SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/
  18. cd "$SCRIPT_DIR" || exit 1
  19. # 使用固定的预生成 avatar 目录(不生成时间戳)
  20. # 如果需要生成新的,取消下面的注释并注释掉固定名称
  21. AVATAR_ID="wav2lip256_avatar1"
  22. # TIMESTAMP=$(date +%Y%m%d_%H%M%S)
  23. # AVATAR_ID="avatar_${TIMESTAMP}"
  24. AVATAR_DIR="${SCRIPT_DIR}data/avatars/${AVATAR_ID}"
  25. # 不自动删除 avatar 目录(因为是预生成的)
  26. # cleanup_avatar_dir() {
  27. # if [ -n "${AVATAR_DIR:-}" ] && [ -d "${AVATAR_DIR}" ]; then
  28. # echo "Cleaning generated avatar directory: ${AVATAR_DIR}"
  29. # rm -rf -- "${AVATAR_DIR}"
  30. # fi
  31. # }
  32. # trap 'cleanup_avatar_dir' EXIT
  33. # 默认环境变量(可通过外部传入覆盖)
  34. : "${CUDA_VISIBLE_DEVICES:=4,5,7}"
  35. : "${AIOICE_PORT_MIN:=50000}"
  36. : "${AIOICE_PORT_MAX:=50010}"
  37. : "${AIOICE_BIND_IP:=192.168.22.9}"
  38. : "${WEBRTC_NAT_IP:=183.252.196.135}"
  39. # PyTorch 显存优化
  40. : "${PYTORCH_CUDA_ALLOC_CONF:=max_split_size_mb:512,expandable_segments:True}"
  41. # 禁用 TorchDynamo 编译(避免 VoxCPM2 兼容性问题)
  42. : "${TORCHDYNAMO_DISABLE:=1}"
  43. # RTMP 推流配置(使用 nginx-rtmp + FFmpeg)
  44. : "${ENABLE_RTMP:=true}" # 是否启用 RTMP 推流(默认启用)
  45. : "${RTMP_PORT:=1935}" # RTMP 服务端口
  46. : "${FFMPEG_PUSH:=true}" # 是否启动本机 FFmpeg 转发
  47. export CUDA_VISIBLE_DEVICES AIOICE_PORT_MIN AIOICE_PORT_MAX AIOICE_BIND_IP WEBRTC_NAT_IP
  48. export PYTORCH_CUDA_ALLOC_CONF TORCHDYNAMO_DISABLE
  49. echo "========================================"
  50. echo "Digital Human Generation Pipeline (Green Screen)"
  51. echo "========================================"
  52. echo
  53. echo "Input Video: ${INPUT_VIDEO}"
  54. echo "Avatar ID: ${AVATAR_ID}"
  55. echo "Timestamp: ${TIMESTAMP}"
  56. 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}"
  57. echo
  58. # 检查输入视频是否存在
  59. if [ ! -f "${INPUT_VIDEO}" ]; then
  60. echo "Error: Input video not found: ${INPUT_VIDEO}"
  61. exit 1
  62. fi
  63. echo "Current Directory: $(pwd)"
  64. echo
  65. # ========================================
  66. # Step 1: Convert video background to green screen
  67. # ========================================
  68. echo
  69. echo "[Step 1/3] Converting video background to green screen..."
  70. echo
  71. # 确保 back/video 目录存在
  72. mkdir -p "${SCRIPT_DIR}back/video"
  73. # 生成输出文件名
  74. INPUT_BASENAME=$(basename "${INPUT_VIDEO}" | sed 's/\.[^.]*$//')
  75. GREENSCREEN_VIDEO="${SCRIPT_DIR}back/video/${INPUT_BASENAME}_greenscreen.mp4"
  76. echo "Converting video to green screen..."
  77. echo " Input: ${INPUT_VIDEO}"
  78. echo " Output: ${GREENSCREEN_VIDEO}"
  79. echo
  80. # 运行绿幕转换脚本
  81. python back/video_to_greenscreen.py "${INPUT_VIDEO}" -o "${GREENSCREEN_VIDEO}"
  82. if [ $? -ne 0 ]; then
  83. echo "Error: Green screen conversion failed!"
  84. cd "${SCRIPT_DIR}" || exit 1
  85. exit 1
  86. fi
  87. # 检查输出文件是否存在
  88. if [ ! -f "${GREENSCREEN_VIDEO}" ]; then
  89. echo "Error: Green screen video not generated!"
  90. cd "${SCRIPT_DIR}" || exit 1
  91. exit 1
  92. fi
  93. echo
  94. echo "Green screen conversion completed!"
  95. echo "Generated Video: ${GREENSCREEN_VIDEO}"
  96. echo
  97. # ========================================
  98. # Step 2: Run genavatar.py for pre-processing
  99. # ========================================
  100. echo
  101. echo "[Step 2/3] Running genavatar.py for pre-processing..."
  102. echo
  103. # 进入wav2lip目录
  104. cd "${SCRIPT_DIR}wav2lip" || exit 1
  105. echo "Generating avatar data with:"
  106. echo " Video: ${GREENSCREEN_VIDEO}"
  107. echo " Avatar ID: ${AVATAR_ID}"
  108. echo " Image Size: 256"
  109. echo
  110. # 运行 genavatar.py
  111. python genavatar.py --video_path "${GREENSCREEN_VIDEO}" --img_size 256 --avatar_id "${AVATAR_ID}"
  112. if [ $? -ne 0 ]; then
  113. echo "Error: genavatar.py failed!"
  114. cd "${SCRIPT_DIR}" || exit 1
  115. exit 1
  116. fi
  117. cd "${SCRIPT_DIR}" || exit 1
  118. echo
  119. echo "Pre-processing completed!"
  120. echo "Avatar data saved to: data/avatars/${AVATAR_ID}"
  121. echo
  122. # ========================================
  123. # Step 3: 启动 VoxCPM2 API 服务 + 运行 app.py
  124. # ========================================
  125. echo
  126. echo "[Step 3/4] 检查并启动 VoxCPM2 API 服务..."
  127. echo
  128. # VoxCPM2 API 服务配置
  129. VOXCPM2_API_URL="http://localhost:6003"
  130. VOXCPM2_API_LOG="/tmp/voxcpm2_api.log"
  131. echo "检查 VoxCPM2 API 服务状态..."
  132. echo " API 地址:${VOXCPM2_API_URL}"
  133. # 检查 API 服务是否已在运行
  134. if curl -s ${VOXCPM2_API_URL}/health > /dev/null 2>&1; then
  135. echo "✅ VoxCPM2 API 服务已在运行"
  136. curl -s ${VOXCPM2_API_URL}/health | python -m json.tool 2>/dev/null || true
  137. else
  138. echo "🚀 VoxCPM2 API 服务未运行,正在启动..."
  139. echo
  140. # 检查模型目录是否存在
  141. if [ ! -d "/mnt/nvme1data/model/VoxCPM2" ]; then
  142. echo "❌ 错误:VoxCPM2 模型目录不存在:/mnt/nvme1data/model/VoxCPM2"
  143. echo "请先下载或复制模型文件到该目录"
  144. exit 1
  145. fi
  146. # 检查 API 脚本是否存在
  147. if [ ! -f "/mnt/nvme1data/model/voxcpm2_api.py" ]; then
  148. echo "❌ 错误:VoxCPM2 API 脚本不存在:/mnt/nvme1data/model/voxcpm2_api.py"
  149. exit 1
  150. fi
  151. # 在后台启动 API 服务
  152. cd /mnt/nvme1data/model
  153. nohup python voxcpm2_api.py > ${VOXCPM2_API_LOG} 2>&1 &
  154. API_PID=$!
  155. echo "✅ VoxCPM2 API 服务已启动"
  156. echo " PID: ${API_PID}"
  157. echo " 日志:${VOXCPM2_API_LOG}"
  158. echo
  159. # 等待服务启动(最多 120 秒)
  160. echo "等待模型加载(最多120秒)..."
  161. START_TIME=$(date +%s)
  162. for i in $(seq 1 120); do
  163. if curl -s ${VOXCPM2_API_URL}/health > /dev/null 2>&1; then
  164. END_TIME=$(date +%s)
  165. WAIT_TIME=$((END_TIME - START_TIME))
  166. echo "✅ VoxCPM2 API 服务已就绪(等待 ${WAIT_TIME} 秒)"
  167. echo
  168. # 显示服务信息
  169. curl -s ${VOXCPM2_API_URL}/health | python -m json.tool 2>/dev/null || true
  170. break
  171. fi
  172. # 每 10 秒显示一次进度
  173. if [ $((i % 10)) -eq 0 ]; then
  174. echo " 已等待 ${i} 秒..."
  175. fi
  176. if [ $i -eq 120 ]; then
  177. echo
  178. echo "❌ VoxCPM2 API 服务启动超时(120秒)"
  179. echo
  180. echo "最后 50 行日志:"
  181. echo "========================================"
  182. tail -50 ${VOXCPM2_API_LOG}
  183. echo "========================================"
  184. echo
  185. echo "请检查:"
  186. echo " 1. 模型文件是否完整:ls -lh /mnt/nvme1data/model/VoxCPM2/"
  187. echo " 2. voxcpm 包是否安装:python -c 'import voxcpm'"
  188. echo " 3. GPU 是否可用:nvidia-smi"
  189. echo
  190. exit 1
  191. fi
  192. sleep 1
  193. done
  194. cd "${SCRIPT_DIR}"
  195. fi
  196. echo
  197. echo "========================================"
  198. echo "[Step 4/4] 启动数字人服务(API 调用模式)"
  199. echo "========================================"
  200. echo
  201. echo "Starting digital human server..."
  202. echo "Avatar ID: ${AVATAR_ID}"
  203. echo "TTS: voxcpm2api (VoxCPM2 API 调用模式 - 显存隔离)"
  204. echo "API URL: ${VOXCPM2_API_URL}"
  205. echo "Model: /mnt/nvme1data/model/VoxCPM2 (独立进程)"
  206. echo "Transport: ${TRANSPORT_MODE}"
  207. if [ "${ENABLE_RTMP}" = "true" ]; then
  208. echo "RTMP Push URL: ${PUSH_URL}"
  209. echo "Jump Server Command:"
  210. echo " ffmpeg -i rtmp://127.0.0.1:1935/live/${AVATAR_ID} -c copy -f flv \"rtmp://livepush.ailien.shop/alien/people?auth_key=...\""
  211. fi
  212. echo
  213. echo "优势:"
  214. echo " ✅ 模型显存独立(~8GB),不影响数字人渲染"
  215. echo " ✅ 多个数字人实例可共用同一 API 服务"
  216. echo " ✅ API 崩溃不影响数字人主进程"
  217. echo " ✅ 更新模型只需重启 API,无需停数字人"
  218. echo
  219. echo "访问地址:"
  220. echo " Web 界面:http://localhost:7868/dashboard.html"
  221. echo " API 健康检查:${VOXCPM2_API_URL}/health"
  222. echo
  223. echo "Press Ctrl+C to stop the server."
  224. echo
  225. # 检查并安装pyaudio
  226. echo "Checking pyaudio installation..."
  227. python -c "import pyaudio" 2>/dev/null || (echo "Installing pyaudio..." && pip install pyaudio)
  228. # ========================================
  229. # Step 3.1: 检查并配置 nginx-rtmp(如果需要)
  230. # ========================================
  231. if [ "${ENABLE_RTMP}" = "true" ]; then
  232. echo
  233. echo "[Step 3.1/4] Checking nginx-rtmp configuration..."
  234. echo
  235. # 检查 nginx RTMP 模块是否已安装
  236. if dpkg -l | grep -q libnginx-mod-rtmp; then
  237. echo "✅ nginx-rtmp-module is already installed"
  238. else
  239. echo "⚠️ nginx-rtmp-module not found. Please run:"
  240. echo " sudo bash setup_nginx_rtmp.sh"
  241. echo
  242. echo "Continuing without RTMP support..."
  243. ENABLE_RTMP="false"
  244. fi
  245. if [ "${ENABLE_RTMP}" = "true" ]; then
  246. # 检查 nginx 是否运行
  247. if systemctl is-active --quiet nginx; then
  248. echo "✅ nginx is running"
  249. else
  250. echo "Starting nginx..."
  251. sudo systemctl start nginx
  252. if [ $? -eq 0 ]; then
  253. echo "✅ nginx started successfully"
  254. else
  255. echo "❌ Failed to start nginx"
  256. ENABLE_RTMP="false"
  257. fi
  258. fi
  259. # 设置推流地址
  260. # app.py 推送到 nginx-rtmp,跳板机从 nginx-rtmp 拉流
  261. PUSH_URL="rtmp://127.0.0.1:${RTMP_PORT}/live/${AVATAR_ID}"
  262. TRANSPORT_MODE="rtcpush"
  263. echo
  264. echo "RTMP Configuration:"
  265. echo " Push URL: ${PUSH_URL}"
  266. echo " Jump Server Pull: ffmpeg -i rtmp://127.0.0.1:1935/live/${AVATAR_ID} -c copy -f flv \"rtmp://cdn...\""
  267. echo
  268. fi
  269. else
  270. PUSH_URL=""
  271. TRANSPORT_MODE="webrtc"
  272. fi
  273. # 启动 app.py
  274. # 关键环境变量说明:
  275. # AIOICE_PORT_MIN/MAX : aiortc 绑定的 UDP 端口范围,必须与防火墙放行范围一致
  276. # AIOICE_BIND_IP : aiortc 只绑定这一个 IP(服务器有多网卡时,指定浏览器能路由到的那个)
  277. # WEBRTC_NAT_IP : 如果浏览器用的是跳板机 IP 而非服务器 IP,设置此项做 SDP IP 替换
  278. #
  279. # 默认值(可通过外部传入环境变量覆盖):
  280. # CUDA_VISIBLE_DEVICES=6,7 (使用 GPU 6,7 运行)
  281. # AIOICE_PORT_MIN=50000 AIOICE_PORT_MAX=50010
  282. # AIOICE_BIND_IP=192.168.22.9 WEBRTC_NAT_IP=183.252.196.135
  283. #
  284. 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}"
  285. CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES}" \
  286. AIOICE_BIND_IP="${AIOICE_BIND_IP}" \
  287. WEBRTC_NAT_IP="${WEBRTC_NAT_IP}" \
  288. AIOICE_PORT_MIN="${AIOICE_PORT_MIN}" \
  289. AIOICE_PORT_MAX="${AIOICE_PORT_MAX}" \
  290. python app.py \
  291. --avatar_id "${AVATAR_ID}" \
  292. --tts voxcpm2api \
  293. --VOXCPM2_API_URL ${VOXCPM2_API_URL} \
  294. --VOXCPM2_REF_WAV voice_output.wav \
  295. --VOXCPM2_REF_TEXT "你好,买水果,卖水果,新鲜的水果。" \
  296. --CFG_VALUE 1.5 \
  297. --INFERENCE_TIMESTEPS 10 \
  298. --model wav2lip \
  299. --transport ${TRANSPORT_MODE} \
  300. ${PUSH_URL:+--push_url "${PUSH_URL}"} \
  301. --listenport 7868
  302. echo
  303. echo "========================================"
  304. echo "Pipeline completed!"
  305. echo "========================================"