Flatten skill category directory structure
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
剪映/CapCut 导出脚本(hailuo-promo 专用封装)
|
||||
|
||||
封装 clip-export skill 的 jy_wrapper API,预置 Hailuo Promo 专用配置:
|
||||
- 竖屏 1080×1920 分辨率
|
||||
- 数字人 PiP 左下角定位
|
||||
- 字幕轨道
|
||||
- BGM 混合
|
||||
|
||||
使用方式:
|
||||
python export_to_jianying.py --project-name "my_promo" \\
|
||||
--photos photo_01.jpg photo_02.jpg photo_03.jpg \\
|
||||
--digital-person talking_head_green.mp4 \\
|
||||
--voiceover voiceover.mp3 \\
|
||||
--bgm bgm.mp3 \\
|
||||
--subtitles voiceover_subtitle.json \\
|
||||
--output 导出到剪映草稿目录
|
||||
|
||||
依赖:
|
||||
pip3 install pyJianYingDraft
|
||||
需安装 clip-export skill(脚本通过相对路径引用 jy_wrapper)
|
||||
|
||||
脚本路径:
|
||||
../clip-export/scripts/jy_wrapper.py (相对于本脚本)
|
||||
或设置 JY_WRAPPER_PATH 环境变量覆盖
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
# ─────────────────────────── 路径配置 ───────────────────────────
|
||||
|
||||
# 尝试多个可能路径找到 jy_wrapper
|
||||
SEARCH_PATHS = [
|
||||
Path(__file__).parent.parent / "clip-export" / "scripts",
|
||||
Path.home() / ".claude" / "skills" / "clip-export" / "scripts",
|
||||
Path("/Users/ilike/Movies/Hub/skills/clip-export/scripts"),
|
||||
]
|
||||
|
||||
JY_WRAPPER_PATH = os.environ.get("JY_WRAPPER_PATH")
|
||||
if JY_WRAPPER_PATH:
|
||||
sys.path.insert(0, JY_WRAPPER_PATH)
|
||||
else:
|
||||
for p in SEARCH_PATHS:
|
||||
if (p / "jy_wrapper.py").exists():
|
||||
sys.path.insert(0, str(p))
|
||||
break
|
||||
else:
|
||||
print("❌ 找不到 jy_wrapper.py,请确保:")
|
||||
print(" 1. 安装了 clip-export skill")
|
||||
print(" 2. 或设置 JY_WRAPPER_PATH 环境变量指向 clip-export/scripts/ 目录")
|
||||
sys.exit(1)
|
||||
|
||||
from jy_wrapper import JyProject, ClipSettings
|
||||
|
||||
|
||||
# ─────────────────────────── 配置常量 ───────────────────────────
|
||||
|
||||
CANVAS_W = 1080
|
||||
CANVAS_H = 1920
|
||||
|
||||
# 数字人 PiP 参数(Hailuo Promo 标准位置)
|
||||
PIP_SCALE_X = 0.4
|
||||
PIP_SCALE_Y = 0.4
|
||||
PIP_POS_X = -0.8 # 左下角(transform_x 范围 -1 到 1)
|
||||
PIP_POS_Y = 0.5
|
||||
|
||||
# 字幕位置(transform_y 范围 -1 到 1,底部约 -0.35)
|
||||
SUBTITLE_TRANSFORM_Y = -0.35
|
||||
|
||||
# BGM 音量
|
||||
BGM_VOLUME = 0.15
|
||||
|
||||
|
||||
# ─────────────────────────── 核心函数 ───────────────────────────
|
||||
|
||||
|
||||
def add_photo_carousel(
|
||||
project: JyProject,
|
||||
photos: List[str],
|
||||
each_duration: float = 3.0,
|
||||
transition: str = "slideleft",
|
||||
track_name: str = "主视频",
|
||||
) -> None:
|
||||
"""添加写真图轮播(带转场)"""
|
||||
for i, photo in enumerate(photos):
|
||||
start_time = i * each_duration
|
||||
project.add_video(
|
||||
photo,
|
||||
start_time=start_time,
|
||||
duration=f"{each_duration}s",
|
||||
track_name=track_name,
|
||||
)
|
||||
|
||||
|
||||
def add_digital_person_pip(
|
||||
project: JyProject,
|
||||
video_path: str,
|
||||
start_time: float = 0.5,
|
||||
duration: float = 15.0,
|
||||
track_name: str = "数字人PiP",
|
||||
) -> None:
|
||||
"""添加数字人 PiP(预置 Hailuo Promo 标准位置)"""
|
||||
pip_settings = ClipSettings(
|
||||
scale_x=PIP_SCALE_X,
|
||||
scale_y=PIP_SCALE_Y,
|
||||
transform_x=PIP_POS_X,
|
||||
transform_y=PIP_POS_Y,
|
||||
)
|
||||
project.add_video(
|
||||
video_path,
|
||||
start_time=start_time,
|
||||
duration=f"{duration}s",
|
||||
track_name=track_name,
|
||||
clip_settings=pip_settings,
|
||||
)
|
||||
|
||||
|
||||
def add_bgm(project: JyProject, bgm_path: str, start_time: float = 0.0) -> None:
|
||||
"""添加 BGM(预置音量 0.15)"""
|
||||
project.add_audio(
|
||||
bgm_path, start_time=start_time, volume=BGM_VOLUME, track_name="BGM"
|
||||
)
|
||||
|
||||
|
||||
def add_voiceover(
|
||||
project: JyProject,
|
||||
voiceover_path: str,
|
||||
start_time: float = 0.0,
|
||||
duration: Optional[float] = None,
|
||||
track_name: str = "配音",
|
||||
) -> None:
|
||||
"""添加配音"""
|
||||
kwargs = {"start_time": start_time, "track_name": track_name, "volume": 1.0}
|
||||
if duration:
|
||||
kwargs["duration"] = f"{duration}s"
|
||||
project.add_audio(voiceover_path, **kwargs)
|
||||
|
||||
|
||||
# ─────────────────────────── 主入口 ───────────────────────────
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="导出 Hailuo Promo 项目到剪映/CapCut(预置 1080×1920 + PiP 定位)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--project-name", required=True, help="项目名称(显示在剪映中)"
|
||||
)
|
||||
parser.add_argument("--photos", nargs="+", required=True, help="写真图路径列表")
|
||||
parser.add_argument("--digital-person", default=None, help="数字人视频路径(可选)")
|
||||
parser.add_argument("--voiceover", default=None, help="配音路径(可选)")
|
||||
parser.add_argument("--bgm", default=None, help="BGM 路径(可选)")
|
||||
parser.add_argument(
|
||||
"--subtitles",
|
||||
default=None,
|
||||
help="字幕 JSON 文件(由 audio_transcribe_lyrics 生成,可选)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--each-duration",
|
||||
type=float,
|
||||
default=3.0,
|
||||
help="每张写真展示时长(秒,默认 3.0)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pip-start",
|
||||
type=float,
|
||||
default=0.5,
|
||||
help="数字人 PiP 出现时间(秒,默认 0.5)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--app-source",
|
||||
default=None,
|
||||
choices=["lv", "cc"],
|
||||
help="目标软件(lv=剪映,cc=CapCut;不指定则询问)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 创建项目(竖屏 1080×1920)
|
||||
app_source = args.app_source
|
||||
if not app_source:
|
||||
print("\n🎯 目标平台选择:")
|
||||
print(" [1] 剪映(中文版)")
|
||||
print(" [2] CapCut(国际版)")
|
||||
choice = input("请选择 [1/2]: ").strip()
|
||||
app_source = "lv" if choice == "1" else "cc"
|
||||
|
||||
project = JyProject(
|
||||
args.project_name,
|
||||
width=CANVAS_W,
|
||||
height=CANVAS_H,
|
||||
direct=True,
|
||||
overwrite=True,
|
||||
app_source=app_source,
|
||||
)
|
||||
|
||||
# 添加写真轮播
|
||||
print(f"📷 添加 {len(args.photos)} 张写真...")
|
||||
add_photo_carousel(
|
||||
project,
|
||||
args.photos,
|
||||
each_duration=args.each_duration,
|
||||
)
|
||||
|
||||
# 添加数字人 PiP
|
||||
if args.digital_person:
|
||||
print(f"🤳 添加数字人 PiP...")
|
||||
add_digital_person_pip(project, args.digital_person, start_time=args.pip_start)
|
||||
|
||||
# 添加配音
|
||||
if args.voiceover:
|
||||
print(f"🎙️ 添加配音...")
|
||||
add_voiceover(project, args.voiceover)
|
||||
|
||||
# 添加 BGM
|
||||
if args.bgm:
|
||||
print(f"🎵 添加 BGM...")
|
||||
add_bgm(project, args.bgm)
|
||||
|
||||
# 添加字幕(如果提供 JSON)
|
||||
if args.subtitles:
|
||||
print(f"📝 导入字幕...")
|
||||
try:
|
||||
project.import_srt(args.subtitles, track_name="字幕")
|
||||
except Exception:
|
||||
print(f"⚠️ 字幕导入失败(可能格式不兼容),跳过")
|
||||
|
||||
# 保存
|
||||
print("\n💾 保存到剪映/CapCut 草稿目录...")
|
||||
project.save()
|
||||
print(
|
||||
f"✅ 完成!请打开{'剪映' if app_source == 'lv' else 'CapCut'}查看项目「{args.project_name}」"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user