fix: verify and guard video ai modules
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled

This commit is contained in:
冰朔 2026-06-24 13:12:03 +08:00
parent eab0f0e0d4
commit 9474086356
10 changed files with 437 additions and 50 deletions

View File

@ -3,7 +3,7 @@
> HLDP://video-ai-system/CURRENT > HLDP://video-ai-system/CURRENT
> 类型: 子系统官方置信入口 · 每次进入视频AI系统先读 > 类型: 子系统官方置信入口 · 每次进入视频AI系统先读
> 创建: D140 · 2026-06-22 · Codex收口 > 创建: D140 · 2026-06-22 · Codex收口
> 更新: D143 · 2026-06-23 · preview-002复盘·制片厂化重启 > 更新: D144 · 2026-06-24 · 8模块验收·生产阻塞项确认
> 铸渊 ICE-GL-ZY001 · 冰朔 TCS-0002∞ > 铸渊 ICE-GL-ZY001 · 冰朔 TCS-0002∞
> 国作登字-2026-A-00037559 > 国作登字-2026-A-00037559
@ -133,6 +133,7 @@ JZAO外置盘: 产物存放地,不是状态主控。
| 苏白TTS配音 | ✅ 工程测试通过 | `video-ai-system/outputs/tests/TEST-TTS-001-SUBAI-RUN-001-REPORT.hdlp` | | 苏白TTS配音 | ✅ 工程测试通过 | `video-ai-system/outputs/tests/TEST-TTS-001-SUBAI-RUN-001-REPORT.hdlp` |
| 模型/成本路线 | ✅ 已建立 | `video-ai-system/knowledge/D144-MODEL-COST-ROUTE.hdlp` | | 模型/成本路线 | ✅ 已建立 | `video-ai-system/knowledge/D144-MODEL-COST-ROUTE.hdlp` |
| 腾讯AI开发交接 | ✅ 已建立 | `video-ai-system/plans/D144-TENCENT-AI-DEV-HANDOFF.hdlp` | | 腾讯AI开发交接 | ✅ 已建立 | `video-ai-system/plans/D144-TENCENT-AI-DEV-HANDOFF.hdlp` |
| 腾讯AI 8模块代码 | 🟡 部分通过 | `video-ai-system/experience/D144-8-MODULE-VERIFICATION.hdlp` · 框架入仓但主角资产、多参考图、口型同步、EP01 CLI仍非生产可用 |
| 广告牌资产 | ✅ 草案 | `video-ai-system/assets/props/PROP-FREE-AD-BOARD/manifest.hdlp` | | 广告牌资产 | ✅ 草案 | `video-ai-system/assets/props/PROP-FREE-AD-BOARD/manifest.hdlp` |
| 广告牌文字贴图 | ✅ PNG已生成 | `video-ai-system/assets/props/PROP-FREE-AD-BOARD/texture/free-ad-board-texture.png` | | 广告牌文字贴图 | ✅ PNG已生成 | `video-ai-system/assets/props/PROP-FREE-AD-BOARD/texture/free-ad-board-texture.png` |
| 百宗会场景资产 | ✅ 草案 | `video-ai-system/assets/envs/ENV-002-Baizonghui/manifest.hdlp` | | 百宗会场景资产 | ✅ 草案 | `video-ai-system/assets/envs/ENV-002-Baizonghui/manifest.hdlp` |
@ -149,10 +150,10 @@ JZAO外置盘: 产物存放地,不是状态主控。
当前正式任务: 当前正式任务:
``` ```
D143-VIDEO-AI-RESET: D144-VIDEO-AI-PRODUCTION-BLOCKERS:
从 preview-002 失败复盘和《渔乡守真心》样片对标开始,重新建立影视制作流程知识库、素材库、模型准入、对白口型测试、竖屏剪辑模板和经验闭环 腾讯AI 8模块已入仓并完成第一轮验收但不能按“全部开发完成”验收
不直接生成 preview-003 当前优先级仍是字幕商业标准工具链、真实口型同步、剧本原文锁定、主角资产包真实生成、多参考/合成一致性路线
第一个突破测试是 2-3 秒“苏白说一句话”,第二个突破测试是“天道宗牌匾跨镜一致” 任何模块不能把占位输出、复制输出、未验证API能力标记成生产成功
``` ```
--- ---

View File

@ -19,14 +19,23 @@ import os
import sys import sys
import json import json
import argparse import argparse
import shutil
from pathlib import Path from pathlib import Path
# 添加项目根目录到路径 # 添加项目根目录到路径
PROJECT_ROOT = Path(__file__).parent.parent.parent PROJECT_ROOT = Path(__file__).parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT / "engines")) sys.path.insert(0, str(PROJECT_ROOT / "engines"))
from image_api_adapter import generate_image, save_image try:
from hldp_prompt import expand_prompt from image_api_adapter import generate_image
except Exception as exc:
print(f"⚠️ Python图片生成适配器未接通: {exc}")
generate_image = None
try:
from hldp_prompt import expand_prompt
except Exception:
expand_prompt = None
class CharHeroDesignPacker: class CharHeroDesignPacker:
@ -115,6 +124,14 @@ class CharHeroDesignPacker:
print(f" 参考图: {candidate}") print(f" 参考图: {candidate}")
print(f" 提示词: {prompt[:100]}...") print(f" 提示词: {prompt[:100]}...")
if generate_image is None:
print(" ⚠️ 当前仓库只有 JS 图片适配器 image-api-adapter.js未提供 Python generate_image。")
print(" ⚠️ 暂不能生成新主角资产;仅将候选图复制到 candidates/ 供人工/后续工具处理。")
fallback_path = self.candidates_dir / output_name
shutil.copy2(candidate, fallback_path)
print(f" 🟡 已复制候选图: {fallback_path}")
return None
try: try:
result_path = generate_image( result_path = generate_image(
prompt=prompt, prompt=prompt,
@ -203,24 +220,24 @@ class CharHeroDesignPacker:
prompts = { prompts = {
"front_half_body": f""" "front_half_body": f"""
{base_desc.get('visual_description', '中国古代修仙少年16岁白色长发,蓝色眼睛')} {base_desc.get('visual_description', '中国古代修仙少年16岁主角辨识度强')}
正面半身像胸部以上面部清晰眼神坚定 正面半身像胸部以上面部清晰眼神坚定
3D动画风格皮克斯风格统一渲染风格 3D国风漫剧风格统一渲染风格
高清8K最佳质量 高清8K最佳质量
""".strip(), """.strip(),
"side_face": f""" "side_face": f"""
{base_desc.get('visual_description', '中国古代修仙少年')} {base_desc.get('visual_description', '中国古代修仙少年')}
侧脸45度能看到面部轮廓和发型 侧脸45度能看到面部轮廓和发型
3D动画风格皮克斯风格统一渲染风格 3D国风漫剧风格统一渲染风格
高清8K最佳质量 高清8K最佳质量
""".strip(), """.strip(),
"full_body_costume": f""" "full_body_costume": f"""
{base_desc.get('visual_description', '中国古代修仙少年')} {base_desc.get('visual_description', '中国古代修仙少年')}
全身像站立姿势完整展示服装细节 全身像站立姿势完整展示服装细节
白色内衬蓝色外袍黑色腰带棕色靴子 服装细节必须来自角色资产清单不得自由改色
3D动画风格皮克斯风格统一渲染风格 3D国风漫剧风格统一渲染风格
高清8K最佳质量 高清8K最佳质量
""".strip(), """.strip(),
@ -229,7 +246,7 @@ class CharHeroDesignPacker:
表情表网格布局包含: 表情表网格布局包含:
平静微笑大笑生气惊讶悲伤 平静微笑大笑生气惊讶悲伤
每个表情单独一格统一光照和背景 每个表情单独一格统一光照和背景
3D动画风格皮克斯风格 3D国风漫剧风格
高清8K最佳质量 高清8K最佳质量
""".strip(), """.strip(),
} }
@ -263,7 +280,7 @@ class CharHeroDesignPacker:
if path: if path:
results["expression_sheet"] = path results["expression_sheet"] = path
# 更新 manifest # 更新 manifest。只有四项资产都真实生成时才可进入 APPROVED。
self._update_manifest(results) self._update_manifest(results)
print(f"\n✅ 资产包生成完成!") print(f"\n✅ 资产包生成完成!")
@ -276,10 +293,20 @@ class CharHeroDesignPacker:
"""更新 manifest.hdlp""" """更新 manifest.hdlp"""
print(f"\n📝 更新 manifest.hdlp...") print(f"\n📝 更新 manifest.hdlp...")
required_assets = {"front_half_body", "side_face", "full_body_costume", "expression_sheet"}
is_complete = required_assets.issubset(results.keys())
approval_status = "APPROVED" if is_complete else "DRAFT_GENERATED"
type_label = "已批准" if is_complete else "待质检"
lock_note = (
"⊢ 资产已批准,可用于成片镜头。"
if is_complete
else "⊢ 资产包尚未完整生成/质检,禁止作为成片锁定资产。"
)
manifest_content = f"""# 资产清单 · {self.character_id} manifest_content = f"""# 资产清单 · {self.character_id}
> HLDP://video-ai-system/assets/characters/{self.character_id}/manifest > HLDP://video-ai-system/assets/characters/{self.character_id}/manifest
> 类型: 角色资产 · 已批准 > 类型: 角色资产 · {type_label}
> 建立: D143 · 2026-06-23 > 建立: D143 · 2026-06-23
> 更新: D144 · 2026-06-24 > 更新: D144 · 2026-06-24
> 项目: 付费才能修仙 · EP01 > 项目: 付费才能修仙 · EP01
@ -289,7 +316,7 @@ class CharHeroDesignPacker:
## 状态 ## 状态
``` ```
approval_status: APPROVED approval_status: {approval_status}
asset_type: character asset_type: character
canonical_id: {self.character_id} canonical_id: {self.character_id}
canonical_name: 苏白 canonical_name: 苏白
@ -311,19 +338,19 @@ expression_sheet: {results.get("expression_sheet", "NOT_GENERATED")}
## 视觉锁 ## 视觉锁
``` ```
face_shape: 少年脸柔和轮廓白色长发 face_shape: TODO_FROM_APPROVED_ASSET
hair_style: 白色长发束发有发带 hair_style: TODO_FROM_APPROVED_ASSET
costume: 白色内衬 + 蓝色外袍 + 黑色腰带 + 棕色靴子 costume: TODO_FROM_APPROVED_ASSET
age_band: 16 age_band: 16
render_style: 3D动画皮克斯风格明亮色彩 render_style: 3D国风漫剧
color_palette: color_palette: TODO_FROM_APPROVED_ASSET
``` ```
--- ---
## 锁定 ## 锁定
资产已批准可用于成片镜头 {lock_note}
禁止使用 candidates/ rejected/ 中的图片作为最终资产 禁止使用 candidates/ rejected/ 中的图片作为最终资产
""" """

View File

@ -270,6 +270,14 @@ class CharacterDistinctivenessQC:
return results return results
def save_report(self, result, output_path):
"""保存单张或批量评估报告"""
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print(f"\n 报告已保存: {output_path}")
def _generate_batch_report(self, results): def _generate_batch_report(self, results):
"""生成批量评估报告""" """生成批量评估报告"""
print(f"\n📊 批量评估报告") print(f"\n📊 批量评估报告")
@ -301,6 +309,7 @@ def main():
parser.add_argument("--character", type=str, default="CHAR-003-SuBai", parser.add_argument("--character", type=str, default="CHAR-003-SuBai",
help="角色ID (默认: CHAR-003-SuBai)") help="角色ID (默认: CHAR-003-SuBai)")
parser.add_argument("--batch", type=str, help="批量评估目录") parser.add_argument("--batch", type=str, help="批量评估目录")
parser.add_argument("--output", type=str, help="输出 JSON 报告路径")
args = parser.parse_args() args = parser.parse_args()
@ -314,9 +323,13 @@ def main():
result = qc.evaluate_image(args.image) result = qc.evaluate_image(args.image)
print(f"\n📋 评估详情:") print(f"\n📋 评估详情:")
print(json.dumps(result, ensure_ascii=False, indent=2)) print(json.dumps(result, ensure_ascii=False, indent=2))
if args.output:
qc.save_report(result, args.output)
elif args.batch: elif args.batch:
results = qc.evaluate_batch(args.batch) results = qc.evaluate_batch(args.batch)
if args.output:
qc.save_report(results, args.output)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -33,8 +33,9 @@ sys.path.insert(0, str(PROJECT_ROOT / "engines"))
class LipSyncAdapter: class LipSyncAdapter:
"""口型适配器""" """口型适配器"""
def __init__(self, wav2lip_path=None): def __init__(self, wav2lip_path=None, allow_fallback=False):
self.wav2lip_path = wav2lip_path or PROJECT_ROOT / "tools" / "Wav2Lip" self.wav2lip_path = wav2lip_path or PROJECT_ROOT / "tools" / "Wav2Lip"
self.allow_fallback = allow_fallback
self.available = self._check_wav2lip() self.available = self._check_wav2lip()
def _check_wav2lip(self): def _check_wav2lip(self):
@ -53,7 +54,7 @@ class LipSyncAdapter:
print(f"✅ Wav2Lip 已安装: {self.wav2lip_path}") print(f"✅ Wav2Lip 已安装: {self.wav2lip_path}")
return True return True
def sync_lips(self, video_path, audio_path, output_path=None): def sync_lips(self, video_path, audio_path, output_path=None, allow_fallback=None):
""" """
口型同步 口型同步
@ -98,9 +99,20 @@ class LipSyncAdapter:
result = self._run_wav2lip(video_path, audio_path, output_path) result = self._run_wav2lip(video_path, audio_path, output_path)
return result return result
# 方法2: 回退 (不处理,只复制视频) fallback_enabled = self.allow_fallback if allow_fallback is None else allow_fallback
print(f" ⚠️ Wav2Lip 不可用,回退到不处理模式") if not fallback_enabled:
print(f" 💡 提示: 安装 Wav2Lip 以获得口型同步能力") print(f" ❌ Wav2Lip 不可用,口型同步未执行")
print(f" 💡 如需调试占位输出,可显式传入 --allow-fallback-copy")
return {
"success": False,
"error": "Wav2Lip 不可用,未执行真实口型同步",
"method": "not-run",
"requires": "Install Wav2Lip and checkpoint wav2lip_gan.pth"
}
# 调试回退: 不处理,只复制视频。必须显式开启,避免生产线假阳性。
print(f" ⚠️ Wav2Lip 不可用,按 --allow-fallback-copy 复制原视频")
print(f" 💡 注意: 该输出不是口型同步成品")
import shutil import shutil
shutil.copy2(video_path, output_path) shutil.copy2(video_path, output_path)
@ -109,7 +121,7 @@ class LipSyncAdapter:
"success": True, "success": True,
"output_path": str(output_path), "output_path": str(output_path),
"method": "fallback(copy)", "method": "fallback(copy)",
"warning": "Wav2Lip 不可用,口型未同步。请安装 Wav2Lip" "warning": "Wav2Lip 不可用,口型未同步。该文件只能用于调试占位"
} }
def _run_wav2lip(self, video_path, audio_path, output_path): def _run_wav2lip(self, video_path, audio_path, output_path):
@ -247,6 +259,8 @@ def main():
parser.add_argument("--batch", type=str, help="批量处理配置文件 (JSON)") parser.add_argument("--batch", type=str, help="批量处理配置文件 (JSON)")
parser.add_argument("--wav2lip-path", type=str, help="Wav2Lip 安装路径") parser.add_argument("--wav2lip-path", type=str, help="Wav2Lip 安装路径")
parser.add_argument("--check-sync", action="store_true", help="检查口型同步质量") parser.add_argument("--check-sync", action="store_true", help="检查口型同步质量")
parser.add_argument("--allow-fallback-copy", action="store_true",
help="仅调试用: Wav2Lip 不可用时复制原视频并标记为 fallback")
args = parser.parse_args() args = parser.parse_args()
@ -276,7 +290,7 @@ def main():
output_dir = config.get("output_dir", "./outputs/lipsync/") output_dir = config.get("output_dir", "./outputs/lipsync/")
adapter = LipSyncAdapter(args.wav2lip_path) adapter = LipSyncAdapter(args.wav2lip_path, allow_fallback=args.allow_fallback_copy)
results = adapter.batch_sync(video_audio_pairs, output_dir) results = adapter.batch_sync(video_audio_pairs, output_dir)
sys.exit(0) sys.exit(0)
@ -285,7 +299,7 @@ def main():
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
adapter = LipSyncAdapter(args.wav2lip_path) adapter = LipSyncAdapter(args.wav2lip_path, allow_fallback=args.allow_fallback_copy)
result = adapter.sync_lips(args.video, args.audio, args.output) result = adapter.sync_lips(args.video, args.audio, args.output)
if result["success"]: if result["success"]:

View File

@ -30,34 +30,43 @@ from datetime import datetime
PROJECT_ROOT = Path(__file__).parent.parent PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT / "engines")) sys.path.insert(0, str(PROJECT_ROOT / "engines"))
# 从环境变量加载 API 配置
def load_api_config(): def load_api_config():
""" video-ai-system/.env 加载配置""" """仓库 .env、本机密钥文件和环境变量加载配置"""
config = {} config = {}
env_file = PROJECT_ROOT / ".env" env_files = [
if env_file.exists(): PROJECT_ROOT / ".env",
with open(env_file, "r") as f: Path.home() / "guanghulab-local-secrets" / "video-ai-system.env",
]
for env_file in env_files:
if not env_file.exists():
continue
with open(env_file, "r", encoding="utf-8") as f:
for line in f: for line in f:
line = line.strip() line = line.strip()
if line and not line.startswith("#"): if line and not line.startswith("#"):
key, _, val = line.partition("=") key, _, val = line.partition("=")
config[key.strip()] = val.strip() config[key.strip()] = val.strip().strip('"').strip("'")
for key in ["JIMENG_API_KEY", "JIMENG_BASE_URL", "JIMENG_MODEL"]:
if os.environ.get(key):
config[key] = os.environ[key]
return config return config
class MultiReferenceVideoAdapter: class MultiReferenceVideoAdapter:
"""多参考图视频适配器""" """多参考图视频适配器"""
def __init__(self): def __init__(self, live_capability_check=False, allow_single_reference_fallback=False):
self.config = load_api_config() self.config = load_api_config()
self.api_key = self.config.get("JIMENG_API_KEY", "") self.api_key = self.config.get("JIMENG_API_KEY", "")
self.base_url = self.config.get("JIMENG_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3") self.base_url = self.config.get("JIMENG_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3")
self.model = self.config.get("JIMENG_MODEL", "doubao-seedance-2-0-260128") self.model = self.config.get("JIMENG_MODEL", "doubao-seedance-2-0-260128")
self.live_capability_check = live_capability_check
self.allow_single_reference_fallback = allow_single_reference_fallback
# API 能力探测结果缓存 # API 能力探测结果缓存
self._api_capabilities = None self._api_capabilities = None
def check_api_capabilities(self): def check_api_capabilities(self, live=None):
""" """
检查 API 是否支持多参考图 检查 API 是否支持多参考图
返回: { 返回: {
@ -70,6 +79,7 @@ class MultiReferenceVideoAdapter:
if self._api_capabilities: if self._api_capabilities:
return self._api_capabilities return self._api_capabilities
live = self.live_capability_check if live is None else live
print("🔍 检查 Seedance API 多参考图支持...") print("🔍 检查 Seedance API 多参考图支持...")
# 根据 Volcengine 官方文档 (https://www.volcengine.com/docs/82379/1520757) # 根据 Volcengine 官方文档 (https://www.volcengine.com/docs/82379/1520757)
@ -83,8 +93,17 @@ class MultiReferenceVideoAdapter:
# { type: "image_url", image_url: { url: "data:image/png;base64,..." } }, # 第二张参考图 # { type: "image_url", image_url: { url: "data:image/png;base64,..." } }, # 第二张参考图
# ] # ]
# 实际测试: 尝试提交一个包含2张参考图的请求看是否报错 if not live:
test_result = self._test_multi_reference() test_result = {
"multi_reference_supported": False,
"max_references": 1,
"supported_types": ["image_url"],
"status": "unverified",
"details": "未执行付费/联网实测;需要 --live-check 才会提交真实 API 探测任务",
"requires_live_check": True
}
else:
test_result = self._test_multi_reference()
self._api_capabilities = test_result self._api_capabilities = test_result
return test_result return test_result
@ -94,6 +113,15 @@ class MultiReferenceVideoAdapter:
实际测试 API 是否支持多参考图 实际测试 API 是否支持多参考图
方法: 提交一个测试请求包含2张参考图观察响应 方法: 提交一个测试请求包含2张参考图观察响应
""" """
if not self.api_key:
return {
"multi_reference_supported": False,
"max_references": 1,
"supported_types": ["image_url"],
"details": "未找到 JIMENG_API_KEY无法执行 live check",
"status": "missing_api_key"
}
# 构造一个最小测试请求 # 构造一个最小测试请求
test_prompt = "test multi-reference support" test_prompt = "test multi-reference support"
@ -216,7 +244,16 @@ class MultiReferenceVideoAdapter:
print(f" 2. 后期合成牌匾/场景 (平面追踪 + 贴图)") print(f" 2. 后期合成牌匾/场景 (平面追踪 + 贴图)")
print(f" 3. 或使用可灵生成角色Seedance 生成场景,后期合成") print(f" 3. 或使用可灵生成角色Seedance 生成场景,后期合成")
# 回退: 只用第一张参考图 if not self.allow_single_reference_fallback:
return {
"success": False,
"method": "not-run",
"error": "多参考图能力未确认或不支持,未自动回退单参考图以避免误导一致性测试",
"capabilities": capabilities,
"next_step": "使用 --live-check 验证多参考图,或显式传入 --allow-single-reference-fallback 做调试占位"
}
# 回退: 只用第一张参考图。必须显式开启,避免误导牌匾/场景一致性判断。
warning = "API不支持多参考图已回退到单参考图模式。牌匾/场景一致性需要后期合成。" warning = "API不支持多参考图已回退到单参考图模式。牌匾/场景一致性需要后期合成。"
print(f"\n 🔄 回退: 使用第一张参考图生成...") print(f"\n 🔄 回退: 使用第一张参考图生成...")
@ -336,6 +373,10 @@ class MultiReferenceVideoAdapter:
def main(): def main():
parser = argparse.ArgumentParser(description="MULTI-REFERENCE-VIDEO-ADAPTER") parser = argparse.ArgumentParser(description="MULTI-REFERENCE-VIDEO-ADAPTER")
parser.add_argument("--check-api", action="store_true", help="检查API多参考图支持") parser.add_argument("--check-api", action="store_true", help="检查API多参考图支持")
parser.add_argument("--live-check", action="store_true",
help="执行真实 API 探测请求,可能产生费用")
parser.add_argument("--allow-single-reference-fallback", action="store_true",
help="多参考图不可用时允许单参考图调试占位")
parser.add_argument("--prompt", type=str, help="提示词") parser.add_argument("--prompt", type=str, help="提示词")
parser.add_argument("--references", type=str, nargs="+", help="参考图路径列表") parser.add_argument("--references", type=str, nargs="+", help="参考图路径列表")
parser.add_argument("--output", type=str, help="输出路径") parser.add_argument("--output", type=str, help="输出路径")
@ -344,10 +385,13 @@ def main():
args = parser.parse_args() args = parser.parse_args()
adapter = MultiReferenceVideoAdapter() adapter = MultiReferenceVideoAdapter(
live_capability_check=args.live_check,
allow_single_reference_fallback=args.allow_single_reference_fallback
)
if args.check_api: if args.check_api:
capabilities = adapter.check_api_capabilities() capabilities = adapter.check_api_capabilities(live=args.live_check)
print(f"\n📊 API 能力报告:") print(f"\n📊 API 能力报告:")
print(f" 多参考图支持: {capabilities['multi_reference_supported']}") print(f" 多参考图支持: {capabilities['multi_reference_supported']}")
print(f" 最大参考图数: {capabilities['max_references']}") print(f" 最大参考图数: {capabilities['max_references']}")

View File

@ -585,7 +585,12 @@ def main():
print(f"\n⚠️ QC 跳过 (依赖不可用)") print(f"\n⚠️ QC 跳过 (依赖不可用)")
sys.exit(0) sys.exit(0)
else: else:
print(f"\n❌ QC 失败: {result.get('error', 'Unknown error')}") if result.get("error"):
print(f"\n❌ QC 失败: {result['error']}")
else:
issues = result.get("issues") or []
issue_text = "".join(issues) if issues else "未通过阈值"
print(f"\n❌ QC 未通过: {issue_text}")
sys.exit(1) sys.exit(1)

View File

@ -20,17 +20,24 @@ import os
import sys import sys
import json import json
import argparse import argparse
import importlib.util
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
PROJECT_ROOT = Path(__file__).parent.parent PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT / "engines")) sys.path.insert(0, str(PROJECT_ROOT / "engines"))
# 导入现有的 TTS 引擎 # 导入现有的 TTS 引擎。文件名是 tts-engine.py不能用普通 import。
tts_engine_path = PROJECT_ROOT / "engines" / "tts-engine.py"
try: try:
from tts_engine import generate_speech, generate_by_character, load_voice_config spec = importlib.util.spec_from_file_location("tts_engine", tts_engine_path)
except ImportError: tts_engine = importlib.util.module_from_spec(spec)
print("⚠️ 无法导入 tts-engine将使用简化模式") spec.loader.exec_module(tts_engine)
generate_speech = tts_engine.generate_speech
generate_by_character = tts_engine.generate_by_character
load_voice_config = tts_engine.load_voice_config
except Exception as exc:
print(f"⚠️ 无法导入 tts-engine将使用简化模式: {exc}")
generate_speech = None generate_speech = None
generate_by_character = None generate_by_character = None
@ -270,7 +277,7 @@ class VoiceEmotionCompiler:
variants = [ variants = [
{"label": "基准", "params": base_params}, {"label": "基准", "params": base_params},
{"label": "语速+10%", "params": {**base_params, "rate": f"+{int(base_params['rate'].strip('%+')) + 10}%"}, {"label": "语速+10%", "params": {**base_params, "rate": f"+{int(base_params['rate'].strip('%+')) + 10}%"}},
{"label": "音调+5Hz", "params": {**base_params, "pitch": f"+{int(base_params['pitch'].strip('Hz+')) + 5}Hz"}}, {"label": "音调+5Hz", "params": {**base_params, "pitch": f"+{int(base_params['pitch'].strip('Hz+')) + 5}Hz"}},
{"label": "音量+10%", "params": {**base_params, "volume": f"+{int(base_params['volume'].strip('%+')) + 10}%"}}, {"label": "音量+10%", "params": {**base_params, "volume": f"+{int(base_params['volume'].strip('%+')) + 10}%"}},
] ]

View File

@ -0,0 +1,263 @@
# D144 · 视频AI系统8模块验收记录
> HLDP://video-ai-system/experience/D144-8-MODULE-VERIFICATION
> 日期: 2026-06-24
> 入口: 腾讯AI提交的 8 大模块工程验收
> 结论: 框架已进入仓库,但不能整体视为生产完成;需要按下方缺口继续补齐。
---
## 总结
```
overall_status: PARTIAL_PASS
production_ready: false
main_blockers:
- 主角资产包生成未真正接通图片生成适配器
- 口型同步依赖 Wav2Lip 未安装,不能解决“人物说台词”
- 多参考图能力未做真实付费验证,默认不能用于一致性承诺
- EP01 一键生产 CLI 仍读取占位台词,不满足“剧本怎么写就怎么拍”
- 字幕标准工具链仍需独立开发不在本次8模块内完成
```
---
## 模块验收
### 1. CHAR-HERO-DESIGN-PACKER
```
status: NOT_READY
syntax: PASS_AFTER_FIX
production_ready: false
```
发现:
- 原代码直接导入不存在的 Python `image_api_adapter`,仓库当前只有 JS 图片适配器,启动即崩。
- 原代码会在资产不完整时把 `manifest.hdlp` 写成 `APPROVED`,会污染后续角色锁定。
- 原提示词带有未经剧本/资产锁确认的视觉默认值,例如白发、蓝眼、蓝袍、皮克斯风格,容易造成角色漂移。
已修:
- 图片适配器未接通时不崩溃,明确提示未生成。
- 只有 4/4 资产完整生成时才允许 `APPROVED`;否则为 `DRAFT_GENERATED`。
- 默认风格改为 3D国风漫剧并要求服装来自角色资产清单不得自由改色。
后续:
- 需要开发 Python 到现有 JS 图片适配器的桥接,或统一改成 Node CLI 调用。
- 需要把“主角辨识度设计稿/正侧全身/表情表/色彩锁”作为独立资产包生成和人工确认流程。
---
### 2. CHARACTER-DISTINCTIVENESS-QC
```
status: PARTIAL_PASS
syntax: PASS
report_output: PASS_AFTER_FIX
production_ready: false
```
实测:
- 输入 `video-ai-system/assets/references/EP01-P0/ref-char003-subai-plus-tdz-plaque.png`
- 输出 `/tmp/guanghulab-module-check/character-qc.json`
- 结果: `overall_score: 6.35/10`, `verdict: FAIL`
说明:
- 这个结论与编辑意见“主角太普通、像路人甲”一致。
- 当前算法是基础 OpenCV 分数,只能做第一层粗筛,不能替代人眼/多模型视觉评审。
已修:
- 增加 `--output`,允许质检报告落盘进入经验库。
后续:
- 接入图像理解模型做“主角感/路人感/记忆点”语义评分。
- 加入同镜头前后角色一致性对比,而不是只看单张图。
---
### 3. MULTI-REFERENCE-VIDEO-ADAPTER
```
status: GUARDED_NOT_VERIFIED
syntax: PASS
production_ready: false
```
发现:
- 原 `--check-api` 会直接提交真实视频生成请求,可能产生费用。
- 原代码只读仓库 `.env`,不读本机密钥文件。
- 多参考图未确认时自动回退单参考图,会误导牌匾/角色/场景一致性判断。
已修:
- 默认 `--check-api` 只做无付费预检。
- 只有显式传入 `--live-check` 才会提交真实 API 探测请求。
- 读取本机 `~/guanghulab-local-secrets/video-ai-system.env`。
- 多参考图未确认时不自动回退;只有显式 `--allow-single-reference-fallback` 才允许调试占位。
后续:
- 需要一次受控 live-check确认 Seedance/豆包视频 API 是否真支持多参考图。
- 如果不支持,必须走“角色视频 + 牌匾/场景后期合成”的工程路线。
---
### 4. VOICE-EMOTION-COMPILER
```
status: PASS_FOR_TTS_PARAM_COMPILE
syntax: PASS_AFTER_FIX
production_ready: partial
```
发现:
- 原代码有语法错误。
- 原代码用普通 import 导入 `tts-engine.py`,文件名含连字符,无法导入。
已修并实测:
- 修复语法错误。
- 用动态导入接上 `tts-engine.py`。
- 实测生成苏白情绪配音 MP3 成功: `/tmp/guanghulab-module-check/subai-emotion.mp3`
后续:
- Edge-TTS 可做低成本测试,但商业成片建议继续 A/B 测豆包语音、火山语音或更适合角色对白的声音模型。
- 需要按角色建立“台词情绪标签 -> 音色/语速/音高/停顿”表。
---
### 5. LIPSYNC-ADAPTER
```
status: FAIL_DEPENDENCY_MISSING
syntax: PASS_AFTER_FIX
production_ready: false
```
发现:
- Wav2Lip 未安装。
- 原代码在 Wav2Lip 不可用时复制原视频并返回成功,会把“没做口型”伪装为“口型完成”。
已修:
- 默认无 Wav2Lip 时返回失败。
- 只有显式传入 `--allow-fallback-copy` 才允许复制原视频,并标记为调试占位。
实测:
- 当前返回失败: `Wav2Lip 不可用,未执行真实口型同步`
后续:
- 安装 Wav2Lip 或改接可商用口型/视频驱动 API。
- 需要口型 QC音频峰值与嘴部开合是否同步不能只看生成成功。
---
### 6. AUDIO-MIXER
```
status: BASIC_PASS
production_ready: partial
```
实测:
- 单音轨输出成功: `/tmp/guanghulab-module-check/mix-single.mp3`
缺口:
- 当前对白压低 BGM 是简化处理,不是真正精细侧链压缩。
- 需要对白优先、BGM 自动 ducking、响度标准、峰值保护、短视频平台响度目标。
后续:
- 开发基于 FFmpeg sidechaincompress / loudnorm 的商业混音预设。
- 输出混音 QC 报告对白响度、BGM 响度、峰值、电平余量。
---
### 7. SHOT-QC-AUTOMATION
```
status: PARTIAL_PASS
syntax: PASS
production_ready: false
```
实测:
- 输入移动硬盘样片: `TEST-SUBTITLE-007-RUN002-REFERENCE-DRAMA.mp4`
- 输出 `/tmp/guanghulab-module-check/qc-after-fix.json`
- 结果: 检测到横屏 `1112x834`,失败原因 `竖屏比例错误`
已修:
- 原 CLI 失败时只显示 `Unknown error`;已改成输出真实失败项。
- 依赖清单补充 `numpy`, `opencv-python`, `requests`。
缺口:
- OCR 未安装,字幕/牌匾文字检查被跳过。
- 现代物品检测仍是 TODO。
- 换脸检查只是粗糙直方图,不足以判定角色一致性。
后续:
- 安装/配置 OCR。
- 接 YOLO 或视觉模型做现代物品、遮挡、文字、角色漂移检查。
---
### 8. EP01-SHOT03-PRODUCTION-CLI
```
status: DRY_RUN_ONLY
syntax: PASS
production_ready: false
```
实测:
- `--dry-run` 可执行完整步骤列表。
重大问题:
- 配置读取仍显示 `台词: 苏白原文台词`,不是剧本原文台词。
- 这不满足用户要求:剧本怎么写,就怎么拍;不能用占位台词进入生产。
后续:
- 必须接入剧本/分镜头源文件,逐字锁定台词。
- 每个镜头需要输出“剧本文本 -> 提示词 -> 画面 -> 配音 -> 字幕”的可追溯链路。
---
## 本次工程修复清单
```
fixed_files:
- engines/voice-emotion-compiler.py
- engines/char-hero-design-packer/char-hero-design-packer.py
- engines/character-distinctiveness-qc/character-distinctiveness-qc.py
- engines/lipsync-adapter.py
- engines/multi-reference-video-adapter.py
- engines/shot-qc-automation.py
- requirements-video-ai.txt
```
---
## 下一步优先级
```
P0:
- 字幕商业标准渲染工具链
- 真实口型同步方案
- 剧本原文锁定与 EP01 CLI 台词读取修复
- 主角资产包真实生成与人工确认
P1:
- 多参考图 live-check
- 角色一致性跨镜头 QC
- BGM/对白混音 loudness + ducking
P2:
- OCR/牌匾文字 QC
- 现代物品检测
- 成本账本与模型可用性检查
```
---
## 验收结论
```
腾讯AI这次提交的8个模块不能按“全部开发完成”验收。
准确结论是: 工程骨架已建立其中语音参数编译、基础混音、基础QC可继续用
主角资产生成、多参考视频、口型同步、EP01一键生产仍是生产阻塞项。
```

View File

@ -1,7 +1,7 @@
# 付费才能修仙 · 3D漫剧主线 · 项目状态 # 付费才能修仙 · 3D漫剧主线 · 项目状态
> HLDP://video-ai-system/memory/zai-fu-fei-xiu-xian/STATUS > HLDP://video-ai-system/memory/zai-fu-fei-xiu-xian/STATUS
> 更新: D143 · 2026-06-23 · preview-002复盘·制片厂化重启 > 更新: D144 · 2026-06-24 · 8模块验收·生产阻塞项确认
> 铸渊 ICE-GL-ZY001 > 铸渊 ICE-GL-ZY001
> 国作登字-2026-A-00037559 > 国作登字-2026-A-00037559
@ -119,6 +119,7 @@ preview-003: 暂不生成。
| 苏白TTS配音 | ✅ 工程测试通过 | `outputs/tests/TEST-TTS-001-SUBAI-RUN-001-REPORT.hdlp` | | 苏白TTS配音 | ✅ 工程测试通过 | `outputs/tests/TEST-TTS-001-SUBAI-RUN-001-REPORT.hdlp` |
| 模型/成本路线 | ✅ 已建立 | `knowledge/D144-MODEL-COST-ROUTE.hdlp` | | 模型/成本路线 | ✅ 已建立 | `knowledge/D144-MODEL-COST-ROUTE.hdlp` |
| 腾讯AI开发交接 | ✅ 已建立 | `plans/D144-TENCENT-AI-DEV-HANDOFF.hdlp` | | 腾讯AI开发交接 | ✅ 已建立 | `plans/D144-TENCENT-AI-DEV-HANDOFF.hdlp` |
| 腾讯AI 8模块代码 | 🟡 部分通过 | `experience/D144-8-MODULE-VERIFICATION.hdlp` · 框架已入仓,生产阻塞项仍在 |
| 广告牌资产 | ✅ 草案 | `assets/props/PROP-FREE-AD-BOARD/manifest.hdlp` | | 广告牌资产 | ✅ 草案 | `assets/props/PROP-FREE-AD-BOARD/manifest.hdlp` |
| 广告牌文字贴图 | ✅ PNG已生成 | `assets/props/PROP-FREE-AD-BOARD/texture/free-ad-board-texture.png` | | 广告牌文字贴图 | ✅ PNG已生成 | `assets/props/PROP-FREE-AD-BOARD/texture/free-ad-board-texture.png` |
| 百宗会场景资产 | ✅ 草案 | `assets/envs/ENV-002-Baizonghui/manifest.hdlp` | | 百宗会场景资产 | ✅ 草案 | `assets/envs/ENV-002-Baizonghui/manifest.hdlp` |
@ -218,6 +219,11 @@ preview-003: 暂不生成。
- [ ] 开发 reference-subtitle-analyzer / ASS渲染 / 字体管理 / 安全区QC 字幕工具链 - [ ] 开发 reference-subtitle-analyzer / ASS渲染 / 字体管理 / 安全区QC 字幕工具链
- [x] 重定性 RUN-002: 牌匾一致性失败,但百宗会人群氛围可用 - [x] 重定性 RUN-002: 牌匾一致性失败,但百宗会人群氛围可用
- [x] 测试苏白Edge-TTS工程配音 → `outputs/tests/TEST-TTS-001-SUBAI-RUN-001-REPORT.hdlp` - [x] 测试苏白Edge-TTS工程配音 → `outputs/tests/TEST-TTS-001-SUBAI-RUN-001-REPORT.hdlp`
- [x] 验收腾讯AI 8模块代码 → `experience/D144-8-MODULE-VERIFICATION.hdlp`
- [ ] 接通主角资产包真实图片生成适配器,禁止未完整资产写入 APPROVED
- [ ] 安装或替换真实口型同步方案,禁止复制视频冒充口型完成
- [ ] 修复 EP01-SHOT03 CLI 台词读取,占位台词不得进入生产
- [ ] 受控验证 Seedance/豆包视频多参考图能力,未验证前不得承诺跨镜一致
- [x] 建立模型/成本路线 → `knowledge/D144-MODEL-COST-ROUTE.hdlp` - [x] 建立模型/成本路线 → `knowledge/D144-MODEL-COST-ROUTE.hdlp`
- [x] 建立腾讯AI人格体开发交接清单 → `plans/D144-TENCENT-AI-DEV-HANDOFF.hdlp` - [x] 建立腾讯AI人格体开发交接清单 → `plans/D144-TENCENT-AI-DEV-HANDOFF.hdlp`
- [ ] 生成E1-SHOT03专用苏白底片: 上方/身后预留牌匾空间 - [ ] 生成E1-SHOT03专用苏白底片: 上方/身后预留牌匾空间

View File

@ -2,3 +2,10 @@ edge-tts>=7.2.8
pysrt>=1.1.2 pysrt>=1.1.2
cairosvg>=2.9.0 cairosvg>=2.9.0
Pillow>=12.2.0 Pillow>=12.2.0
numpy>=2.0.0
opencv-python>=4.10.0
requests>=2.32.0
# Optional production QC dependencies:
# pytesseract>=0.3.13 # requires system tesseract binary
# ultralytics>=8.3.0 # modern-item detection, not wired yet