D144 · 口型同步三路由上线: Wav2Lip→火山云端→Edge-TTS
- lipsync-adapter.py: 新增云端路由,Wav2Lip不可用时自动尝试火山改口型API - volce-lipsync-adapter.py: 火山引擎口型同步适配器(STUB),需开通智能视觉服务 - 当前状态: Wav2Lip需CUDA GPU(本地不可用),火山需AK/SK配置,TTS配音可用 铸渊 ICE-GL-ZY001 · D144 · 2026-06-24
This commit is contained in:
parent
6714d54a8b
commit
bc2d56ccb2
@ -31,12 +31,13 @@ sys.path.insert(0, str(PROJECT_ROOT / "engines"))
|
||||
|
||||
|
||||
class LipSyncAdapter:
|
||||
"""口型适配器"""
|
||||
"""口型适配器 — 支持多路由: Wav2Lip(本地GPU) → 火山改口型(云端) → 回退"""
|
||||
|
||||
def __init__(self, wav2lip_path=None, allow_fallback=False):
|
||||
self.wav2lip_path = wav2lip_path or PROJECT_ROOT / "tools" / "Wav2Lip"
|
||||
self.allow_fallback = allow_fallback
|
||||
self.available = self._check_wav2lip()
|
||||
self._cloud_available = None
|
||||
|
||||
def _check_wav2lip(self):
|
||||
"""检查 Wav2Lip 是否可用"""
|
||||
@ -93,12 +94,27 @@ class LipSyncAdapter:
|
||||
# 确保输出目录存在
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 方法1: Wav2Lip
|
||||
# 方法1: Wav2Lip (本地GPU)
|
||||
if self.available:
|
||||
print(f" 🔧 使用 Wav2Lip...")
|
||||
result = self._run_wav2lip(video_path, audio_path, output_path)
|
||||
return result
|
||||
|
||||
# 方法2: 火山引擎云端改口型
|
||||
cloud = self._check_cloud()
|
||||
if cloud["available"]:
|
||||
print(f" ☁️ 尝试火山改口型...")
|
||||
result = self._run_cloud_lipsync(video_path, audio_path, output_path)
|
||||
if result.get("success"):
|
||||
return result
|
||||
print(f" ⚠️ 云端改口型失败: {result.get('error')}")
|
||||
|
||||
# 方法3: Edge-TTS 配音替代(角色不说话时可用)
|
||||
print(f" 💡 当前可用路由:")
|
||||
print(f" Route1 Wav2Lip: {'✅' if self.available else '❌ 需要CUDA GPU'}")
|
||||
print(f" Route2 火山改口型: {'STUB 需开通' if not cloud.get('available') else '可用'}")
|
||||
print(f" Route3 Edge-TTS: ✅ (voice-emotion-compiler已通过)")
|
||||
|
||||
fallback_enabled = self.allow_fallback if allow_fallback is None else allow_fallback
|
||||
if not fallback_enabled:
|
||||
print(f" ❌ Wav2Lip 不可用,口型同步未执行")
|
||||
@ -124,6 +140,37 @@ class LipSyncAdapter:
|
||||
"warning": "Wav2Lip 不可用,口型未同步。该文件只能用于调试占位。"
|
||||
}
|
||||
|
||||
def _check_cloud(self):
|
||||
"""检查云端改口型服务是否可用"""
|
||||
if self._cloud_available is not None:
|
||||
return self._cloud_available
|
||||
try:
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
'volce',
|
||||
str(PROJECT_ROOT / 'engines' / 'volce-lipsync-adapter.py')
|
||||
)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
self._cloud_available = mod.check_available()
|
||||
except Exception as e:
|
||||
self._cloud_available = {"available": False, "reason": f"云端适配器不可用: {e}"}
|
||||
return self._cloud_available
|
||||
|
||||
def _run_cloud_lipsync(self, video_path, audio_path, output_path):
|
||||
"""通过火山引擎云端改口型"""
|
||||
try:
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
'volce',
|
||||
str(PROJECT_ROOT / 'engines' / 'volce-lipsync-adapter.py')
|
||||
)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod.submit_lipsync(video_path, audio_path)
|
||||
except Exception as e:
|
||||
return {"success": False, "error": f"云端适配器调用失败: {e}", "method": "cloud-stub"}
|
||||
|
||||
def _run_wav2lip(self, video_path, audio_path, output_path):
|
||||
"""运行 Wav2Lip"""
|
||||
import subprocess
|
||||
|
||||
104
video-ai-system/engines/volce-lipsync-adapter.py
Normal file
104
video-ai-system/engines/volce-lipsync-adapter.py
Normal file
@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
volce-lipsync-adapter.py · 火山引擎口型同步适配器
|
||||
D144 · 铸渊 ICE-GL-ZY001
|
||||
|
||||
对接火山引擎智能视觉服务"视频改口型"API。
|
||||
需要预先开通服务并获取 AK/SK。
|
||||
|
||||
文档:
|
||||
https://www.volcengine.com/docs/85128/1462622
|
||||
|
||||
依赖:
|
||||
pip install volcengine-python-sdk
|
||||
|
||||
当前状态: STUB — 需要开通服务并配置 AK/SK 后才能激活
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
SECRETS_FILE = "/Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env"
|
||||
|
||||
|
||||
def _load_secrets():
|
||||
"""加载本地密钥文件"""
|
||||
secrets = {}
|
||||
if os.path.isfile(SECRETS_FILE):
|
||||
with open(SECRETS_FILE) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
k, v = line.split("=", 1)
|
||||
secrets[k.strip()] = v.strip().strip('"').strip("'")
|
||||
return secrets
|
||||
|
||||
|
||||
def check_available():
|
||||
"""检查火山口型同步服务是否可用"""
|
||||
secrets = _load_secrets()
|
||||
ak = secrets.get("VOLCE_ACCESS_KEY", "")
|
||||
sk = secrets.get("VOLCE_SECRET_KEY", "")
|
||||
|
||||
if not ak or not sk:
|
||||
return {
|
||||
"available": False,
|
||||
"reason": "未配置 VOLCE_ACCESS_KEY / VOLCE_SECRET_KEY",
|
||||
"help": "请前往火山引擎控制台 → 访问控制 → 创建Access Key → 写入本地密钥文件"
|
||||
}
|
||||
|
||||
return {
|
||||
"available": True,
|
||||
"method": "volce-video-lipsync"
|
||||
}
|
||||
|
||||
|
||||
def submit_lipsync(video_path, audio_path, source_lang="zh", target_lang="zh"):
|
||||
"""
|
||||
提交视频改口型任务
|
||||
|
||||
注意: 当前为 STUB 实现。火山引擎视频改口型 API 需要:
|
||||
1. 开通智能视觉服务
|
||||
2. 获取独立 AK/SK(不是 JIMENG_API_KEY)
|
||||
3. 上传视频到火山 TOS 对象存储
|
||||
4. 提交异步任务并轮询结果
|
||||
|
||||
:param video_path: 输入视频
|
||||
:param audio_path: 对白音频
|
||||
:param source_lang: 源语言
|
||||
:param target_lang: 目标语言
|
||||
:return: {success, output_path, method}
|
||||
"""
|
||||
status = check_available()
|
||||
if not status["available"]:
|
||||
return {
|
||||
"success": False,
|
||||
"error": status["reason"],
|
||||
"method": "volce-stub"
|
||||
}
|
||||
|
||||
# STUB: 需要完整开通后才能调用
|
||||
return {
|
||||
"success": False,
|
||||
"error": "火山视频改口型 API 尚未完整接入(STUB)",
|
||||
"method": "volce-stub",
|
||||
"next_steps": [
|
||||
"开通火山引擎智能视觉服务",
|
||||
"配置 VOLCE_ACCESS_KEY / VOLCE_SECRET_KEY",
|
||||
"实现视频上传到 TOS 的逻辑",
|
||||
"实现异步任务提交和轮询"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = check_available()
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
Loading…
x
Reference in New Issue
Block a user