run_train_and_clone.sh 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. # 启用bash的扩展功能(对应bat的enabledelayedexpansion)
  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_clone.sh \"source_image\""
  9. echo
  10. echo "Examples:"
  11. echo " ./run_train_and_clone.sh \"LivePortrait-main/assets/examples/source/s9.jpg\""
  12. exit 1
  13. fi
  14. # 定义核心变量
  15. SOURCE_IMAGE="$1"
  16. DRIVING_VIDEO="LivePortrait-main/assets/examples/driving/output.mp4"
  17. # 获取脚本所在目录(对应bat的%~dp0)
  18. SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/
  19. cd "$SCRIPT_DIR" || exit 1
  20. # 生成时间戳(对应bat的powershell时间戳)
  21. TIMESTAMP=$(date +%Y%m%d_%H%M%S)
  22. AVATAR_ID="avatar_${TIMESTAMP}"
  23. AVATAR_DIR="${SCRIPT_DIR}data/avatars/${AVATAR_ID}"
  24. # 停止脚本时自动删除本次生成的头像目录
  25. cleanup_avatar_dir() {
  26. if [ -n "${AVATAR_DIR:-}" ] && [ -d "${AVATAR_DIR}" ]; then
  27. echo "Cleaning generated avatar directory: ${AVATAR_DIR}"
  28. rm -rf -- "${AVATAR_DIR}"
  29. fi
  30. }
  31. trap 'cleanup_avatar_dir' EXIT
  32. # 默认环境变量(可通过外部传入覆盖)
  33. : "${CUDA_VISIBLE_DEVICES:=6,7}"
  34. : "${AIOICE_PORT_MIN:=50000}"
  35. : "${AIOICE_PORT_MAX:=50010}"
  36. : "${AIOICE_BIND_IP:=192.168.22.9}"
  37. : "${WEBRTC_NAT_IP:=183.252.196.135}"
  38. export CUDA_VISIBLE_DEVICES AIOICE_PORT_MIN AIOICE_PORT_MAX AIOICE_BIND_IP WEBRTC_NAT_IP
  39. echo "========================================"
  40. echo "Digital Human Generation Pipeline"
  41. echo "========================================"
  42. echo
  43. echo "Source Image: ${SOURCE_IMAGE}"
  44. echo "Driving Video: ${DRIVING_VIDEO} (Fixed)"
  45. echo "Avatar ID: ${AVATAR_ID}"
  46. echo "Timestamp: ${TIMESTAMP}"
  47. 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}"
  48. echo
  49. # 检查源图片是否存在
  50. if [ ! -f "${SOURCE_IMAGE}" ]; then
  51. echo "Error: Source image not found: ${SOURCE_IMAGE}"
  52. exit 1
  53. fi
  54. # 检查驱动视频是否存在
  55. if [ ! -f "${DRIVING_VIDEO}" ]; then
  56. echo "Error: Driving video not found: ${DRIVING_VIDEO}"
  57. exit 1
  58. fi
  59. echo "Current Directory: $(pwd)"
  60. echo
  61. # ========================================
  62. # Step 1: Run LivePortrait to generate video
  63. # ========================================
  64. echo
  65. echo "[Step 1/3] Running LivePortrait to generate video..."
  66. echo
  67. # 转换驱动视频为绝对路径(对应bat的DRIVING_VIDEO_ABS)
  68. DRIVING_VIDEO_ABS="${DRIVING_VIDEO}"
  69. if [[ ! "${DRIVING_VIDEO_ABS}" =~ ^/ ]]; then
  70. DRIVING_VIDEO_ABS="${SCRIPT_DIR}${DRIVING_VIDEO}"
  71. fi
  72. echo "Debug: SCRIPT_DIR = ${SCRIPT_DIR}"
  73. echo "Debug: DRIVING_VIDEO_ABS = ${DRIVING_VIDEO_ABS}"
  74. echo "Debug: File exists check:"
  75. # 检查驱动视频绝对路径是否存在
  76. if [ ! -f "${DRIVING_VIDEO_ABS}" ]; then
  77. echo "Error: Driving video file does not exist at: ${DRIVING_VIDEO_ABS}"
  78. echo
  79. echo "Checking if the path is correct..."
  80. echo "Checking: LivePortrait-main/assets/examples/driving/output.mp4"
  81. if [ -f "LivePortrait-main/assets/examples/driving/output.mp4" ]; then
  82. echo "File exists with relative path!"
  83. else
  84. echo "File NOT found with relative path!"
  85. fi
  86. exit 1
  87. fi
  88. # 创建临时驱动视频副本(对应bat的TEMP_DRIVING)
  89. DRIVE_EXT="${DRIVING_VIDEO_ABS##*.}"
  90. DRIVE_BASE="${DRIVING_VIDEO_ABS%.*}"
  91. TEMP_DRIVING="temp_driving_${TIMESTAMP}.${DRIVE_EXT}"
  92. TEMP_DRIVING_PATH="${SCRIPT_DIR}LivePortrait-main/${TEMP_DRIVING}"
  93. echo "Creating temporary copy of driving video: ${TEMP_DRIVING}"
  94. echo "From: ${DRIVING_VIDEO_ABS}"
  95. echo "To: ${TEMP_DRIVING_PATH}"
  96. # 复制文件(对应bat的copy命令)
  97. cp "${DRIVING_VIDEO_ABS}" "${TEMP_DRIVING_PATH}"
  98. if [ $? -ne 0 ]; then
  99. echo "Error: Failed to copy driving video"
  100. echo "Check if file exists: ${DRIVING_VIDEO_ABS}"
  101. cd "${SCRIPT_DIR}" || exit 1
  102. exit 1
  103. fi
  104. # 进入LivePortrait目录
  105. cd "${SCRIPT_DIR}LivePortrait-main" || exit 1
  106. # 处理源图片路径(移除LivePortrait-main前缀)
  107. LP_SOURCE="${SOURCE_IMAGE}"
  108. LP_SOURCE=${LP_SOURCE//LivePortrait-main\//}
  109. LP_SOURCE=${LP_SOURCE//LivePortrait-main\\/}
  110. echo "Running LivePortrait with:"
  111. echo " Source: ${LP_SOURCE}"
  112. echo " Driving: ${TEMP_DRIVING}"
  113. echo
  114. # 运行 LivePortrait 推理(确保使用 live conda 环境)
  115. source "$(conda info --base)"/etc/profile.d/conda.sh
  116. conda activate live
  117. python inference.py -s "${LP_SOURCE}" -d "${TEMP_DRIVING}"
  118. if [ $? -ne 0 ]; then
  119. echo "Error: LivePortrait failed!"
  120. # 清理临时文件
  121. if [ -f "${TEMP_DRIVING_PATH}" ]; then
  122. rm "${TEMP_DRIVING_PATH}"
  123. fi
  124. cd "${SCRIPT_DIR}" || exit 1
  125. exit 1
  126. fi
  127. # 清理临时驱动视频
  128. if [ -f "${TEMP_DRIVING_PATH}" ]; then
  129. rm "${TEMP_DRIVING_PATH}"
  130. fi
  131. echo "Temporary file deleted."
  132. # 查找生成的视频文件
  133. # 获取源图片的basename(支持.jpg 和.png 等扩展名)
  134. SOURCE_BASENAME=$(basename "${SOURCE_IMAGE}" | sed 's/\.[^.]*$//')
  135. EXPECTED_VIDEO="${SOURCE_BASENAME}--temp_driving_${TIMESTAMP}.mp4"
  136. GENERATED_VIDEO=""
  137. # 检查预期视频是否存在
  138. if [ -f "${SCRIPT_DIR}LivePortrait-main/animations/${EXPECTED_VIDEO}" ]; then
  139. GENERATED_VIDEO="${SCRIPT_DIR}LivePortrait-main/animations/${EXPECTED_VIDEO}"
  140. echo "Found expected face-only video: ${EXPECTED_VIDEO}"
  141. elif [ -n "$(ls "${SCRIPT_DIR}LivePortrait-main/animations/"*--temp_driving_${TIMESTAMP}.mp4 2>/dev/null | grep -v "_concat" | head -1)" ]; then
  142. # 查找最新生成的视频(不含_concat)
  143. GENERATED_VIDEO=$(ls -t "${SCRIPT_DIR}LivePortrait-main/animations/"*--temp_driving_${TIMESTAMP}.mp4 2>/dev/null | grep -v "_concat" | head -1)
  144. echo "Found video: $(basename "${GENERATED_VIDEO}")"
  145. else
  146. # 降级查找:匹配*--output.mp4且不含_concat的文件
  147. FALLBACK_VIDEO=$(ls "${SCRIPT_DIR}LivePortrait-main/animations/"*--output.mp4 2>/dev/null | grep -v "_concat" | head -1)
  148. if [ -n "${FALLBACK_VIDEO}" ]; then
  149. GENERATED_VIDEO="${FALLBACK_VIDEO}"
  150. echo "Found fallback face-only video: $(basename "${GENERATED_VIDEO}")"
  151. else
  152. echo "Error: Could not find generated video in animations/"
  153. echo "Expected: ${EXPECTED_VIDEO}"
  154. echo "Searching in animations/..."
  155. ls "${SCRIPT_DIR}LivePortrait-main/animations/" 2>/dev/null
  156. cd "${SCRIPT_DIR}" || exit 1
  157. exit 1
  158. fi
  159. fi
  160. echo
  161. echo "LivePortrait completed!"
  162. echo "Generated Video: ${GENERATED_VIDEO}"
  163. echo
  164. cd "${SCRIPT_DIR}" || exit 1
  165. # ========================================
  166. # Step 2: Run genavatar.py for pre-processing
  167. # ========================================
  168. echo
  169. echo "[Step 2/3] Running genavatar.py for pre-processing..."
  170. echo
  171. # 进入wav2lip目录
  172. cd "${SCRIPT_DIR}wav2lip" || exit 1
  173. echo "Generating avatar data with:"
  174. echo " Video: ${GENERATED_VIDEO}"
  175. echo " Avatar ID: ${AVATAR_ID}"
  176. echo " Image Size: 256"
  177. echo
  178. # 运行 genavatar.py
  179. python genavatar.py --video_path "${GENERATED_VIDEO}" --img_size 256 --avatar_id "${AVATAR_ID}"
  180. if [ $? -ne 0 ]; then
  181. echo "Error: genavatar.py failed!"
  182. cd "${SCRIPT_DIR}" || exit 1
  183. exit 1
  184. fi
  185. # 清理 LivePortrait-main 目录中的临时文件 (pkl 和视频)
  186. echo "Cleaning up temporary files in LivePortrait-main..."
  187. cd "${SCRIPT_DIR}LivePortrait-main" || exit 1
  188. # 删除 temp_driving_*.pkl 文件
  189. find . -maxdepth 1 -name "temp_driving_*.pkl" -type f -delete
  190. echo "Temporary pkl files deleted."
  191. # 删除 animations 目录中对应的视频文件
  192. if [ -n "${GENERATED_VIDEO}" ] && [ -f "${GENERATED_VIDEO}" ]; then
  193. echo "Deleting generated video: ${GENERATED_VIDEO}"
  194. rm -f "${GENERATED_VIDEO}"
  195. # 同时删除 _concat 版本 (如果存在)
  196. CONCAT_VIDEO="${GENERATED_VIDEO%.mp4}_concat.mp4"
  197. if [ -f "${CONCAT_VIDEO}" ]; then
  198. echo "Deleting concat version: $(basename "${CONCAT_VIDEO}")"
  199. rm -f "${CONCAT_VIDEO}"
  200. fi
  201. fi
  202. echo "Temporary video files deleted."
  203. cd "${SCRIPT_DIR}" || exit 1
  204. echo
  205. echo "Pre-processing completed!"
  206. echo "Avatar data saved to: data/avatars/${AVATAR_ID}"
  207. echo
  208. # ========================================
  209. # Step 3: Run app.py with edgetts for digital human generation
  210. # ========================================
  211. echo
  212. echo "[Step 3/3] Starting app.py with edgetts for digital human generation..."
  213. echo
  214. echo "Starting digital human server..."
  215. echo "Avatar ID: ${AVATAR_ID}"
  216. echo "TTS: edgetts (Edge TTS via local API http://127.0.0.1:1024/tts)"
  217. echo
  218. echo "The server will start and you can access it via web interface."
  219. echo "Press Ctrl+C to stop the server."
  220. echo
  221. # 检查并安装pyaudio(对应bat的pyaudio检查)
  222. echo "Checking pyaudio installation..."
  223. python -c "import pyaudio" 2>/dev/null || (echo "Installing pyaudio..." && pip install pyaudio)
  224. # 启动 app.py
  225. # 关键环境变量说明:
  226. # AIOICE_PORT_MIN/MAX : aiortc 绑定的 UDP 端口范围,必须与防火墙放行范围一致
  227. # AIOICE_BIND_IP : aiortc 只绑定这一个 IP(服务器有多网卡时,指定浏览器能路由到的那个)
  228. # WEBRTC_NAT_IP : 如果浏览器用的是跳板机 IP 而非服务器 IP,设置此项做 SDP IP 替换
  229. #
  230. # 默认值(可通过外部传入环境变量覆盖):
  231. # CUDA_VISIBLE_DEVICES=6,7 (使用 GPU 6,7 运行)
  232. # AIOICE_PORT_MIN=50000 AIOICE_PORT_MAX=50010
  233. # AIOICE_BIND_IP=192.168.22.9 WEBRTC_NAT_IP=183.252.196.135
  234. #
  235. 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}"
  236. CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES}" \
  237. AIOICE_BIND_IP="${AIOICE_BIND_IP}" \
  238. WEBRTC_NAT_IP="${WEBRTC_NAT_IP}" \
  239. AIOICE_PORT_MIN="${AIOICE_PORT_MIN}" \
  240. AIOICE_PORT_MAX="${AIOICE_PORT_MAX}" \
  241. python app.py \
  242. --avatar_id "${AVATAR_ID}" \
  243. --tts edgetts \
  244. --REF_FILE zh-CN-YunxiaNeural \
  245. --model wav2lip \
  246. --transport webrtc \
  247. --listenport 7868
  248. echo
  249. echo "========================================"
  250. echo "Pipeline completed!"
  251. echo "========================================"