run_train_and_cloneb.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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. : "${TRANSPORT_MODE:=webrtc}" # 传输模式,默认 WebRTC
  46. : "${RTMP_PORT:=1935}" # RTMP 服务端口
  47. : "${FFMPEG_PUSH:=true}" # 是否启动本机 FFmpeg 转发
  48. export CUDA_VISIBLE_DEVICES AIOICE_PORT_MIN AIOICE_PORT_MAX AIOICE_BIND_IP WEBRTC_NAT_IP
  49. export PYTORCH_CUDA_ALLOC_CONF TORCHDYNAMO_DISABLE
  50. echo "========================================"
  51. echo "Digital Human Generation Pipeline (Green Screen)"
  52. echo "========================================"
  53. echo
  54. echo "Input Video: ${INPUT_VIDEO}"
  55. echo "Avatar ID: ${AVATAR_ID}"
  56. echo "Timestamp: ${TIMESTAMP}"
  57. 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}"
  58. echo
  59. # 检查输入视频是否存在
  60. if [ ! -f "${INPUT_VIDEO}" ]; then
  61. echo "Error: Input video not found: ${INPUT_VIDEO}"
  62. exit 1
  63. fi
  64. echo "Current Directory: $(pwd)"
  65. echo
  66. # ========================================
  67. # Step 1: 调用 API 转换视频背景到绿幕
  68. # ========================================
  69. echo
  70. echo "[Step 1/3] Calling API for video background conversion..."
  71. echo
  72. # 确保 back/video 目录存在
  73. mkdir -p "${SCRIPT_DIR}back/video"
  74. # 生成输出文件名
  75. INPUT_BASENAME=$(basename "${INPUT_VIDEO}" | sed 's/\.[^.]*$//')
  76. GREENSCREEN_VIDEO="${SCRIPT_DIR}back/video/${INPUT_BASENAME}_greenscreen.mp4"
  77. echo "Converting video to green screen via API..."
  78. echo " Input: ${INPUT_VIDEO}"
  79. echo " Output: ${GREENSCREEN_VIDEO}"
  80. echo " API: http://183.252.196.135:6001"
  81. echo
  82. # 调用 RVM GreenScreen API,传递输出文件路径
  83. python back/back_api.py "${GREENSCREEN_VIDEO}"
  84. if [ $? -ne 0 ]; then
  85. echo "Error: API video conversion failed!"
  86. cd "${SCRIPT_DIR}" || exit 1
  87. exit 1
  88. fi
  89. # 检查输出文件是否存在
  90. if [ ! -f "${GREENSCREEN_VIDEO}" ]; then
  91. echo "Error: Green screen video not generated!"
  92. cd "${SCRIPT_DIR}" || exit 1
  93. exit 1
  94. fi
  95. echo
  96. echo "Green screen conversion completed!"
  97. echo "Generated Video: ${GREENSCREEN_VIDEO}"
  98. echo
  99. # ========================================
  100. # Step 2: Run genavatar.py for pre-processing
  101. # ========================================
  102. echo
  103. echo "[Step 2/3] Running genavatar.py for pre-processing..."
  104. echo
  105. # 进入wav2lip目录
  106. cd "${SCRIPT_DIR}wav2lip" || exit 1
  107. echo "Generating avatar data with:"
  108. echo " Video: ${GREENSCREEN_VIDEO}"
  109. echo " Avatar ID: ${AVATAR_ID}"
  110. echo " Image Size: 256"
  111. echo
  112. # 运行 genavatar.py
  113. python genavatar.py --video_path "${GREENSCREEN_VIDEO}" --img_size 256 --avatar_id "${AVATAR_ID}"
  114. if [ $? -ne 0 ]; then
  115. echo "Error: genavatar.py failed!"
  116. cd "${SCRIPT_DIR}" || exit 1
  117. exit 1
  118. fi
  119. cd "${SCRIPT_DIR}" || exit 1
  120. echo
  121. echo "Pre-processing completed!"
  122. echo "Avatar data saved to: data/avatars/${AVATAR_ID}"
  123. echo
  124. # ========================================
  125. # Step 3: 测试 VoxCPM2 API TTS 服务 + 运行 app.py
  126. # ========================================
  127. echo
  128. echo "[Step 3/4] Testing VoxCPM2 API TTS service..."
  129. echo
  130. # VoxCPM2 API 服务配置
  131. VOXCPM2_API_URL="http://183.252.196.135:6003"
  132. echo "Testing VoxCPM2 API TTS service..."
  133. echo " API 地址:${VOXCPM2_API_URL}"
  134. echo
  135. # 先测试 API 服务是否可用(重试3次)
  136. API_READY=false
  137. for i in 1 2 3; do
  138. if curl -s --max-time 5 ${VOXCPM2_API_URL}/health > /dev/null 2>&1; then
  139. API_READY=true
  140. break
  141. fi
  142. echo " 等待 API 启动... (${i}/3)"
  143. sleep 2
  144. done
  145. if [ "${API_READY}" != "true" ]; then
  146. echo "❌ VoxCPM2 API 服务无法连接:${VOXCPM2_API_URL}"
  147. echo
  148. echo "请确保远程服务器已启动 VoxCPM2 API 服务:"
  149. echo " SSH到服务器: ssh root@183.252.196.135"
  150. echo " 然后: ssh test@192.168.22.9"
  151. echo " 启动服务: cd /mnt/nvme1data/model && python voxcpm2_api.py"
  152. echo
  153. echo "如果服务已启动,请建立 SSH 隧道:"
  154. echo " ssh -f -N -L 6003:192.168.22.9:6003 root@183.252.196.135"
  155. echo
  156. exit 1
  157. fi
  158. echo "✅ VoxCPM2 API 服务正常"
  159. curl -s ${VOXCPM2_API_URL}/health | python -m json.tool 2>/dev/null || true
  160. echo
  161. # 测试 TTS 生成
  162. echo "🧪 测试 TTS 音频生成..."
  163. python voxcpm_api.py
  164. if [ $? -ne 0 ]; then
  165. echo "⚠️ TTS 测试失败,但将继续启动数字人服务"
  166. else
  167. echo "✅ TTS 测试成功"
  168. fi
  169. echo
  170. echo "========================================"
  171. echo "[Step 4/4] 启动数字人服务(API 调用模式)"
  172. echo "========================================"
  173. echo
  174. echo "Starting digital human server..."
  175. echo "Avatar ID: ${AVATAR_ID}"
  176. echo "TTS: voxcpm2api (VoxCPM2 API 调用模式 - 显存隔离)"
  177. echo "API URL: ${VOXCPM2_API_URL}"
  178. echo "Model: /mnt/nvme1data/model/VoxCPM2 (独立进程)"
  179. echo "Transport: ${TRANSPORT_MODE}"
  180. if [ "${ENABLE_RTMP}" = "true" ]; then
  181. echo "RTMP Push URL: ${PUSH_URL}"
  182. echo "Jump Server Command:"
  183. echo " ffmpeg -i rtmp://127.0.0.1:1935/live/${AVATAR_ID} -c copy -f flv \"rtmp://livepush.ailien.shop/alien/people?auth_key=...\""
  184. fi
  185. echo
  186. echo "优势:"
  187. echo " ✅ 模型显存独立(~8GB),不影响数字人渲染"
  188. echo " ✅ 多个数字人实例可共用同一 API 服务"
  189. echo " ✅ API 崩溃不影响数字人主进程"
  190. echo " ✅ 更新模型只需重启 API,无需停数字人"
  191. echo
  192. echo "访问地址:"
  193. echo " Web 界面:http://localhost:7868/dashboard.html"
  194. echo " API 健康检查:${VOXCPM2_API_URL}/health"
  195. echo
  196. echo "Press Ctrl+C to stop the server."
  197. echo
  198. # 检查并安装pyaudio
  199. echo "Checking pyaudio installation..."
  200. python -c "import pyaudio" 2>/dev/null || (echo "Installing pyaudio..." && pip install pyaudio)
  201. # ========================================
  202. # Step 3.1: 检查并配置 nginx-rtmp(如果需要)
  203. # ========================================
  204. if [ "${ENABLE_RTMP}" = "true" ]; then
  205. echo
  206. echo "[Step 3.1/4] Checking nginx-rtmp configuration..."
  207. echo
  208. # 检查 nginx RTMP 模块是否已安装
  209. if dpkg -l | grep -q libnginx-mod-rtmp; then
  210. echo "✅ nginx-rtmp-module is already installed"
  211. else
  212. echo "⚠️ nginx-rtmp-module not found. Please run:"
  213. echo " sudo bash setup_nginx_rtmp.sh"
  214. echo
  215. echo "Continuing without RTMP support..."
  216. ENABLE_RTMP="false"
  217. fi
  218. if [ "${ENABLE_RTMP}" = "true" ]; then
  219. # 检查 nginx 是否运行
  220. if systemctl is-active --quiet nginx; then
  221. echo "✅ nginx is running"
  222. else
  223. echo "Starting nginx..."
  224. sudo systemctl start nginx
  225. if [ $? -eq 0 ]; then
  226. echo "✅ nginx started successfully"
  227. else
  228. echo "❌ Failed to start nginx"
  229. ENABLE_RTMP="false"
  230. fi
  231. fi
  232. # 设置推流地址
  233. # app.py 推送到 nginx-rtmp,跳板机从 nginx-rtmp 拉流
  234. PUSH_URL="rtmp://127.0.0.1:${RTMP_PORT}/live/${AVATAR_ID}"
  235. TRANSPORT_MODE="rtcpush"
  236. echo
  237. echo "RTMP Configuration:"
  238. echo " Push URL: ${PUSH_URL}"
  239. echo " Jump Server Pull: ffmpeg -i rtmp://127.0.0.1:1935/live/${AVATAR_ID} -c copy -f flv \"rtmp://cdn...\""
  240. echo
  241. fi
  242. else
  243. PUSH_URL=""
  244. TRANSPORT_MODE="webrtc"
  245. fi
  246. # 启动 app.py
  247. # 关键环境变量说明:
  248. # AIOICE_PORT_MIN/MAX : aiortc 绑定的 UDP 端口范围,必须与防火墙放行范围一致
  249. # AIOICE_BIND_IP : aiortc 只绑定这一个 IP(服务器有多网卡时,指定浏览器能路由到的那个)
  250. # WEBRTC_NAT_IP : 如果浏览器用的是跳板机 IP 而非服务器 IP,设置此项做 SDP IP 替换
  251. #
  252. # 默认值(可通过外部传入环境变量覆盖):
  253. # CUDA_VISIBLE_DEVICES=6,7 (使用 GPU 6,7 运行)
  254. # AIOICE_PORT_MIN=50000 AIOICE_PORT_MAX=50010
  255. # AIOICE_BIND_IP=192.168.22.9 WEBRTC_NAT_IP=183.252.196.135
  256. #
  257. 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}"
  258. CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES}" \
  259. AIOICE_BIND_IP="${AIOICE_BIND_IP}" \
  260. WEBRTC_NAT_IP="${WEBRTC_NAT_IP}" \
  261. AIOICE_PORT_MIN="${AIOICE_PORT_MIN}" \
  262. AIOICE_PORT_MAX="${AIOICE_PORT_MAX}" \
  263. python app.py \
  264. --avatar_id "${AVATAR_ID}" \
  265. --tts voxcpm2api \
  266. --VOXCPM2_API_URL ${VOXCPM2_API_URL} \
  267. --VOXCPM2_REF_WAV voice_output.wav \
  268. --VOXCPM2_REF_TEXT "你好,买水果,卖水果,新鲜的水果。" \
  269. --CFG_VALUE 1.5 \
  270. --INFERENCE_TIMESTEPS 10 \
  271. --model wav2lip \
  272. --transport ${TRANSPORT_MODE} \
  273. ${PUSH_URL:+--push_url "${PUSH_URL}"} \
  274. --listenport 7868
  275. echo
  276. echo "========================================"
  277. echo "Pipeline completed!"
  278. echo "========================================"