fix: repair video ai production cli toolchain
This commit is contained in:
parent
a77bc6b884
commit
8c33ca43d6
@ -32,6 +32,26 @@ import time
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(PROJECT_ROOT / "engines"))
|
||||
DEFAULT_PYTHON = "/Users/bingshuolingdianyuanhe/.workbuddy/binaries/python/envs/default/bin/python3"
|
||||
|
||||
|
||||
def resolve_python_bin():
|
||||
"""Pick the Python that has the video AI dependencies installed."""
|
||||
candidates = [
|
||||
os.environ.get("PYTHON_BIN"),
|
||||
os.environ.get("PYTHON"),
|
||||
DEFAULT_PYTHON,
|
||||
sys.executable,
|
||||
"python3",
|
||||
]
|
||||
for candidate in candidates:
|
||||
if not candidate:
|
||||
continue
|
||||
candidate_path = Path(candidate)
|
||||
if candidate_path.is_absolute() and not candidate_path.exists():
|
||||
continue
|
||||
return str(candidate)
|
||||
return sys.executable
|
||||
|
||||
|
||||
class EP01Shot03ProductionCLI:
|
||||
@ -47,6 +67,7 @@ class EP01Shot03ProductionCLI:
|
||||
self.config_dir = PROJECT_ROOT / "plans" / "script-to-screen"
|
||||
self.output_dir = PROJECT_ROOT / "outputs" / "ep01" / "shot03"
|
||||
self.assets_dir = PROJECT_ROOT / "assets"
|
||||
self.python_bin = resolve_python_bin()
|
||||
|
||||
# 确保输出目录存在
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
@ -70,6 +91,7 @@ class EP01Shot03ProductionCLI:
|
||||
print(f" 镜头: {self.shot_id}")
|
||||
print(f" 输出: {self.output_dir}")
|
||||
print(f" Dry Run: {self.dry_run}")
|
||||
print(f" Python: {self.python_bin}")
|
||||
|
||||
def _load_config(self):
|
||||
"""加载 E1-SHOT03 配置,从剧本原文+分镜文件读取真实台词"""
|
||||
@ -241,7 +263,7 @@ class EP01Shot03ProductionCLI:
|
||||
print(f" 📤 生成资产包...")
|
||||
result = subprocess.run(
|
||||
[
|
||||
"python", str(PROJECT_ROOT / "engines" / "char-hero-design-packer" / "char-hero-design-packer.py"),
|
||||
self.python_bin, str(PROJECT_ROOT / "engines" / "char-hero-design-packer" / "char-hero-design-packer.py"),
|
||||
"--character", self.config["character"],
|
||||
"--generate-all"
|
||||
],
|
||||
@ -287,7 +309,7 @@ class EP01Shot03ProductionCLI:
|
||||
if len(reference_images) >= 2:
|
||||
# 多参考图模式
|
||||
cmd = [
|
||||
"python", str(PROJECT_ROOT / "engines" / "multi-reference-video-adapter" / "multi-reference-video-adapter.py"),
|
||||
self.python_bin, str(PROJECT_ROOT / "engines" / "multi-reference-video-adapter.py"),
|
||||
"--prompt", self.config["prompt"],
|
||||
"--references"] + reference_images + [
|
||||
"--output", str(output_path),
|
||||
@ -333,7 +355,7 @@ class EP01Shot03ProductionCLI:
|
||||
output_with_plaque = self.output_dir / "video_with_plaque.mp4"
|
||||
|
||||
# TODO: 实际调用 planar-tracker.py
|
||||
# cmd = ["python", str(PROJECT_ROOT / "engines" / "planar-tracker.py"), ...]
|
||||
# cmd = [self.python_bin, str(PROJECT_ROOT / "engines" / "planar-tracker.py"), ...]
|
||||
|
||||
return {
|
||||
"output_path": str(output_with_plaque),
|
||||
@ -349,7 +371,7 @@ class EP01Shot03ProductionCLI:
|
||||
output_path = self.output_dir / "dialogue.mp3"
|
||||
|
||||
cmd = [
|
||||
"python", str(PROJECT_ROOT / "engines" / "voice-emotion-compiler" / "voice-emotion-compiler.py"),
|
||||
self.python_bin, str(PROJECT_ROOT / "engines" / "voice-emotion-compiler.py"),
|
||||
"--text", self.config["dialogue"],
|
||||
"--emotion", self.config["emotion"],
|
||||
"--output", str(output_path)
|
||||
@ -383,7 +405,7 @@ class EP01Shot03ProductionCLI:
|
||||
output_path = self.output_dir / "video_synced.mp4"
|
||||
|
||||
cmd = [
|
||||
"python", str(PROJECT_ROOT / "engines" / "lipsync-adapter" / "lipsync-adapter.py"),
|
||||
self.python_bin, str(PROJECT_ROOT / "engines" / "lipsync-adapter.py"),
|
||||
"--video", str(video_path),
|
||||
"--audio", str(audio_path),
|
||||
"--output", str(output_path)
|
||||
@ -417,12 +439,14 @@ class EP01Shot03ProductionCLI:
|
||||
raise Exception(f"视频不存在: {video_path}")
|
||||
|
||||
output_path = self.output_dir / "video_with_subtitles.mp4"
|
||||
srt_path = self.output_dir / "dialogue.srt"
|
||||
self._write_single_line_srt(srt_path, self.config["dialogue"], float(self.config.get("duration", 3)))
|
||||
|
||||
# 调用 subtitle-renderer.py
|
||||
cmd = [
|
||||
"python", str(PROJECT_ROOT / "engines" / "subtitle-renderer.py"),
|
||||
"--input", str(video_path),
|
||||
"--text", self.config["dialogue"],
|
||||
self.python_bin, str(PROJECT_ROOT / "engines" / "subtitle-renderer.py"),
|
||||
"--srt", str(srt_path),
|
||||
"--video", str(video_path),
|
||||
"--output", str(output_path)
|
||||
]
|
||||
|
||||
@ -467,7 +491,7 @@ class EP01Shot03ProductionCLI:
|
||||
# 混音
|
||||
print(f" 混音...")
|
||||
cmd = [
|
||||
"python", str(PROJECT_ROOT / "engines" / "audio-mixer" / "audio-mixer.py"),
|
||||
self.python_bin, str(PROJECT_ROOT / "engines" / "audio-mixer.py"),
|
||||
"--dialogue", str(dialogue_path),
|
||||
"--output", str(self.output_dir / "mixed_audio.mp3")
|
||||
]
|
||||
@ -502,7 +526,7 @@ class EP01Shot03ProductionCLI:
|
||||
return {"skipped": True, "reason": "no video found"}
|
||||
|
||||
cmd = [
|
||||
"python", str(PROJECT_ROOT / "engines" / "shot-qc-automation" / "shot-qc-automation.py"),
|
||||
self.python_bin, str(PROJECT_ROOT / "engines" / "shot-qc-automation.py"),
|
||||
"--video", str(video_path),
|
||||
"--character", self.config["character"],
|
||||
"--output", str(self.output_dir / "qc_report.json")
|
||||
@ -582,6 +606,21 @@ class EP01Shot03ProductionCLI:
|
||||
|
||||
return True
|
||||
|
||||
def _write_single_line_srt(self, srt_path, text, duration):
|
||||
"""Write a one-line SRT from the locked source dialogue."""
|
||||
duration_ms = max(500, int(duration * 1000))
|
||||
end_seconds = duration_ms // 1000
|
||||
end_ms = duration_ms % 1000
|
||||
srt_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
content = (
|
||||
"1\n"
|
||||
f"00:00:00,000 --> 00:00:{end_seconds:02d},{end_ms:03d}\n"
|
||||
f"{text}\n"
|
||||
)
|
||||
with open(srt_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
return srt_path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="EP01-SHOT03-PRODUCTION-CLI")
|
||||
|
||||
@ -34,10 +34,13 @@ def load_api_config():
|
||||
"""从仓库 .env、本机密钥文件和环境变量加载配置"""
|
||||
config = {}
|
||||
env_files = [
|
||||
Path(os.environ["VIDEO_AI_SECRETS_FILE"]) if os.environ.get("VIDEO_AI_SECRETS_FILE") else None,
|
||||
Path("/Users/bingshuolingdianyuanhe/Documents/guanghulab-local-secrets/video-ai-system.env"),
|
||||
PROJECT_ROOT / ".env",
|
||||
Path.home() / "guanghulab-local-secrets" / "video-ai-system.env",
|
||||
]
|
||||
for env_file in env_files:
|
||||
if env_file is None:
|
||||
continue
|
||||
if not env_file.exists():
|
||||
continue
|
||||
with open(env_file, "r", encoding="utf-8") as f:
|
||||
|
||||
@ -5,7 +5,7 @@ Subtitle Renderer · 字幕渲染引擎
|
||||
将 SRT 字幕文件渲染为 PNG 序列,再通过 FFmpeg 合成到视频中。
|
||||
|
||||
依赖:
|
||||
pip install cairosvg Pillow pysrt
|
||||
pip install Pillow pysrt
|
||||
|
||||
用法:
|
||||
# 基本用法(SRT → PNG 序列)
|
||||
@ -50,11 +50,10 @@ except ImportError:
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
import cairosvg
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
except ImportError:
|
||||
print("[ERROR] 缺少依赖:cairosvg Pillow")
|
||||
print("请先安装:pip install cairosvg Pillow")
|
||||
print("[ERROR] 缺少依赖:Pillow")
|
||||
print("请先安装:pip install Pillow")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user