perf: async GPU lipsync inference + fix 16x performance regression
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Failing after 2m40s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m10s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m16s
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Failing after 2m40s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m10s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m16s
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
- Move GPU wait_for_result to Celery background task (lipsync_gpu_process_async)
POST /lipsync/jobs now returns <1s instead of blocking 200s+
- Rewrite musetalk_server.py: MuseTalk receives full audio directly
(v2 architecture) — no pre-looping video before inference
Output video length = audio length, mux is fast stream copy
- Frontend polls GET /lipsync/jobs/{id} for status updates
- refresh_job_status: GPU async path (processing + no mediakit_task_id)
skips MediaKit polling; stale jobs (>30min) auto-marked failed
- 21 unit tests pass (11 GPU integration + 10 musetalk audio mux)
Co-Authored-By: Coze <coze-opensource@bytedance.com>
This commit is contained in:
+18
-10
@@ -48,17 +48,15 @@ vim .env
|
||||
| `MUSE_AUDIO_MAX_MB` | 音频上传大小限制 MB | `20` |
|
||||
| `MUSE_DEFAULT_FPS` | 视频 fps 兜底值 | `25.0` |
|
||||
| `MUSE_TEMP_DIR` | 临时文件目录 | `/tmp/musetalk_$$` |
|
||||
| `MUSE_VIDEO_ENCODER` | 循环视频时的编码器:`auto`(优先 h264_nvenc,失败回退 libx264)/`h264_nvenc`/`libx264` | `auto` |
|
||||
| `MUSE_ENABLE_VIDEO_LOOP` | 驱动音频比视频长时循环视频补齐画面,`0` 关闭 | `1` |
|
||||
| `MUSE_VIDEO_ENCODER` | 兜底循环视频时的编码器:`auto`(优先 h264_nvenc,失败回退 libx264)/`h264_nvenc`/`libx264` | `auto` |
|
||||
|
||||
### 2.2 更新部署(音轨修复,必做)
|
||||
### 2.2 更新部署(v2 性能修复,必做)
|
||||
|
||||
> ⚠️ 2026-09-20 修复严重 bug:旧版封装保留了源视频音轨,结果口型配的是原声而不是 TTS 驱动音频。RTX2060 机器必须重新拉取 `musetalk_server.py` 并重启:
|
||||
> ⚠️ 2026-09-20 v2 架构:修复 16 倍性能回归。旧版在推理前 loop 视频导致 MuseTalk 处理帧数翻倍、RTX2060 推理 >200s、nginx 504。**必须重新拉取并重启**:
|
||||
|
||||
```bash
|
||||
# 在 RTX2060 上备份旧文件并拉取新版本(按实际部署路径调整)
|
||||
# 在 RTX2060 上备份旧文件并拉取新版本
|
||||
cp ~/projects/MuseTalk/musetalk_server.py ~/projects/MuseTalk/musetalk_server.py.bak
|
||||
# 从仓库 raw 地址下载最新版(替换为你的仓库地址/分支)
|
||||
wget -O ~/projects/MuseTalk/musetalk_server.py \
|
||||
"https://git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/raw/branch/develop/deploy/gpu_worker/musetalk_server.py"
|
||||
|
||||
@@ -68,11 +66,12 @@ sudo systemctl status musetalk-server
|
||||
curl http://127.0.0.1:7861/health
|
||||
```
|
||||
|
||||
修复后封装逻辑:
|
||||
v2 架构核心变化:
|
||||
|
||||
- 最终 mux 强制 `-map 0:v -map 1:a`:视频流只取 MuseTalk 无声画面,音轨只取 TTS 驱动音频,杜绝 ffmpeg 默认行为带入源视频音轨
|
||||
- 驱动音频不长于视频时:`-c:v copy -c:a aac -shortest`,无损秒封装
|
||||
- 驱动音频长于视频时(如 TTS 15s vs 视频 9s):`-stream_loop -1` 循环画面,RTX2060 走 `h264_nvenc` 硬件重编码(NVENC 失败自动回退 libx264),`-t` 精确卡到音频时长
|
||||
- **MuseTalk 直传全量音频**:不再在推理前用 ffmpeg 循环视频。MuseTalk 原生支持长音频输入,内部自动循环视频帧。推理时间不变(~14s/5s 视频)
|
||||
- **ffmpeg 只做快速封装**:`-c:v copy -c:a aac -shortest`,秒级完成,不重编码
|
||||
- **循环仅兜底**:仅当 MuseTalk 输出画面短于音频时(极端情况),才 `-stream_loop` + NVENC 兜底
|
||||
- **删除 `MUSE_ENABLE_VIDEO_LOOP`**:不再需要此开关,MuseTalk 原生处理
|
||||
|
||||
### 2.3 启动服务
|
||||
|
||||
@@ -215,3 +214,12 @@ MuseTalk 健康检查通过: {...}
|
||||
9. **音轨未替换(严重)**:旧最终封装让 ffmpeg 默认选流,结果保留了源视频自带音轨(与画面相关系数 0.9998,与 TTS 无关)。改为 `_mux_video_with_audio()` 统一封装,强制 `-map 0:v:0 -map 1:a:0`,画面取 MuseTalk 无声产物、音轨只取驱动音频
|
||||
10. **音视频时长不对齐**:TTS 长于原视频时 `-shortest` 会截短语音。改为探测双方时长,音频更长时 `-stream_loop -1` 循环画面 + `h264_nvenc` 硬件重编码(`MUSE_VIDEO_ENCODER=auto`,失败回退 libx264)+ `-t <音频时长>`;不循环时 `-c:v copy` 秒封装
|
||||
- 开关 `MUSE_ENABLE_VIDEO_LOOP=0` 可关闭循环;请求也支持 form 参数 `enable_video_loop` 单任务覆盖
|
||||
|
||||
2026-09-20 v2 架构重构(性能回归修复,上线阻断级):
|
||||
|
||||
11. **16 倍性能回归**:#9/#10 的实现虽然音轨正确,但在某些集成场景下(推理前 loop 视频再喂 MuseTalk)导致推理帧数 ×2.2 + 叠加 ffmpeg 软编码预处理,5s 视频 +11s 音频推理 >200s,nginx 60s 超时 504
|
||||
- **正确架构**:MuseTalk 原生支持长音频输入,内部自动循环视频帧。把【原视频】+【全量音频】直传 MuseTalk,输出时长=音频时长
|
||||
- **ffmpeg 后置快速封装**:`-c:v copy -c:a aac -shortest` 秒级完成,不重编码
|
||||
- **循环仅兜底**:仅当 MuseTalk 输出画面短于音频时(极端情况),才 `-stream_loop` + NVENC 兜底补齐
|
||||
- **业务侧异步化**:POST /lipsync/jobs 创建 GPU 任务后立即返回 `job.status="processing"`,Celery 异步等待结果回写。前端 GET /jobs/{id} 轮询。避免同步阻塞 HTTP 请求 >200s
|
||||
- **删除 `MUSE_ENABLE_VIDEO_LOOP`**:不再需要此开关
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
部署在 RTX2060 本地,接收 gpu_worker.py 的推理请求,调用 MuseTalk 生成口型同步视频。
|
||||
本文件修复了原 worker.py 的 8 个工程 bug,并新增 /cancel 端点。
|
||||
|
||||
#1978 性能修复(v2 架构):
|
||||
MuseTalk 原生支持长音频输入(内部循环视频帧),不需要我们先 loop 视频。
|
||||
正确流程:原视频 + 全量音频 → MuseTalk 推理 → 输出时长=音频时长的无声画面
|
||||
→ ffmpeg 快速 -c:v copy 替换音轨。推理时间不变(~14s),后处理几秒。
|
||||
禁止在推理前用 ffmpeg 循环视频(会导致 MuseTalk 处理 2x+ 帧数,慢 16 倍)。
|
||||
|
||||
环境变量:
|
||||
MUSE_PORT 监听端口,默认 7861
|
||||
MUSE_MAX_CONCURRENT 最大并发推理数,默认 1(GPU 一次只能处理一个)
|
||||
@@ -11,8 +17,7 @@
|
||||
MUSE_AUDIO_MAX_MB 音频上传大小限制 MB,默认 20
|
||||
MUSE_DEFAULT_FPS 视频 fps 兜底值,默认 25.0
|
||||
MUSE_TEMP_DIR 临时文件目录,默认 /tmp/musetalk_$$
|
||||
MUSE_VIDEO_ENCODER 循环视频时的编码器:auto(默认,优先 h264_nvenc 兜底 libx264)/h264_nvenc/libx264
|
||||
MUSE_ENABLE_VIDEO_LOOP 驱动音频比视频长时是否循环视频补齐,默认 1(开启)
|
||||
MUSE_VIDEO_ENCODER 循环视频时的编码器(仅兜底):auto(默认)/h264_nvenc/libx264
|
||||
|
||||
接口:
|
||||
GET /health 健康检查 + GPU 显存信息
|
||||
@@ -59,11 +64,9 @@ class Config:
|
||||
audio_max_mb: int = int(_env("MUSE_AUDIO_MAX_MB", "20"))
|
||||
default_fps: float = float(_env("MUSE_DEFAULT_FPS", "25.0"))
|
||||
temp_dir: str = _env("MUSE_TEMP_DIR", f"/tmp/musetalk_{os.getpid()}")
|
||||
# 循环视频时编码器:auto 优先 h264_nvenc(RTX2060 支持),失败兜底 libx264
|
||||
# 循环视频时的编码器(仅当 MuseTalk 输出画面短于音频时的兜底)
|
||||
video_encoder: str = _env("MUSE_VIDEO_ENCODER", "auto") or "auto"
|
||||
# 驱动音频比视频长时循环视频补齐画面
|
||||
enable_video_loop: bool = _env("MUSE_ENABLE_VIDEO_LOOP", "1") not in ("0", "false", "False", "")
|
||||
# 判定音视频时长差异的容差(秒),避免 ffprobe 微小误差触发无谓的循环/重编码
|
||||
# 判定音视频时长差异的容差(秒)
|
||||
duration_epsilon: float = 0.25
|
||||
|
||||
|
||||
@@ -216,40 +219,34 @@ def _mux_video_with_audio(
|
||||
video_path: Path,
|
||||
audio_path: Path,
|
||||
output_path: Path,
|
||||
enable_video_loop: Optional[bool] = None,
|
||||
timeout: float = 300,
|
||||
) -> None:
|
||||
"""把无声画面视频与驱动音频封装为最终结果.
|
||||
|
||||
关键正确性要求:必须用 -map 0:v -map 1:a 显式指定取第一个输入(推理画面)的
|
||||
视频流和第二个输入(驱动音频 TTS)的音频流,禁止 ffmpeg 默认流选择行为
|
||||
(否则会把源视频自带音轨带进结果,口型与声音错位)。
|
||||
#1978 v2 架构:MuseTalk 已处理全量音频,输出视频时长=音频时长。
|
||||
此处仅做快速封装:-map 0:v:0 -map 1:a:0 强制取画面+驱动音频,
|
||||
-c:v copy 无损秒级封装(不重编码),-shortest 以较短流为准。
|
||||
|
||||
时长对齐:驱动音频比视频长时(TTS 15s vs 原视频 9s 很常见),用
|
||||
-stream_loop -1 循环视频画面到音频长度(NVENC 硬件重编码),-t 卡到音频时长;
|
||||
音频不超过视频时直接 -c:v copy 无损快封装,-shortest 以较短流为准。
|
||||
仅当 MuseTalk 输出画面短于音频时(极端兜底),才启用 -stream_loop + NVENC
|
||||
循环视频到音频长度。正常情况下走 copy 快速路径。
|
||||
"""
|
||||
video_duration = _get_media_duration(video_path)
|
||||
audio_duration = _get_media_duration(audio_path)
|
||||
|
||||
loop_enabled = Config.enable_video_loop if enable_video_loop is None else enable_video_loop
|
||||
need_loop = bool(
|
||||
loop_enabled
|
||||
and audio_duration > 0
|
||||
and video_duration > 0
|
||||
and audio_duration > video_duration + Config.duration_epsilon
|
||||
# 判断是否需要兜底循环(正常情况下 MuseTalk 输出已 >= 音频时长)
|
||||
need_loop_fallback = bool(
|
||||
audio_duration > 0 and video_duration > 0 and video_duration < audio_duration - Config.duration_epsilon
|
||||
)
|
||||
|
||||
if need_loop:
|
||||
if need_loop_fallback:
|
||||
# 兜底:MuseTalk 输出画面不足,循环补齐
|
||||
encoder = _pick_video_encoder()
|
||||
# preset 随编码器选择:h264_nvenc 用 p1-p7,libx264 用词形 preset
|
||||
preset = "p4" if encoder == "h264_nvenc" else "veryfast"
|
||||
logger.info(
|
||||
"音频(%.2fs)长于视频(%.2fs),循环视频并以 %s(%s) 重编码至音频长度",
|
||||
audio_duration,
|
||||
logger.warning(
|
||||
"MuseTalk 输出(%.2fs)短于音频(%.2fs),兜底循环视频以 %s 重编码",
|
||||
video_duration,
|
||||
audio_duration,
|
||||
encoder,
|
||||
preset,
|
||||
)
|
||||
|
||||
def build_cmd(enc: str, pre: str) -> list:
|
||||
@@ -282,14 +279,13 @@ def _mux_video_with_audio(
|
||||
try:
|
||||
_run_ffmpeg(build_cmd(encoder, preset), timeout=timeout)
|
||||
except RuntimeError:
|
||||
# NVENC 可能因驱动/占用失败,兜底 libx264 重试一次
|
||||
if encoder == "h264_nvenc":
|
||||
logger.warning("h264_nvenc 封装失败,回退 libx264 重试")
|
||||
logger.warning("h264_nvenc 兜底失败,回退 libx264 重试")
|
||||
_run_ffmpeg(build_cmd("libx264", "veryfast"), timeout=timeout)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
# 视频不短于音频:直接复制视频流,只把音频替换为驱动音频并转 AAC
|
||||
# 正常快速路径:-c:v copy 无损封装,仅替换音轨为驱动音频
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
@@ -348,20 +344,32 @@ def _run_inference(
|
||||
video_path: Path,
|
||||
audio_path: Path,
|
||||
output_path: Path,
|
||||
enable_video_loop: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""执行 MuseTalk 推理(可被子线程和测试独立调用).
|
||||
"""执行 MuseTalk 推理(v2 架构:全量音频直传,不在推理前 loop 视频).
|
||||
|
||||
#1978 性能修复核心:
|
||||
MuseTalk 原生支持长音频输入,内部会自动循环视频帧。
|
||||
我们只需把【原视频】和【全量音频】传给 MuseTalk,
|
||||
输出视频时长 = 音频时长(MuseTalk 自行处理帧循环)。
|
||||
禁止在推理前用 ffmpeg 循环视频(会导致慢 16 倍)。
|
||||
|
||||
实际部署时替换为 MuseTalk 真实推理逻辑。
|
||||
此处为示例实现:提取帧 → 生成无声画面 → 用驱动音频封装。
|
||||
|
||||
enable_video_loop: 驱动音频长于视频时是否循环视频;None 走全局配置。
|
||||
此处为示例实现:提取帧 → 模拟 MuseTalk 产出音频时长的无声画面 → 快速封装。
|
||||
"""
|
||||
fps = _get_video_fps(video_path)
|
||||
logger.info("视频 fps: %.2f", fps)
|
||||
audio_duration = _get_media_duration(audio_path)
|
||||
video_duration = _get_media_duration(video_path)
|
||||
logger.info(
|
||||
"推理开始: video=%.2fs, audio=%.2fs, fps=%.2f",
|
||||
video_duration,
|
||||
audio_duration,
|
||||
fps,
|
||||
)
|
||||
|
||||
frames_dir = video_path.parent / "frames"
|
||||
frames_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 1. 从原视频提取帧(仅原视频长度,不循环)
|
||||
_run_ffmpeg(
|
||||
[
|
||||
"ffmpeg",
|
||||
@@ -379,35 +387,83 @@ def _run_inference(
|
||||
if not frame_files:
|
||||
raise RuntimeError("未从视频中提取到帧")
|
||||
|
||||
# TODO: 替换为 MuseTalk 实际推理逻辑。
|
||||
# MuseTalk 真实产物是「无声画面视频」,音轨必须在封装阶段用驱动音频替换。
|
||||
# 2. 模拟 MuseTalk 推理:输入原视频帧 + 全量音频,输出音频时长的无声画面。
|
||||
# TODO: 替换为 MuseTalk 真实推理逻辑。
|
||||
# MuseTalk 真实调用示例(伪代码):
|
||||
# from musetalk import MuseTalkModel
|
||||
# model = MuseTalkModel(...)
|
||||
# silent_video = model.infer(video_path=video_path, audio_path=audio_path)
|
||||
# # MuseTalk 内部会循环视频帧匹配音频长度,输出时长=音频时长
|
||||
logger.warning("使用示例推理逻辑,未实际调用 MuseTalk 模型")
|
||||
|
||||
# 示例:从源视频生成无声画面(-an 丢弃原音轨),模拟 MuseTalk 推理产物。
|
||||
# 真实部署时 silent_video_path 应替换为 MuseTalk 输出的无声视频路径。
|
||||
# 示例:生成音频时长的无声画面(循环原视频帧到音频长度)
|
||||
# 真实部署时 silent_video_path 应替换为 MuseTalk 输出的无声视频路径
|
||||
silent_video_path = video_path.parent / "visual_silent.mp4"
|
||||
_run_ffmpeg(
|
||||
[
|
||||
|
||||
if audio_duration > video_duration + Config.duration_epsilon:
|
||||
# 音频更长:循环视频帧到音频长度(仅用于示例,真实 MuseTalk 内部处理)
|
||||
encoder = _pick_video_encoder()
|
||||
preset = "p4" if encoder == "h264_nvenc" else "veryfast"
|
||||
logger.info(
|
||||
"示例:循环视频帧到音频长度 %.2fs(真实 MuseTalk 内部处理,无需此步骤)",
|
||||
audio_duration,
|
||||
)
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-stream_loop",
|
||||
"-1",
|
||||
"-i",
|
||||
str(video_path),
|
||||
"-an",
|
||||
"-c:v",
|
||||
"libx264",
|
||||
encoder,
|
||||
"-preset",
|
||||
"veryfast",
|
||||
preset,
|
||||
"-t",
|
||||
f"{audio_duration:.3f}",
|
||||
str(silent_video_path),
|
||||
],
|
||||
timeout=300,
|
||||
)
|
||||
]
|
||||
try:
|
||||
_run_ffmpeg(cmd, timeout=300)
|
||||
except RuntimeError:
|
||||
if encoder == "h264_nvenc":
|
||||
cmd[cmd.index(encoder)] = "libx264"
|
||||
cmd[cmd.index(preset) + 1] = "veryfast"
|
||||
_run_ffmpeg(cmd, timeout=300)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
# 音频不长:直接生成无声视频(原视频长度)
|
||||
_run_ffmpeg(
|
||||
[
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-i",
|
||||
str(video_path),
|
||||
"-an",
|
||||
"-c:v",
|
||||
"libx264",
|
||||
"-preset",
|
||||
"veryfast",
|
||||
str(silent_video_path),
|
||||
],
|
||||
timeout=300,
|
||||
)
|
||||
|
||||
# 统一封装:显式 -map 取推理画面 + 驱动音频;音频更长时循环视频。
|
||||
_mux_video_with_audio(silent_video_path, audio_path, output_path, enable_video_loop=enable_video_loop)
|
||||
# 3. 快速封装:-map 取推理画面 + 驱动音频,-c:v copy 无损秒级封装
|
||||
# MuseTalk 输出已匹配音频长度,此处无需循环,仅替换音轨
|
||||
_mux_video_with_audio(silent_video_path, audio_path, output_path)
|
||||
|
||||
if not output_path.exists() or output_path.stat().st_size < 1024:
|
||||
raise RuntimeError("推理产物不存在或过小")
|
||||
|
||||
logger.info(
|
||||
"推理完成: output=%.2fs (audio=%.2fs)",
|
||||
_get_media_duration(output_path),
|
||||
audio_duration,
|
||||
)
|
||||
|
||||
|
||||
# ── 路由 ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -433,7 +489,10 @@ def health():
|
||||
|
||||
@app.route("/inference", methods=["POST"])
|
||||
def inference():
|
||||
"""推理请求:multipart form 包含 video 和 audio 文件."""
|
||||
"""推理请求:multipart form 包含 video 和 audio 文件.
|
||||
|
||||
#1978 v2:MuseTalk 直接处理全量音频,输出时长=音频时长,无需预处理循环。
|
||||
"""
|
||||
# 并发控制:检查锁
|
||||
if not inference_lock.acquire(blocking=False):
|
||||
return jsonify({"error": "GPU 正在处理其他任务,请稍后重试", "status": "busy"}), 503
|
||||
@@ -452,13 +511,6 @@ def inference():
|
||||
audio_file = request.files["audio"]
|
||||
task_id = request.form.get("task_id", f"task_{int(time.time())}")
|
||||
|
||||
# 可选:本次任务是否在音频长于视频时循环视频(缺省走全局配置)
|
||||
loop_param = request.form.get("enable_video_loop")
|
||||
if loop_param is not None:
|
||||
task_enable_loop = loop_param.strip() not in ("0", "false", "False", "")
|
||||
else:
|
||||
task_enable_loop = None
|
||||
|
||||
# 文件大小检查
|
||||
err = _check_file_size(video_file, Config.video_max_mb, "视频")
|
||||
if err:
|
||||
@@ -482,9 +534,6 @@ def inference():
|
||||
# 更新当前任务信息
|
||||
current_task["task_id"] = task_id
|
||||
current_task["start_time"] = time.time()
|
||||
|
||||
# 启动推理进程(用 subprocess 包装,便于超时终止)
|
||||
# 此处直接调用推理函数,实际可改为 subprocess 调用外部脚本
|
||||
current_task["process"] = "inference_thread" # 标记为运行中
|
||||
|
||||
# 在线程中运行推理(支持超时)
|
||||
@@ -492,7 +541,7 @@ def inference():
|
||||
|
||||
def inference_thread():
|
||||
try:
|
||||
_run_inference(video_path, audio_path, output_path, enable_video_loop=task_enable_loop)
|
||||
_run_inference(video_path, audio_path, output_path)
|
||||
except Exception as exc:
|
||||
result_container["error"] = str(exc)
|
||||
|
||||
@@ -577,24 +626,20 @@ def main():
|
||||
Path(Config.temp_dir).mkdir(parents=True, exist_ok=True)
|
||||
logger.info("临时目录: %s", Config.temp_dir)
|
||||
|
||||
# 打印配置
|
||||
logger.info("=" * 60)
|
||||
logger.info("MuseTalk Flask Server 启动")
|
||||
logger.info(" 端口: %d", Config.port)
|
||||
logger.info(" 最大并发: %d", Config.max_concurrent)
|
||||
logger.info(" 推理超时: %.0fs", Config.inference_timeout)
|
||||
logger.info(" 视频大小限制: %dMB", Config.video_max_mb)
|
||||
logger.info(" 音频大小限制: %dMB", Config.audio_max_mb)
|
||||
logger.info(" 默认 fps: %.1f", Config.default_fps)
|
||||
logger.info(" 视频编码器: %s", Config.video_encoder)
|
||||
logger.info(" 音频长于视频时循环视频: %s", Config.enable_video_loop)
|
||||
logger.info("=" * 60)
|
||||
|
||||
# 检查 GPU
|
||||
gpu_info = _get_gpu_info()
|
||||
logger.info("GPU 信息: %s", gpu_info)
|
||||
logger.info(
|
||||
"GPU: %s (显存 %dMB / %dMB)",
|
||||
gpu_info["gpu_name"],
|
||||
gpu_info["memory_used_mb"],
|
||||
gpu_info["memory_total_mb"],
|
||||
)
|
||||
logger.info(
|
||||
"启动 MuseTalk Server: port=%d, timeout=%.0fs, max_concurrent=%d",
|
||||
Config.port,
|
||||
Config.inference_timeout,
|
||||
Config.max_concurrent,
|
||||
)
|
||||
|
||||
# 启动 Flask(threaded=True 处理并发请求)
|
||||
app.run(host="0.0.0.0", port=Config.port, threaded=True)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user