
Chanjing Content Creation Skill
- 2 installs
- Updated April 7, 2026
- chanjing-ai/chanjing-content-creation-skills
Create AI-generated videos with digital avatars, TTS, and voice cloning
About
Chanjing provides content creation capabilities including TTS synthesis, voice cloning, digital human avatars, and one-click video composition. Use it to generate videos at scale with customizable avatars and voice.
- Text-to-speech, voice cloning, and digital avatar synthesis
- One-click video arrangement and cartoon video creation
Chanjing Content Creation Skill by the numbers
- 2 all-time installs (skills.sh)
- Ranked #1,166 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/chanjing-ai/chanjing-content-creation-skills --skill chanjing-content-creation-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| Last updated | April 7, 2026 |
| Repository | chanjing-ai/chanjing-content-creation-skills ↗ |
What it does
Create AI-generated videos with digital avatars, TTS, and voice cloning
Files
chanjing content creation skill
蝉镜内容创作技能包(顶层路由)
本文件只负责:触发边界、路由消歧、统一执行约束与运行时契约摘要。 具体执行步骤以子技能 *-SKILL.md 为真值;长文细节以 references/ 为真值。
Use When
当用户需求涉及以下任一项时使用本 Skill:
- 短视频成片(含一键成片、卡通视频、口播混剪)
- 蝉镜语音能力(TTS、声音克隆)
- 数字人能力(公共/定制数字人、对口型、文生数字人)
- 蝉镜 AI 创作(文生图、文生视频)
- 蝉镜凭据管理(APP_ID/SECRET_KEY 配置、Token 刷新、登录引导)
Do NOT Use
- 与蝉镜平台无关的通用需求(纯文案、非蝉镜剪辑工具等)
- 用户仅讨论方案且明确不执行任何蝉镜 API/脚本调用
Route Map(唯一映射)
| 用户意图 | 路由目标 |
|---|---|
| 一键成片、完整短视频、做一个 xx 视频 | `orchestration/chanjing-one-click-video-creation/chanjing-one-click-video-creation-SKILL.md` |
| 卡通短剧、动漫多镜叙事 | `orchestration/cartoon-video-creation/cartoon-video-creation-SKILL.md` |
| 文字转语音、语音合成 | `products/chanjing-tts/chanjing-tts-SKILL.md` |
| 声音克隆、音色复刻 | `products/chanjing-tts-voice-clone/chanjing-tts-voice-clone-SKILL.md` |
| 对口型、唇形驱动(非卡通) | `products/chanjing-avatar/chanjing-avatar-SKILL.md` |
| 数字人口播、公共数字人、纯口播视频、卡通数字人口播合成 | `products/chanjing-video-compose/chanjing-video-compose-SKILL.md` |
| 文生图、创意视频 | `products/chanjing-ai-creation/chanjing-ai-creation-SKILL.md` |
| 定制数字人(上传素材训练) | `products/chanjing-customised-person/chanjing-customised-person-SKILL.md` |
| 人设图、人像 LoRA、文生数字人形象 | `products/chanjing-text-to-digital-person/chanjing-text-to-digital-person-SKILL.md` |
| 凭据、APP_ID/SECRET_KEY、Token | `products/chanjing-credentials-guard/chanjing-credentials-guard-SKILL.md` |
Conflict Resolution(强制消歧)
出现重叠意图时,按以下优先级判定:
1. 完整成片(文案+分镜+画面+交付)优先路由 chanjing-one-click-video-creation 2. 卡通/动漫多镜叙事成片优先路由 cartoon-video-creation 3. 卡通数字人口播合成(model=2)或仅数字人口播,路由 chanjing-video-compose 4. 已有视频改口型(非卡通)路由 chanjing-avatar 5. 路由不确定时,遵循 `orchestration/orchestration-contract.md` 的 need_param 分支最小补参
Root Workflow(最小入口流程)
| Step | 动作 | 输出 | 下一步 | 失败分支 |
|---|---|---|---|---|
| 1 | 凭据可用性检查(缺失时走 chanjing-credentials-guard) | ok 或 auth_required | Step 2 | auth_required:先完成鉴权再回 Step 2 |
| 2 | 按 Route Map 选择单一目标子技能 | 目标 *-SKILL.md 路径 | Step 3 | need_param:只补路由必需信息 |
| 3 | 打开目标子技能并执行其 Standard Workflow | 子技能结果 | Step 4 | 按子技能失败分支执行 |
| 4 | 返回产物 URL/路径或标准错误语义 | ok/need_param/auth_required/upstream_error/timeout | 结束 | 需要降级跨产品时先征得用户确认 |
Global Execution Rules
- 仅可调用
products/*/scripts/与orchestration/*/scripts/的已有入口。 - 禁止自行编写临时脚本替代既有 CLI。
- 默认不主动下载;仅当用户明确要求“下载/保存到本地”时执行下载脚本。
- 重复任务可复用已确认参数,但当核心输入冲突时必须切换为新任务。
- 外部副作用(网络、写盘、子进程、浏览器)必须与运行时契约一致。
Three-Layer Architecture
| 层级 | 路径 | 作用 |
|---|---|---|
| 顶层入口 | SKILL.md | 路由、消歧、契约摘要 |
| L1 公共层 | `common/` | 公共鉴权、HTTP 封装、上传下载、日志 |
| L2 产品层 | `products/` | 单产品能力与标准脚本 |
| L3 编排层 | `orchestration/` | 跨产品场景编排 |
依赖方向固定为:common <- products <- orchestration(禁止反向依赖)。
Runtime Contract(摘要)
完整契约见 `references/top-level-runtime-contract.md`。
- 凭据来源:项目
.env(CHANJING_APP_ID、CHANJING_SECRET_KEY) - 凭据文件:
skills/chanjing-content-creation-skill/.env - Token 缓存:
.env中CHANJING_ACCESS_TOKEN、CHANJING_TOKEN_EXPIRE_IN(按过期时间刷新) - API 基址:
${CHANJING_API_BASE:-https://open-api.chanjing.cc} - 关键环境变量:
CHANJING_APP_ID、CHANJING_SECRET_KEY、CHANJING_ACCESS_TOKEN、CHANJING_TOKEN_EXPIRE_IN、CHANJING_API_BASE、CHAN_SKILLS_DIR - 外部二进制:
python3(所有脚本入口)、ffmpeg/ffprobe(主要用于 L3 成片链路) - 副作用分类:出站 HTTPS、本地写入、子进程、浏览器引导登录
Constraints
- 脚本写盘边界受运行时契约约束(凭据路径、
--output-dir、output/、work/)。
"""将蝉镜结果 URL 下载到本地 output 子目录。"""
from __future__ import annotations
import os
import urllib.parse
import urllib.request
from pathlib import Path
def infer_download_filename(url: str, default_basename: str) -> str:
parsed = urllib.parse.urlparse(url)
name = Path(parsed.path).name or default_basename
if "." not in name:
name += ".bin"
return name
def download_chanjing_result_url(
url: str,
*,
outputs_subdir: str,
user_agent: str,
default_basename: str,
output_path: Path | None = None,
timeout: int = 120,
) -> Path:
"""
下载到 output_path;若未指定则写入
skills/chanjing-content-creation-skill/output/<outputs_subdir>/<推断文件名>。
失败时抛出 RuntimeError(与原有脚本语义一致,便于 CLI 捕获)。
"""
skill_root = Path(__file__).resolve().parents[1]
default_dir = skill_root / "output" / outputs_subdir
dest = output_path if output_path is not None else default_dir / infer_download_filename(url, default_basename)
dest.parent.mkdir(parents=True, exist_ok=True)
req = urllib.request.Request(url, headers={"User-Agent": user_agent}, method="GET")
try:
with urllib.request.urlopen(req, timeout=timeout) as resp, open(dest, "wb") as handle:
handle.write(resp.read())
except Exception as exc:
raise RuntimeError(f"下载失败: {exc}") from exc
if not dest.exists() or dest.stat().st_size == 0:
raise RuntimeError("下载失败: 输出文件为空")
return dest
def print_downloaded_path(path: Path) -> None:
print(os.fspath(path))
from __future__ import annotations
import json
import os
import subprocess
import sys
import time
import urllib.parse
import urllib.request
from pathlib import Path
from typing import Any, Callable
from exceptions import SkillExecutionError, SkillHTTPError, SkillTimeoutError
# --- HTTP(原 http_client.py,合并入 L1 base) ---
def request_json(
method: str,
url: str,
*,
headers: dict[str, str] | None = None,
query: dict[str, Any] | None = None,
body: dict[str, Any] | None = None,
timeout: int = 30,
) -> dict[str, Any]:
if query:
url = f"{url}?{urllib.parse.urlencode(query)}"
data = None
request_headers = dict(headers or {})
if body is not None:
data = json.dumps(body).encode("utf-8")
request_headers.setdefault("Content-Type", "application/json")
req = urllib.request.Request(url, data=data, headers=request_headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return json.loads(resp.read().decode("utf-8"))
except Exception as exc:
raise SkillHTTPError(str(exc)) from exc
# --- 轮询(原 polling.py) ---
def poll_until(
fetch: Callable[[], Any],
*,
is_done: Callable[[Any], bool],
is_failed: Callable[[Any], bool] | None = None,
interval_seconds: float = 3.0,
timeout_seconds: float = 300.0,
) -> Any:
deadline = time.time() + timeout_seconds
while True:
value = fetch()
if is_done(value):
return value
if is_failed and is_failed(value):
return value
if time.time() >= deadline:
raise SkillTimeoutError(f"polling timed out after {timeout_seconds}s")
time.sleep(interval_seconds)
# --- 鉴权配置(原 auth.py,依赖 request_json) ---
SKILL_DIR = Path(__file__).resolve().parent.parent
ENV_FILE = Path(os.environ.get("CHANJING_ENV_FILE", str(SKILL_DIR / ".env")))
API_BASE = os.environ.get("CHANJING_API_BASE", "https://open-api.chanjing.cc")
LOGIN_URL = "https://www.chanjing.cc/openapi/login"
BUFFER_SECONDS = 300
NO_CREDENTIALS_MSG = """已在浏览器打开蝉镜登录/注册页。
获取秘钥后请执行:
python skills/chanjing-content-creation-skill/products/chanjing-credentials-guard/scripts/chanjing_config.py --app-id <你的app_id> --sk <你的secret_key>
配置会写入 skills/chanjing-content-creation-skill/.env。
设置完毕后请重新执行您之前的操作。"""
def _run_open_login_page() -> None:
try:
skills_dir = Path(__file__).resolve().parent.parent
script = skills_dir / "products" / "chanjing-credentials-guard" / "scripts" / "open_login_page.py"
if script.exists():
subprocess.run([sys.executable, str(script)], check=False, timeout=5)
else:
import webbrowser
webbrowser.open(LOGIN_URL)
except Exception:
try:
import webbrowser
webbrowser.open(LOGIN_URL)
except Exception:
pass
def _strip_quotes(value: str) -> str:
text = value.strip()
if len(text) >= 2 and ((text[0] == '"' and text[-1] == '"') or (text[0] == "'" and text[-1] == "'")):
return text[1:-1]
return text
def _read_env_file(path: Path) -> dict[str, str]:
if not path.exists():
return {}
result: dict[str, str] = {}
for raw in path.read_text(encoding="utf-8").splitlines():
line = raw.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key = key.strip()
if key:
result[key] = _strip_quotes(value)
return result
def _write_env_file(path: Path, values: dict[str, str]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
lines = [f"{k}={values[k]}" for k in sorted(values.keys())]
path.write_text("\n".join(lines) + ("\n" if lines else ""), encoding="utf-8")
try:
os.chmod(path, 0o600)
except OSError:
pass
def _resolve_app_and_secret() -> tuple[str, str]:
# 优先级:进程环境变量 -> 项目 .env
env_map = {
"app_id": (
os.environ.get("CHANJING_APP_ID")
or os.environ.get("APP_ID")
or os.environ.get("CHANJING_USER_ID")
or os.environ.get("USER_ID")
or ""
).strip(),
"secret_key": (
os.environ.get("CHANJING_SECRET_KEY")
or os.environ.get("SECRET_KEY")
or ""
).strip(),
}
if env_map["app_id"] and env_map["secret_key"]:
return env_map["app_id"], env_map["secret_key"]
file_map = _read_env_file(ENV_FILE)
file_user = (
file_map.get("CHANJING_APP_ID")
or file_map.get("APP_ID")
or file_map.get("CHANJING_USER_ID")
or file_map.get("USER_ID")
or ""
).strip()
file_secret = (file_map.get("CHANJING_SECRET_KEY") or file_map.get("SECRET_KEY") or "").strip()
if file_user and file_secret:
return file_user, file_secret
return "", ""
def refresh_chanjing_access_token(app_id: str, secret_key: str) -> tuple[str | None, str | None]:
body = request_json(
"POST",
f"{API_BASE}/open/v1/access_token",
body={"app_id": app_id, "secret_key": secret_key},
)
if body.get("code") != 0:
return None, body.get("msg", "获取 Token 失败")
payload = body.get("data") or {}
token = payload.get("access_token")
if not token:
return None, "API 返回无 access_token"
env_data = _read_env_file(ENV_FILE)
env_data["CHANJING_APP_ID"] = app_id
env_data["CHANJING_SECRET_KEY"] = secret_key
env_data["CHANJING_ACCESS_TOKEN"] = str(token)
env_data["CHANJING_TOKEN_EXPIRE_IN"] = str(payload.get("expire_in") or "")
_write_env_file(ENV_FILE, env_data)
return token, None
def resolve_chanjing_access_token() -> tuple[str | None, str | None]:
app_id, secret_key = _resolve_app_and_secret()
if not app_id or not secret_key:
_run_open_login_page()
return None, NO_CREDENTIALS_MSG
env_data = _read_env_file(ENV_FILE)
token = (env_data.get("CHANJING_ACCESS_TOKEN") or "").strip()
expire_in = (env_data.get("CHANJING_TOKEN_EXPIRE_IN") or "").strip()
try:
expire_at = int(expire_in) if expire_in else 0
except ValueError:
expire_at = 0
now = int(time.time())
if token and expire_at > now + BUFFER_SECONDS:
return token, None
return refresh_chanjing_access_token(app_id, secret_key)
def print_chanjing_access_token_cli() -> None:
token, err = resolve_chanjing_access_token()
if err:
raise SystemExit(err)
print(token)
# --- 脚本与能力目录(原 base 能力函数) ---
def repo_root() -> Path:
return Path(__file__).resolve().parents[3]
def skills_root() -> Path:
return repo_root() / "skills"
def skill_dir(skill_name: str) -> Path:
return skills_root() / "chanjing-content-creation-skill" / "products" / skill_name
def script_path(skill_name: str, script_name: str) -> Path:
"""L2:`products/<skill>/scripts/`;仅存在于 L3 的技能(如一键成片)在 `orchestration/<skill>/scripts/`。"""
under_products = skill_dir(skill_name) / "scripts" / script_name
if under_products.exists():
return under_products
under_orch = (
skills_root()
/ "chanjing-content-creation-skill"
/ "orchestration"
/ skill_name
/ "scripts"
/ script_name
)
if under_orch.exists():
return under_orch
return under_products
def run_skill_script(
skill_name: str,
script_name: str,
*,
args: list[str] | None = None,
parse_json: bool = False,
cwd: Path | None = None,
) -> dict[str, Any]:
cmd = [sys.executable, str(script_path(skill_name, script_name)), *(args or [])]
# 默认沿用调用方当前工作目录,确保相对输出路径落在用户当前路径。
effective_cwd = cwd or Path.cwd()
proc = subprocess.run(
cmd,
cwd=str(effective_cwd),
text=True,
capture_output=True,
check=False,
)
result: dict[str, Any] = {
"ok": proc.returncode == 0,
"exit_code": proc.returncode,
"stdout": proc.stdout.strip(),
"stderr": proc.stderr.strip(),
"command": cmd,
}
if parse_json and proc.stdout.strip():
try:
result["json"] = json.loads(proc.stdout)
except json.JSONDecodeError as exc:
raise SkillExecutionError(
f"{skill_name}/{script_name} did not return valid JSON"
) from exc
return result
def capability_catalog(
skill_name: str,
*,
manual: str,
operations: list[dict[str, Any]],
) -> dict[str, Any]:
return {
"skill": skill_name,
"manual": manual,
"operations": operations,
}
def config_contract(
*,
preconditions: list[str],
required: list[str],
optional: list[str] | None = None,
) -> dict[str, Any]:
return {
"preconditions": preconditions,
"required": required,
"optional": optional or [],
}
def usage_contract(*, examples: list[str], outputs: list[str]) -> dict[str, Any]:
return {
"examples": examples,
"outputs": outputs,
}
if __name__ == "__main__":
print_chanjing_access_token_cli()
from __future__ import annotations
class SkillError(Exception):
"""Base error for shared skill infrastructure."""
class SkillConfigError(SkillError):
"""Raised when local configuration is missing or invalid."""
class SkillExecutionError(SkillError):
"""Raised when a script invocation fails."""
class SkillHTTPError(SkillError):
"""Raised when an HTTP request fails."""
class SkillTimeoutError(SkillError):
"""Raised when polling or script execution exceeds timeout."""
"""蝉镜通用文件上传:create_upload_url、PUT、file_detail 轮询。"""
from __future__ import annotations
import json
import os
import time
import urllib.parse
import urllib.request
from pathlib import Path
from typing import Any
API_BASE = os.environ.get("CHANJING_API_BASE", "https://open-api.chanjing.cc")
def get_chanjing_file_detail(token: str, file_id: str) -> tuple[Any | None, str | None]:
"""GET file_detail;API 业务错误返回 (None, msg),网络异常向上抛出。"""
url = f"{API_BASE}/open/v1/common/file_detail?id={urllib.parse.quote(file_id)}"
req = urllib.request.Request(url, headers={"access_token": token}, method="GET")
with urllib.request.urlopen(req, timeout=30) as resp:
body = json.loads(resp.read().decode("utf-8"))
if body.get("code") != 0:
return None, str(body.get("msg", "file_detail failed"))
return body.get("data"), None
def poll_chanjing_file_until_ready(
token: str,
file_id: str,
*,
interval: float = 5.0,
timeout: float = 300.0,
ready_statuses: set[Any],
failed_statuses: set[Any] | None = None,
pending_statuses: set[Any] | None = None,
) -> tuple[bool, str | None]:
"""
pending_statuses 非 None 时:仅这些状态视为处理中;其余非 ready 视为失败(定制数字人)。
pending_statuses 为 None 时:非 ready 且非 failed_statuses 则继续等待(视频合成上传)。
"""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
data, err = get_chanjing_file_detail(token, file_id)
if err:
return False, err
if data is None:
return False, "no data"
status = data.get("status")
if status in ready_statuses:
return True, None
if failed_statuses is not None and status in failed_statuses:
return False, data.get("msg") or f"file status={status}"
if pending_statuses is not None:
if status not in pending_statuses:
return False, data.get("msg") or f"unexpected file status={status}"
time.sleep(interval)
return False, "poll timeout"
def fetch_create_upload_url_payload(
token: str, service: str, name: str
) -> tuple[dict[str, Any] | None, str | None]:
"""GET create_upload_url;成功返回 (data 字段内容, None)。"""
qs = urllib.parse.urlencode({"service": service, "name": name})
url = f"{API_BASE}/open/v1/common/create_upload_url?{qs}"
req = urllib.request.Request(url, headers={"access_token": token}, method="GET")
with urllib.request.urlopen(req, timeout=30) as resp:
body = json.loads(resp.read().decode("utf-8"))
if body.get("code") != 0:
return None, str(body.get("msg", body))
data = body.get("data", {})
if not isinstance(data, dict):
return None, "invalid create_upload_url data"
return data, None
def put_file_to_sign_url(sign_url: str, content: bytes, mime_type: str) -> tuple[bool, str | None]:
put_req = urllib.request.Request(
sign_url,
data=content,
headers={"Content-Type": mime_type},
method="PUT",
)
try:
with urllib.request.urlopen(put_req, timeout=120) as put_resp:
if put_resp.status not in (200, 204):
return False, f"上传返回状态: {put_resp.status}"
except Exception as exc:
return False, f"上传失败: {exc}"
return True, None
def upload_local_file_via_chanjing(
token: str,
local_path: Path,
service: str,
*,
poll_interval: float = 5.0,
poll_timeout: float = 300.0,
ready_statuses: set[Any],
failed_statuses: set[Any] | None = None,
pending_statuses: set[Any] | None = None,
) -> tuple[str | None, str | None]:
"""
完整上传链路。成功返回 (file_id, None),失败返回 (None, error_message)。
"""
if not local_path.is_file():
return None, f"文件不存在: {local_path}"
payload, err = fetch_create_upload_url_payload(token, service, local_path.name)
if err:
return None, err
sign_url = payload.get("sign_url")
mime_type = payload.get("mime_type", "application/octet-stream")
file_id = payload.get("file_id")
if not sign_url or not file_id:
return None, "响应缺少 sign_url 或 file_id"
content = local_path.read_bytes()
ok, put_err = put_file_to_sign_url(sign_url, content, mime_type)
if not ok:
return None, put_err or "上传失败"
ready, poll_err = poll_chanjing_file_until_ready(
token,
file_id,
interval=poll_interval,
timeout=poll_timeout,
ready_statuses=ready_statuses,
failed_statuses=failed_statuses,
pending_statuses=pending_statuses,
)
if not ready:
return None, poll_err or "文件未就绪"
return str(file_id), None
from __future__ import annotations
import json
import logging
import sys
from typing import Any
def get_logger(name: str) -> logging.Logger:
logger = logging.getLogger(name)
if logger.handlers:
return logger
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.propagate = False
return logger
def log_event(logger: logging.Logger, event: str, **fields: Any) -> None:
payload = {"event": event, **fields}
logger.info(json.dumps(payload, ensure_ascii=False, sort_keys=True))
"""AI 创作(ai_creation)Open API 封装。"""
from __future__ import annotations
from typing import Any
from open_api_client import api_get, api_post
RUNNING_STATUSES = {"Queued", "Ready", "Generating"}
SUCCESS_STATUSES = {"Success"}
FAILED_STATUSES = {"Error", "Fail"}
def get_ai_creation_task(token: str, unique_id: str) -> Any:
return api_get(token, "/open/v1/ai_creation/task", {"unique_id": unique_id}, query_doseq=True)
def list_ai_creation_tasks(
token: str,
creation_type: Any,
page: int = 1,
page_size: int = 50,
unique_ids: list | None = None,
is_success: bool | None = None,
) -> Any:
payload: dict[str, Any] = {
"unique_ids": unique_ids or [],
"type": creation_type,
"page": page,
"page_size": page_size,
}
if is_success is not None:
payload["is_success"] = bool(is_success)
return api_post(token, "/open/v1/ai_creation/task/page", payload)
def first_output_url(data: Any) -> str | None:
urls = (data or {}).get("output_url") or []
if isinstance(urls, list) and urls:
return urls[0]
return None
"""文生数字人(aigc photo/motion/lora)Open API 封装。"""
from __future__ import annotations
from typing import Any
from open_api_client import api_get, api_post
PHOTO_RUNNING = {"Ready", "Generating", "Queued"}
PHOTO_SUCCESS = {"Success"}
PHOTO_FAILED = {"Error", "Fail"}
MOTION_RUNNING = {"Ready", "Generating", "Queued"}
MOTION_SUCCESS = {"Success"}
MOTION_FAILED = {"Error", "Fail"}
LORA_RUNNING = {"Queued", "Published", "Generating"}
LORA_SUCCESS = {"Success"}
LORA_FAILED = {"Fail"}
def get_photo_task(token: str, unique_id: str) -> Any:
return api_get(token, "/open/v1/aigc/photo/task", {"unique_id": unique_id})
def list_photo_tasks(token: str, page: int = 1, page_size: int = 10) -> Any:
return api_get(token, "/open/v1/aigc/photo/task/page", {"page": page, "page_size": page_size})
def get_motion_task(token: str, unique_id: str) -> Any:
return api_get(token, "/open/v1/aigc/motion/task", {"unique_id": unique_id})
def get_lora_task(token: str, lora_id: str) -> Any:
return api_get(token, "/open/v1/aigc/lora/task", {"lora_id": lora_id})
def first_output_url(data: Any) -> str | None:
urls = (data or {}).get("output_url") or []
if isinstance(urls, list) and urls:
return urls[0]
return None
"""蝉镜 Open API:带 access_token 的 JSON GET/POST,与 base.API_BASE 环境约定一致。"""
from __future__ import annotations
import json
import os
import urllib.parse
import urllib.request
from typing import Any
from exceptions import SkillHTTPError
API_BASE = os.environ.get("CHANJING_API_BASE", "https://open-api.chanjing.cc")
def api_get(
token: str,
path: str,
query: dict[str, Any] | None = None,
*,
query_doseq: bool = False,
timeout: int = 30,
) -> Any:
query = query or {}
suffix = ""
if query:
suffix = "?" + urllib.parse.urlencode(query, doseq=query_doseq)
url = f"{API_BASE}{path}{suffix}"
req = urllib.request.Request(url, headers={"access_token": token}, method="GET")
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
body = json.loads(resp.read().decode("utf-8"))
except Exception as exc:
raise SkillHTTPError(str(exc)) from exc
if body.get("code") != 0:
raise SkillHTTPError(str(body.get("msg", body)))
return body.get("data")
def api_post(token: str, path: str, payload: dict[str, Any], *, timeout: int = 30) -> Any:
req = urllib.request.Request(
f"{API_BASE}{path}",
data=json.dumps(payload).encode("utf-8"),
headers={"access_token": token, "Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
body = json.loads(resp.read().decode("utf-8"))
except Exception as exc:
raise SkillHTTPError(str(exc)) from exc
if body.get("code") != 0:
raise SkillHTTPError(str(body.get("msg", body)))
return body.get("data")
Evals(最小评测闭环)
本目录用于验证 chanjing-content-creation-skill 的路由准确率、契约一致性与链路可用性。
评测目标
1. 路由准确率:用户意图是否命中正确 L2/L3 子技能。 2. 语义一致性:是否按统一 outcome_code 语义返回。 3. 契约一致性:门控、依赖、副作用声明是否与实现一致。 4. 链路可用性:关键场景是否可端到端跑通(可用 mock 或 dry-run)。
建议指标
routing_accuracy:>= 90%outcome_semantics_coverage:= 100%(5 个 code 都有测试样例)contract_consistency_check:= passcritical_flow_pass_rate:>= 90%
执行方式(人工或脚本均可)
1. 读取 router-evals.jsonl,逐条输入测试提示词。 2. 记录实际命中 skill 与返回语义。 3. 与样例中的 expected_route、expected_outcome 对比。 4. 输出评测报告(可简单用 markdown 表格)。
文件说明
router-evals.jsonl:路由与错误语义样例集
{"id":"route-001","prompt":"帮我做一个60秒的品牌口播短视频","expected_route":"orchestration/chanjing-one-click-video-creation/chanjing-one-click-video-creation-SKILL.md","expected_outcome":"ok"}
{"id":"route-002","prompt":"我只要配音,把这段文案转成女声","expected_route":"products/chanjing-tts/chanjing-tts-SKILL.md","expected_outcome":"ok"}
{"id":"route-003","prompt":"用我的声音做克隆,再读这段稿子","expected_route":"products/chanjing-tts-voice-clone/chanjing-tts-voice-clone-SKILL.md","expected_outcome":"ok"}
{"id":"route-004","prompt":"我有一段真人视频,帮我做对口型","expected_route":"products/chanjing-avatar/chanjing-avatar-SKILL.md","expected_outcome":"ok"}
{"id":"route-005","prompt":"帮我做卡通风格12镜短剧","expected_route":"orchestration/cartoon-video-creation/cartoon-video-creation-SKILL.md","expected_outcome":"ok"}
{"id":"route-006","prompt":"上传这个人物视频,训练一个定制数字人","expected_route":"products/chanjing-customised-person/chanjing-customised-person-SKILL.md","expected_outcome":"ok"}
{"id":"route-007","prompt":"先给我生成人设图,再做一个会说话的视频","expected_route":"products/chanjing-text-to-digital-person/chanjing-text-to-digital-person-SKILL.md","expected_outcome":"ok"}
{"id":"route-008","prompt":"用蝉镜生成一张海报,再生成5秒视频","expected_route":"products/chanjing-ai-creation/chanjing-ai-creation-SKILL.md","expected_outcome":"ok"}
{"id":"route-009","prompt":"我没配过秘钥,先帮我配置蝉镜AKSK","expected_route":"products/chanjing-credentials-guard/chanjing-credentials-guard-SKILL.md","expected_outcome":"auth_required"}
{"id":"route-010","prompt":"我就要写标题和脚本,不要生成视频","expected_route":"none","expected_outcome":"need_param"}
{"id":"route-011","prompt":"帮我做视频,但我不给任何主题和素材","expected_route":"orchestration/chanjing-one-click-video-creation/chanjing-one-click-video-creation-SKILL.md","expected_outcome":"need_param"}
{"id":"route-012","prompt":"系统一直超时,先别下载,告诉我怎么重试","expected_route":"current_skill","expected_outcome":"timeout"}
# skill文档规范 §1–§2 合规
name: chanjing-content-creation-skill
version: 0.1.0
vendor: chanjing
runtime:
interpreter: python3
dependencies:
- ffmpeg
- ffprobe
env:
required: []
optional:
- CHANJING_APP_ID
- CHANJING_SECRET_KEY
- CHANJING_ACCESS_TOKEN
- CHANJING_TOKEN_EXPIRE_IN
- CHANJING_API_BASE
- CHAN_SKILLS_DIR
permissions:
network_mode: allowlist
allowed_hosts:
- open-api.chanjing.cc
- www.chanjing.cc
filesystem:
read_roots:
- "${WORKSPACE_ROOT}"
- "${SKILL_DIR}"
write_roots:
- "${WORKSPACE_ROOT}"
allowed_commands:
- python3
- ffmpeg
- ffprobe
schemaVersion: 1
skill:
id: chanjing-content-creation-skill
author: chan-skills
category: 媒体处理
tags:
- 蝉镜
- Chanjing
- TTS
- 数字人
- 对口型
- 文生视频
- 一键成片
- 卡通视频
summary: >-
蝉镜内容创作聚合技能包:凭据管理、TTS、数字人口播、对口型、文生视频、声音克隆、
定制数字人、一键成片编排等。本 Skill 是路由入口,按意图分流到子产品/编排。
skillDoc: SKILL.md
environment:
variables:
- name: CHANJING_API_BASE
required: false
description: Open API 基址,默认 https://open-api.chanjing.cc
- name: CHAN_SKILLS_DIR
required: false
description: 一键成片脚本仓库根路径(当前实现实际读取)
credentials:
model: environment
persistAccessTokenOnDisk: false
sensitiveFields: []
clientPermissions:
network:
httpsOutbound: true
documentedHosts:
- open-api.chanjing.cc
filesystem:
read:
- "${SKILL_DIR}/"
- user_workflow_and_output_paths
write:
- "${SKILL_DIR}/.env"
- user_output_directory
browser:
mayOpenForAuth: true
subprocess:
allowedPatterns:
- python3
- ffmpeg
- ffprobe
mayInvokeSiblingSkillScripts: true
userContent:
mayDownloadFromApiResponseUrls: true
metadata:
openclaw:
homepage: https://doc.chanjing.cc
os:
- darwin
- linux
requires:
bins:
- python3
anyBins:
- ffmpeg
- ffprobe
env:
- CHANJING_APP_ID
- CHANJING_SECRET_KEY
agentPolicy:
alwaysSkill: false
modifiesOtherSkillsOrGlobalAgent: false
动漫短视频编排(cartoon-video-creation)
编排定位
- 层级:L3
orchestration,仅负责编排与状态推进,不实现产品级 API 细节。 - 目标:将动漫/小说主题自动转为单集短视频(<=120s,12 镜)。
- 边界:
- 文案脚本细则:`references/story-script-guide.md`
- 文生图提示词细则:`references/image-prompt-guide.md`
- 图生视频提示词细则:`references/video-prompt-guide.md`
- 人物身份卡模板:`templates/person-prompt.md`
跨分镜:风格与主题一致(强制)
全集 12 镜须在同一画风、同一主题与世界观调性下呈现;不得中途换画风(如从赛璐珞突然变写实)、不得偏离本集确立的题材与情绪基调。
- 落地方式:在
project.json中固化「全片不变特征」(风格锚、画质锚、主题锚、一致性强化等,字段命名与写法见 `references/image-prompt-guide.md`)。 - 提示词要求:每一镜的
image_prompt与video_prompt都必须显式包含上述不变特征(可与分镜变量拼接,但不可省略);图生视频侧须与首帧图侧同源,避免「静帧日系、动效美漫」类漂移。细则见 image / video 两份 reference。
文案与公共音色:人设一致(强制)
本编排以 AI 分镜画面 + TTS 配音 为主(可选对口型)。台词、`speaker`、公共音色须与角色设定一致:
- 性别与气质:
story_bible.json与本集主题、情感基调先确定叙述视角(旁白 / 角色对白)及说话者性别、年龄气质;characters/*.json身份卡中的性别、人设须与 `speaker` 字段一致。 - 公共音色:首集用
products/chanjing-tts/scripts/list_voices.py --fetch-all建立voice_registry.json;为每个 `speaker`(或旁白) 绑定一条 `audio_man`:`list_voices` 返回的 `gender` 须与该说话者在剧本与身份卡中的性别呈现一致;在一致前提下按 `name` 语义匹配本集应用场景(热血/治愈/搞笑/严肃等)与情感要求。 - 多角色:不同
speaker使用不同audio_man,且各音色与对应角色身份卡一致;同一角色全片固定同一音色,不得中途换声。 - 与一键成片对齐:选型逻辑与 `chanjing-one-click-video-creation` 的 `templates/render-rules.md` §3·C.2.5、§3·C.4 一致(仅本场景通常无公共数字人出镜,不涉及
list_figures)。
---
涉及产品(L2)
| 能力 | 产品目录 | 用途 |
|---|---|---|
| 文生图/图生视频 | chanjing-ai-creation | 参考图、首帧图、分镜视频 |
| TTS | chanjing-tts | 台词转音频 |
| 声音克隆(可选) | chanjing-tts-voice-clone | 角色定制音色 |
| 分镜对口型(可选) | chanjing-avatar | 仅在 enable_lip_sync=true 时对每镜执行 lip-sync(model=2 卡通) |
| 本地合成 | ffmpeg/ffprobe | 音视频对齐、拼接、增强 |
说明:默认成片为「动漫画面 + 分镜配音」,不对口型;与一键成片混剪路径不同。
---
总流程
flowchart TD
IN["输入:动漫/小说名 + 集数"] --> P1["Phase 1 故事研究"]
P1 --> P2["Phase 2 角色建档 + 参考图"]
P2 --> CP1{"CP-1 角色确认"}
CP1 --> P3["Phase 3 分镜脚本(12镜)"]
P3 --> CP2{"CP-2 分镜确认"}
CP2 --> P4["Phase 4 提示词生成"]
CP2 --> P6["Phase 6 台词配音"]
P4 --> P5["Phase 5 视觉生成"]
P6 --> P5
P5 --> CP3{"CP-3 首帧质检"}
CP3 --> P7["Phase 7 合成成片"]
P7 --> OUT["final.mp4"]并行关系:
- Phase 4(提示词)与 Phase 6(TTS)并行
- 默认:Phase 5 在 5.2 图生视频完成后即可记为
video_base_done;Phase 7 将每镜 AI 视频 + 分镜配音 做时长对齐后拼接 - 若
project.json.enable_lip_sync=true:在 Phase 5.2 与 Phase 6 均完成后执行 Phase 5.3 逐镜对口型,Phase 7 使用lip_sync_done视频进入拼接(与默认二选一)
Workflow Output Semantics
本 Skill 输出语义对齐 `../orchestration-contract.md`:
ok:成功交付final.mp4(并保留中间产物与状态文件)need_param:必要输入缺失(主题、集数或关键约束不足)auth_required:凭据不可用(典型10400)upstream_error:上游图/视频/TTS 或渲染步骤失败timeout:异步任务轮询超时
---
编排执行步骤
Phase 1:故事研究
1. 使用 web_search 检索作品剧情、人物关系、世界观。 2. 同步检索相关话题在短视频平台/公开网页上的热门讨论、爆点标题、常见争议点与高互动切口,提炼可复用的 Hook 方向、情绪触发点与观众最关心的问题;仅可参考表达策略,不得直接照搬文案。 3. 对知名 IP(如蜡笔小新)额外检索主要角色公开形象图,记录来源页面与可用图片 URL。 4. 产出 story_bible.json,至少包含:title、genre、world_setting、plot_arcs、main_characters、character_reference_candidates,并建议补充 trend_hooks / audience_interest_points 供 Phase 3 写稿参考。 5. 支持续集补充检索,不要求首次穷尽剧情。
输出路径:{project_dir}/story_bible.json
---
Phase 2:角色建档与参考图
1. 依据 templates/person-prompt.md 生成角色身份卡。 2. 优先复用 Phase 1 的角色参考候选图;每个主角至少确认 1 张主参考图(正脸/半身清晰,无遮挡)。 3. 若公开图质量不足,再按 references/image-prompt-guide.md 生成核心参考图与可选辅助图。 4. 写入角色档案与全局配置;在 project.json 中写入全片共用的 视觉与主题不变特征(风格锚 / 画质锚 / 主题锚 / 一致性强化 等,见 references/image-prompt-guide.md),供后续每一镜文生图、图生视频提示词复用。
输出:
characters/{character_id}.jsoncharacters/{character_id}_ref.png(核心)- 可选:
_ref_side.png、_ref_full.png project.json(须含可逐镜复制的风格与主题锚定字段)
CP-1(可跳过):角色形象确认后继续。
---
Phase 3:分镜脚本编排(12 镜)
1. 按 references/story-script-guide.md 规划单集叙事结构与情绪弧线;单集主题句与世界观边界一旦确立,12 镜剧情与场景变化不得与之矛盾(视觉提示词将据此与 project.json 主题锚对齐)。 2. 文案与镜头切口应结合 Phase 1 提炼的 `trend_hooks` / `audience_interest_points`,优先吸收互联网上该题材的高互动开场、争议问题、反差表达与情绪触发点,用于增强 Hook、冲突与留悬念能力;禁止原样复刻热点文案、标题或他人脚本。 3. 固定约束:
- 总时长 <= 120s
- 固定 12 镜
- 单镜 <= 10s
- 单镜台词 <= 40 字
- 每镜仅 1 名主角色作为视觉主体(primary_subject)
- 主角色位于画面中轴附近且占画幅 >= 1/3
- 其它角色仅允许远景/背景人群形态,不得与主角色争夺主体面积
4. 生成 storyboard.json,每镜至少包含:
scene_id、scene_titlenarrative_phase(hook/build/payoff)emotion_intensityduration、voiceover、speakercamera_shot、camera_movementprimary_subject、primary_subject_ratiobackground_characters_policytransition_hint、status
CP-2(可跳过):12 镜摘要确认后继续。
---
Phase 4:提示词生成(与 Phase 6 并行)
按分镜逐镜写入:
image_prompt:规则见references/image-prompt-guide.md;每镜须拼接project.json中的风格锚、画质锚、主题锚、一致性强化与负向约束(全集不变块),不得省略。video_prompt:规则见references/video-prompt-guide.md;每镜须含与文生图同源的风格与主题不变块,与首帧画风一致。negative_prompt:来自project.jsonref_img_url:当前镜主角色参考图 URL(优先 Phase 1 检索结果,其次角色档案图)
输出:更新 storyboard.json。
---
Phase 5:视觉生成(默认不含口型)
在 project.json 中约定布尔字段 `enable_lip_sync`(默认 false)。未开启时本阶段在 5.2 完成后结束,直接进入 Phase 7 与配音合成。
5.1 首帧图生成(参考图生图优先)
- 调用
products/chanjing-ai-creation/scripts/submit_task.py(creation-type=3) - 参数读取:
model-code<-project.json.image_modelaspect-ratio<-project.json.aspect_ratioprompt<- 当前镜image_promptref-img-url<- 当前镜ref_img_url(主角色参考图)- 记录
image_task_id、image_url、status=image_done - 若
ref-img-url不支持或失效,自动降级为纯 prompt(保留主角色构图约束) - 构图验收:主角色居中且占比 >= 1/3,其他角色只允许远景,不通过则仅重生该镜首帧
5.2 图生视频
- 调用
submit_task.py(creation-type=4) - 参数读取:
model-code<-project.json.video_modelprompt<- 当前镜video_promptref-img-url<- 当前镜首帧图- 记录
video_task_id、video_url、status=video_base_done transition_hint=continuous时可启用尾帧衔接;不支持则自动降级- 持续约束:主角色主体面积和居中关系在视频内尽量保持一致,其他角色保持远景陪衬
5.3 逐镜对口型(可选,默认跳过)
- 仅当
project.json.enable_lip_sync=true(或用户明确要求对口型并已写入该字段)时执行;默认不调用chanjing-avatar。 - 目标:在已具备分镜视频与分镜音频的前提下,对每个 AI 分镜执行一次 lip-sync,输出可用于成片的口型对齐视频。
- 调用
products/chanjing-avatar/scripts/upload_file.py上传: - 分镜视频(service=
lip_sync_video) - 分镜音频(service=
lip_sync_audio) - 调用
products/chanjing-avatar/scripts/create_task.py: --video-file-id<- 当前镜视频 file_id--audio-file-id<- 当前镜音频 file_id--model 2(卡通形象专用)- 调用
products/chanjing-avatar/scripts/poll_task.py获取lip_sync_video_url - 记录
lip_sync_task_id、lip_sync_video_url、status=lip_sync_done - 若单镜 lip-sync 失败,标记
lip_sync_failed并按重试策略仅重跑该镜
CP-3:首帧图自检(角色一致性 + 构图占比)。若已开启对口型,可增加口型对齐抽检(唇动与音频节奏一致)。
---
Phase 6:台词配音(与 Phase 4 并行)
1. 首集读取 list_voices.py --fetch-all 并建立 voice_registry.json:按编排定位「文案与公共音色:人设一致」,为每个 speaker 选定 `audio_man`(性别与角色/旁白一致,气质匹配主题与情感)。 2. 后续集直接复用 voice registry(同一项目同一套角色音色不变)。 3. 每镜调用 TTS 脚本,写入音频与时长、任务号、字幕时间戳。
输出:
audios/{scene_id}.wav- 分镜字段:
audio_task_id、audio_url、audio_duration、tts_subtitles status=audio_done
---
Phase 7:合成与成片
1. 默认(未对口型):单镜使用 video_base_done 的 AI 分镜视频,与对应镜 audio_done 的配音做时长对齐(裁剪或补帧、必要时静音原视频音轨后混入 TTS)。 2. 对口型开启:单镜仅使用 lip_sync_done 视频,进行时长对齐(裁剪或补帧)。 3. 全集拼接:按镜头顺序 concat。 4. 可选增强(有则执行,无则跳过):
- 字幕(基于
tts_subtitles) - BGM 混入
- 转场(cut/fade/dissolve)
- 片头/片尾卡
5. 输出 final.mp4。
---
质量门控与重试
检查点:
- CP-1:角色定稿
- CP-2:分镜确认
- CP-3:首帧质检(对口型开启时含口型抽检)
重试策略:
- 仅重生失败镜头(
retry/failed) - 每镜最多
max_retry(默认 2) - 超限标记
image_failed或video_failed - 跳过镜头 > 2 时输出状态建议为
partial
---
断点恢复
通过 storyboard.json.scenes[].status 恢复:
pending:从首帧图开始image_done:从图生视频开始video_base_done:默认路径下可与audio_done一并进入 Phase 7;若enable_lip_sync=true则等待口型步骤lip_sync_done:对口型开启时可直接进入合成audio_done:与video_base_done齐套后进入合成(默认路径)retry:重跑失败步骤composed:跳过
任务追踪字段:
image_task_idvideo_task_idaudio_task_idlip_sync_task_id(仅对口型开启时)
---
数据与目录(摘要)
outputs/cartoon-video/{project_name}/
├── project.json
├── story_bible.json
├── voice_registry.json
├── characters/
└── episodes/ep{N}/
├── storyboard.json
├── images/
├── videos/
├── audios/
├── composed/
├── subtitles/
└── final.mp4字段细则:
- 分镜脚本字段:
references/story-script-guide.md - 文生图提示词字段:
references/image-prompt-guide.md - 图生视频提示词字段:
references/video-prompt-guide.md
---
编排检查清单(摘要)
1. 环境满足 ffmpeg/ffprobe 2. 分镜满足 12 镜、时长与字数上限 3. 提示词字段完整写入 storyboard.json 4. 失败镜头可选择性重试,成功镜头不回滚 5. 续集读取上一集 story.json 的衔接信息 6. 所有状态字段与任务 ID 已持久化 7. 默认:每个 AI 分镜均达到 video_base_done 且对应镜 audio_done 后可进入成片拼接;若 enable_lip_sync=true,则每镜需达到 lip_sync_done
---
文档索引
- 编排共用约定:`../orchestration-contract.md`
- 包级运行时契约:`../../SKILL.md`
- 文案脚本细则:`references/story-script-guide.md`
- 文生图细则:`references/image-prompt-guide.md`
- 图生视频细则:`references/video-prompt-guide.md`
- 身份卡模板:`templates/person-prompt.md`
文生图提示词指南
本文档为 `cartoon-video-creation-SKILL.md` Phase 2(角色建档)与 Phase 4.1(首帧图提示词)的详细参考。Agent 在执行对应阶段时按需 Read。
---
1. 提示词总公式
分镜首帧图 image_prompt 按以下公式组装:
image_prompt = 风格锚 + 画质锚 + 主题锚 + 一致性强化 + 角色身份卡 + 分镜变量 + 负向约束其中:
风格锚 / 画质锚 / 主题锚 / 一致性强化 / 负向约束:来自project.json,全集 12 镜保持不变;每一镜的完整image_prompt都必须显式写出这些块(可与上一镜逐字相同,禁止省略或改用近义改写导致画风漂移)。角色身份卡:来自characters/{character_id}.json,角色外观描述逐字固定。分镜变量:来自storyboard.json当前分镜字段(画面、情绪、镜头、场景)——仅本块随镜变化。
1.1 全片不变特征(project.json 建议字段)
在 Phase 2 写入 project.json,供 Phase 4 逐镜拼接;字段名可压缩为 JSON key,含义如下:
| 块 | 内容示例 | 作用 |
|---|---|---|
风格锚 | 赛璐珞、平涂、线条粗细、是否 Q 版、参考流派/IP 气质 | 跨分镜画风统一 |
画质锚 | 分辨率感、细节密度、是否胶片颗粒 | 成片质感统一 |
主题锚 | 本集一句主题 + 世界观关键词(时代、地域、科技/魔法层级) | 叙事与视觉主题不跑偏 |
一致性强化 | 全局光照习惯、默认色调倾向、禁止元素提醒 | 减少镜间色差与设定冲突 |
负向约束 | 禁写实皮肤、禁水印、禁文字等 | 全集共用 negative |
质检:任意两镜若去掉「分镜变量」后,剩余前缀应与 project.json 中上述块一致(允许标点与空格差异,不允许语义改换画风)。
---
2. 角色身份卡结构
角色身份卡分为外观维度(固定)与表演维度(用于变量生成):
2.1 外观维度(9 项,固定)
| 维度 | 说明 |
|---|---|
| 头身比 | 如 7-head-tall slim bishounen / 3-head-tall chibi |
| 面容 | 面型、肤色、五官特征 |
| 眼睛 | 瞳色、瞳形、高光位置 |
| 发型 | 发色、长度、造型、发饰 |
| 体型 | 身高体态 |
| 服装 | 主服装描述(固定) |
| 配饰/武器 | 标志性道具 |
| 特征标记 | 疤痕、纹身、兽耳等 |
| 角色色板 | 主色 + 辅色 + 点缀色 HEX |
2.2 表演维度(3 项,可变)
| 维度 | 说明 | 示例 |
|---|---|---|
| 标志性表情 | 角色最具辨识度的默认表情 | 半闭眼冷淡、狡黠微笑 |
| 标志性姿态 | 角色常见肢体习惯 | 抱臂、单手插袋 |
| 情绪范围 | 本角色可覆盖的情绪区间 | 冷漠→惊讶→愤怒→释然 |
---
3. 一致性锚定栈(Anchor Stack)
角色一致性由四层共同保障:
1. Seed 锚(可选,最强) 2. 身份卡锚(固定文本) 3. 参考图锚(ref_image_url) 4. 风格锚(project.json 全局配置)
降级策略:
- API 不支持 seed 或
ref-img-url时,自动回退到身份卡 + 风格锚 - 即使发生降级,也不得改写身份卡外观维度文本
---
4. 参考图策略
在动漫短剧场景,参考图来源按优先级执行:
1. 联网检索图(优先):通过 web_search 获取角色公开形象图,记录来源页与图片 URL。 2. 项目既有角色图:characters/{character_id}_ref*.png。 3. 纯 prompt 生成:仅在前两者不可用时使用。
知名 IP(如蜡笔小新)默认应先做联网检索,不建议直接跳过参考图阶段。
4.1 核心参考图(必须)
- 1 张正面半身、中性表情、白背景
- 用于后续首帧图调用的
--ref-img-url - 若来自联网检索,需满足:人物清晰、无遮挡、无大面积文字水印、分辨率可用
4.2 辅助参考图(建议)
| 辅助图 | 目的 | 文件示例 |
|---|---|---|
| 3/4 侧面像 | 侧脸一致性校验 | {id}_ref_side.png |
| 全身立绘 | 体型与服装比例校验 | {id}_ref_full.png |
| 标志性表情 | 表演风格校验 | {id}_ref_expr.png |
辅助参考图不要求全部传给 API;主要用于 Agent 质检和 Prompt 修正。
---
5. 分镜变量生成规则
分镜变量 推荐覆盖以下子维度:
- 表情:眉眼、嘴部、脸部张力
- 姿态:重心、肩颈、手势
- 衣物与发丝动态
- 场景层次:前景/中景/远景
- 镜头角度:平视/俯视/仰视
- 光照:主光方向、硬软光
- 色调:冷暖、对比、饱和度
- 特效粒子:雨雪、烟尘、魔法粒子
- 氛围词:压迫、轻松、悬疑等
- 主体构图:主角色居中、主体占比、背景角色远景化
5.1 主体构图硬约束(动漫短剧)
每镜 image_prompt 必须显式加入以下约束:
1. 仅 1 名主角色作为视觉主体(primary_subject) 2. 主角色位于画面中央区域(center composition) 3. 主角色面积占画幅 >= 1/3(可写 subject occupies at least one-third of frame) 4. 其他角色仅允许远景或背景轮廓(secondary characters in far background only)
推荐补充语句:
single primary character centered, occupying at least one-third of the frame; all secondary characters remain small in distant background---
6. 表情描述规范(必须具体化)
禁止使用纯情绪词(如 happy, angry);必须拆成可视化细节。
| 模糊写法 | 推荐写法 |
|---|---|
| happy | corners of mouth curving upward, eyes narrowing into crescents |
| angry | brows deeply furrowed, jaw clenched, nostrils slightly flared |
| sad | inner brows raised, lower lip trembling, gaze dropping downward |
| surprised | eyebrows raised high, eyes widened, mouth forming a small O |
| cold | half-lidded eyes, lips in a thin neutral line, chin slightly raised |
---
7. 多角色画面规则
多角色场景中,image_prompt 需补充:
1. 角色空间关系(前后左右与距离) 2. 视线关系(谁看向谁) 3. 互动动作(递物、对峙、追逐) 4. 主次关系(主角占画面比例 >= 1/3,其它角色仅远景)
推荐表达:
Character A stands half-step in front-left of Character B, side-facing B, eye contact maintained.动漫短剧场景建议追加:
Character A remains the sole dominant subject in center frame; Character B and others appear only in distant background with small scale.---
8. 题材适配参考
| 题材类型 | 风格锚方向 | 身份卡侧重 | 负向约束侧重 |
|---|---|---|---|
| 玄幻/仙侠 | 国漫古风、水墨、动态构图 | 服装纹饰、法宝、光效 | 现代服饰、写实人像 |
| 热血少年漫 | 赛璐珞、高饱和、速度线 | 发型、招牌动作、战斗姿态 | 照片质感、真实皮肤纹理 |
| 日常喜剧 | Q 版、粗轮廓、平涂 | 头身比、夸张表情 | 写实比例、复杂阴影 |
| 科幻/赛博 | 霓虹、硬表面、未来光效 | 机械配件、制服、HUD | 古风元素、Q 版变形 |
| 少女漫 | 柔光、细线条、装饰粒子 | 眼部高光、发丝层次 | 粗重线条、高噪点 |
| 武侠/历史 | 写实美型+水墨点缀 | 兵器形制、时代服饰 | 现代元素、过度卡通化 |
---
9. 质检清单(首帧图)
生成完成后,逐镜检查:
- [ ] 本镜 `image_prompt` 是否含 `project.json` 中的风格锚、画质锚、主题锚、一致性强化与负向约束(全集不变块完整,未省略)
- [ ] 与相邻分镜相比,除「分镜变量」外是否与全片锚定一致(无画风突变)
- [ ] 主角脸型与发型是否与参考图一致
- [ ] 服装主色和标志性配饰是否保留
- [ ] 手部、眼睛、耳饰等细节是否畸形
- [ ] 表情是否与
emotion和台词语义一致 - [ ] 镜头景别与
camera_shot是否匹配 - [ ] 场景元素是否符合
scene_environment - [ ] 主角色是否居中且占画幅 >= 1/3
- [ ] 其它角色是否仅为远景陪衬(无抢主体)
不通过的分镜仅重生该镜,不影响其它镜头。
文案脚本编排指南
本文档为 `cartoon-video-creation-SKILL.md` Phase 3(分镜规划)的详细参考。Agent 在执行 Phase 3 时按需 Read。
---
1. 叙事结构(Hook–Build–Payoff)
每集必须遵循三段式结构——这是短视频留存率的核心驱动力。研究表明前 3 秒的视觉冲击可提升 19% 留存率。
| 段落 | 分镜范围 | 时长占比 | 叙事目标 | 景别倾向 |
|---|---|---|---|---|
| Hook(钩子) | #1–#2 | ~15% (15–20s) | 制造悬念/冲突/震撼画面,前 3 秒必须抓住注意力 | 特写 / 大特写 / 极端角度 |
| Build(推进) | #3–#9 | ~60% (60–75s) | 展开故事、升级矛盾、铺垫情感转折 | 中景 / 全景交替 |
| Payoff(收束) | #10–#12 | ~25% (25–30s) | 高潮爆发 → 情感释放/反转 → 留悬念(下集钩子) | 特写 → 全景 → 特写 |
Hook 设计要点
- 第 1 镜前 3 秒是全集生死线,必须有视觉冲击或悬念
- 有效 Hook 类型:冲突开场(角色处于危机中)、悬念引入(提出未解之谜)、视觉震撼(极端画面)、反常规(出乎意料的场景)
- 避免:缓慢铺垫、文字说明、大段旁白开场
- 参考热门内容:在写 Hook 前,先用
web_search补充该作品 / 题材 / 角色在互联网上的高互动讨论点、争议点、名场面、观众最爱提问;优先把这些“观众已被验证会停留”的切口,改写为本集自己的开场冲突或悬念,不得直接照抄热点文案。
Build 节奏控制
- 矛盾应递进升级,不要一直平推
- 至少安排 1 次"假释放"(情绪短暂缓和后再收紧),制造节奏起伏
- 每 2–3 镜切换一次场景/角色组合,防止视觉疲劳
Payoff 收束技巧
- #10 为情绪最高点(
emotion_intensity≥ 8) - #11 为释放/反转,强度略降
- #12 必须留下下集钩子(
next_episode_hook),强度不应高于 #10(收束感)
---
1.1 单集主题与世界观一致(与视觉锚定对齐)
分镜脚本层须先确立 单集主题句(一句话)与 世界观边界(时代、力量体系、是否现代/古代/科幻等)。12 镜的剧情推进、场景切换、情绪转折均不得与此矛盾,以便 Phase 4 在每一镜的 image_prompt / video_prompt 中复用同一套 主题锚 与 风格锚。
- 若某镜需要「换场景」,仍须在
storyboard.json中说明同一世界观下的地点变化,避免脚本要求赛博城下一镜却写「中世纪城堡」而无叙事铺垫(除非本集主题为穿越且已在主题句声明)。 - 与 `../cartoon-video-creation-SKILL.md`「跨分镜:风格与主题一致」及 `image-prompt-guide.md` 的
主题锚字段联动。
叙述者与台词性别一致(配音层)
- 分镜字段 `speaker` 须与角色档案中的姓名/性别/身份一致;旁白单独标记(如
narrator),勿与角色对白混用同一 speaker id 却切换性别自称。 - 全片 `voiceover` 人称(我/你/他称)与
speaker身份一致,便于 Phase 6 为每个说话者绑定同性别、气质匹配的公共 TTS 音色(见编排 SKILL「文案与公共音色:人设一致」)。
1.2 热门话题参考(增强吸引力)
生成单集文案前,建议先补一轮该题材的互联网热门内容研究,将结果作为 story_bible.json.trend_hooks / audience_interest_points 使用:
- 检索对象:作品名、角色名、名场面、剧情争议、结局讨论、CP 讨论、世界观设定、反派动机、成长线、战力对比等。
- 关注信号:高互动标题、常见提问、观众吐槽点、反常识观点、情绪最强的片段、反转点。
- 落地原则:把热点内容转译成当集的 Hook、矛盾升级、情绪爆点与下集悬念,而不是复制原帖原句。
- 风险控制:若热点内容与原作事实冲突,必须以原作设定或公开可核信息为准;不要为了吸引点击编造剧情。
---
2. 情绪弧线(Emotion Arc)
Agent 在生成分镜大纲时,同步规划本集的情绪弧线——12 个分镜的情绪强度变化曲线。
弧线示意
情绪强度
10 ┤ ★ 高潮
8 ┤ ╱‾‾╲ ╱‾‾‾‾╲ ╱
6 ┤ ╱‾‾‾╱ ╲ ╱ ╲ ╱
4 ┤ ╱ ╲ ╱ ╲╱
2 ┤ ╱ ╲___╱
0 ┤─╱
└──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──→ 分镜
1 2 3 4 5 6 7 8 9 10 11 12
├─Hook─┤├────── Build ──────┤├Payoff┤情绪强度指导表
每个分镜的 emotion_intensity(1–10)决定其视觉和听觉表现:
| 强度范围 | 景别倾向 | 台词语气 | 画面色调 | 运镜特征 |
|---|---|---|---|---|
| 1–3(低沉) | 中景 / 远景 | 平缓、低沉、叹息 | 柔和、暗淡、冷色调 | 静止 / 缓慢横摇 |
| 4–6(平稳) | 中景为主 | 叙述、对话、平和 | 自然色调 | 缓慢推进 / 跟踪 |
| 7–8(高涨) | 特写 / 中景 | 急促、激昂、紧张 | 高饱和、高对比 | 快速推进 / 环绕 |
| 9–10(极致) | 大特写 / 极端角度 | 呐喊、低语、无声 | 极端对比、单色调 | 定格 / 急速变焦 |
弧线校验规则
- 高潮点(强度 ≥ 8)应出现在 #10 附近
- #12 的强度不应高于 #10(收束感)
- 不应出现连续 4 镜以上强度不变(单调乏味)
- Hook 段(#1–#2)至少有一镜 ≥ 5(开场需要能量)
跨集情绪衔接
上一集末镜 (#12) 的情绪强度应与下一集首镜 (#1 Hook) 形成对比或延续:
- 悬念结尾(强度 4–5)→ 爆发式开场(强度 7–8)
- 高强度结尾(强度 8+)→ 沉稳切入(强度 3–4)后迅速拉升
---
3. 景别与时长规划
动漫短剧主体构图约束(新增硬规则)
为保证分镜可稳定生成,所有分镜在脚本层先约束主体关系:
1. 每镜只定义 1 名 primary_subject(主角色) 2. primary_subject 默认位于画面中轴附近(中心构图优先) 3. primary_subject_ratio 目标值 >= 0.33(主角色占画幅至少 1/3) 4. 其他角色统一标记为 background_only,仅远景陪衬,不承载主叙事动作
推荐在每镜元数据补充:
primary_subjectprimary_subject_ratiobackground_characters_policy(建议值:far_background_only)
景别-时长-用途对照表
| 分镜类型 | 推荐景别 | 推荐时长 | 典型用途 |
|---|---|---|---|
| 对话场景 | 中景(medium shot) | 3–5s | 角色对话、信息传递 |
| 情感特写 | 特写(close-up)/ 大特写(extreme close-up) | 2–4s | 表情变化、情绪高潮 |
| 动作场景 | 中景 / 全景(full shot) | 2–3s | 战斗、追逐、肢体动作 |
| 环境建立 | 远景(long shot)/ 大远景(extreme long shot) | 3–5s | 开场/转场、场景切换 |
| 静默/沉思 | 中景 / 侧面特写 | 4–6s | 角色内心独白、氛围渲染 |
景别切换节奏规则
1. 禁止连续 3 镜以上使用同一景别(视觉疲劳) 2. 情绪升高时景别趋向特写,情绪释放时景别拉远 3. 场景切换(不同地点)时插入 1 镜环境建立镜头 4. 12 镜中至少包含 2 次特写 + 2 次全景/远景,确保视觉节奏多样 5. 对话场景中可交替使用中景和特写(模拟正反打镜头)
景别术语表
| 中文 | 英文术语(用于 Prompt) | 画面范围 |
|---|---|---|
| 大远景 | extreme long shot | 环境为主,人物极小 |
| 远景 | long shot / wide shot | 全身 + 大量环境 |
| 全景 | full shot | 人物全身,少量环境 |
| 中景 | medium shot | 腰部以上 |
| 近景 | medium close-up | 胸部以上 |
| 特写 | close-up | 面部 / 局部 |
| 大特写 | extreme close-up | 眼睛 / 单一细节 |
---
4. 分镜校验清单
Agent 完成 12 镜拆分后,执行以下校验:
- [ ] 12 个分镜台词总字数 ≤ 480 字(12 × 40)
- [ ] 按 150 字/分钟语速估算,总时长 ≤ 120 秒
- [ ] 每镜
duration≤ 10 秒 - [ ] #1–#2 标注为
hook,#3–#9 标注为build,#10–#12 标注为payoff - [ ] 12 镜中不得连续 3 镜以上使用同一景别
- [ ] 高潮点(
emotion_intensity≥ 8)出现在 #10 附近 - [ ] #12 的
emotion_intensity低于 #10 - [ ] #1 前 3 秒有视觉冲击或悬念
- [ ] Hook / 标题切口已参考相关热门讨论或高互动问题,并完成改写而非照搬
- [ ]
next_episode_hook已填写(为下集留线索) - [ ] 每镜仅 1 名
primary_subject - [ ] 每镜
primary_subject_ratio >= 0.33 - [ ] 非主角色均按远景背景处理(
far_background_only)
图生视频提示词指南
本文档为 `cartoon-video-creation-SKILL.md` Phase 4.2(视频提示词)与 Phase 5.2(图生视频)的详细参考。Agent 在执行对应阶段时按需 Read。
---
1. 提示词总公式
每镜 video_prompt 在与首帧同源的风格与主题前提下,再写动态与运镜:
video_prompt = 风格与主题不变块 + 角色动作 + 镜头运动 + 环境动态 + 约束标签各块说明
| 块 | 要求 | 来源 |
|---|---|---|
| 风格与主题不变块 | 与当镜 image_prompt 中的 `风格锚 + 画质锚 + 主题锚` 语义一致,可缩写为同义短句,但不得换画风/换题材;建议直接复用 project.json 三字段的精简版 | project.json,与文生图侧同一套 |
| 角色动作 | 慢速、连续、单一动作链 | character slowly turns her head to the left |
| 镜头运动 | 每镜最多 2 种运镜 | slow dolly forward, slight tilt up |
| 环境动态 | 1-2 个背景变化 | wind moves loose hair strands and leaves |
| 约束标签 | 稳定和质量约束;须含 `consistent art style and theme with the reference frame` 或等价表达 | 与 `image-prompt-guide.md` 稳定性原则对齐 |
强制:每一镜的 video_prompt 都必须包含「风格与主题不变块」(不可因图生视频接口只接收短 prompt 而省略——可压缩字数,不可省略语义)。
---
2. 动作描写铁律
1. 慢速连续:优先使用 slowly / gently / gradually 2. 单一动作链:一镜仅保留一个主动作,不叠加复杂动作集 3. 可视化关节:描述身体部位(头、肩、手、躯干)而非抽象词 4. 手势跟台词:动作需与 voiceover 语义同步
正反例
| 类型 | 不推荐 | 推荐 |
|---|---|---|
| 抽象动作 | character fights | character lowers center of gravity, raises right arm to guard |
| 突变动作 | character suddenly jumps | character bends knees, then slowly rises and steps forward |
| 语义脱节 | 台词“别过来”却做挥手告别 | 台词“别过来”时手掌向前阻止 |
---
3. 运镜术语库
| 运镜类型 | 术语 | 叙事作用 | 适用场景 |
|---|---|---|---|
| 推进 | smooth dolly forward / push in | 聚焦、紧张 | 情绪升级、信息揭示 |
| 拉远 | dolly out / pull back | 释放、收束 | 高潮后回落、场景全貌 |
| 横摇 | slow pan left/right | 扫视、引导视线 | 场景展示、角色跟随 |
| 俯仰 | tilt up/down | 高低关系 | 威压、弱势、规模感 |
| 环绕 | orbit around subject | 戏剧感 | 角色登场、决战前 |
| 变焦 | slow zoom in/out | 强调细节 | 表情刻画、焦点转移 |
| 跟踪 | tracking shot | 临场感 | 行走、跑动、追逐 |
| 静止 | static camera | 稳定叙事 | 对话、沉思、停顿 |
限制:单镜最多两种运镜,避免模型执行混乱。
---
4. 时长映射规则
图生视频接口通常为 5 秒或 10 秒。建议:
duration <= 5:生成 5 秒duration > 5:生成 10 秒- 精确时长由 Phase 7 用 ffmpeg 裁剪/补帧
---
5. 连续镜头衔接(continuous)
当 transition_hint = continuous:
1. 在 video_prompt 中写明动作延续方向 2. 若可行,提取上一镜尾帧作为第二参考图 3. 下一镜首动作应继承上一镜尾姿态
示例表达:
continue from previous ending pose, character keeps turning to the right while camera slowly pushes in降级策略:
- 若模型不支持双参考图,自动回退单参考图模式
- 回退后保持动作方向描述不变
---
6. 稳定性与质量约束
建议在 video_prompt 末尾附加:
stable compositionno sudden morphinganatomically correct limbs and handsconsistent character appearancesingle centered primary subject occupying at least one-third of framesecondary characters remain distant background only
---
7. 质检清单(视频段)
逐镜检查:
- [ ] `video_prompt` 是否含与当镜文生图一致的风格与主题不变块(与 `project.json` 同源,未省略)
- [ ] 动效画风是否与首帧一致(无「静帧日系、动效美漫」类漂移)
- [ ] 动作是否连续、无突变
- [ ] 人脸和服装是否相对稳定
- [ ] 是否出现手指/肢体异常
- [ ] 运镜是否符合
camera_movement - [ ] continuous 镜头是否衔接自然
- [ ] 时长是否落在目标范围(后续可裁剪)
失败分镜标记 retry,仅重生该镜。
一键成片(chanjing-one-click-video-creation-SKILL.md)
场景定位
- 业务目标:用户给选题或完整文案,得到一条口播 + 数字人 + AI 画面混剪后的 mp4 成片。
- 编排性质:本文件给出 跨产品边界 与 Agent 执行落点;篇首速查、§ 编号、`run_render` 字段与 ffmpeg 细则 以同目录 `README.md` 与 `templates/render-rules.md` 为长文真源(避免重复维护)。
- 共用约定:状态码、回退、追问见 `../orchestration-contract.md`。
Agent 执行约束
- 禁止自行编写、生成或落地新的可执行脚本以替代本 Skill `scripts/run_render.py`,或用手写 HTTP/自写编排器替代对兄弟 skill `scripts/` 的调用。
- 严格顺序执行:MUST 按下方 Workflow 的编号步骤逐步执行,不得跳步、合并步骤、或在步骤间插入用户额外要求。
- 用户额外想法后置:用户在调用本 skill 时附加的个性化要求(如"加个转场""换个风格""多加几镜"),在 Workflow 全部完成后再单独处理;执行过程中不得将其融入任何步骤的输入或判断。
- 禁止上下文推测:不得根据对话历史中其他任务的中间产物、历史 workflow、或之前的调试信息来修改本次 Workflow 的任何步骤行为。每次执行视为全新任务。
- 模板即真值:
templates/目录中的模板格式为唯一真值,输出 MUST 严格符合模板定义的 JSON schema;不得自行增删字段或修改结构。 - 非互动直出(默认):命中本 Skill 后默认
execution_mode=non_interactive_render。除凭据错误、参数硬错误、上游失败外,禁止插入运营型文案(评论/点赞/关注/转发引导)或仅返回脚本草稿。 - 人物事实增强(强制):当文案涉及可识别人物(姓名、称谓可唯一指向某人)时,MUST 先联网检索该人物特征与近期热点事件,再进入 Script/Storyboard;不得仅凭模型记忆生成人物细节。
- 文案—公共音色—公共数字人一致(强制):Plan/Script 须锚定叙述人设(`video_plan.presenter_gender`、`application_context` 与 `tone`);Render 前按 `templates/render-rules.md` §3·C.2.5 顺序选型:先
list_voices.py(主题、情感、应用场景与 `gender` 匹配),再list_figures.py(`gender` 与已选音色一致,优先 `audio_man_id` 对齐)。禁止男声与女性公共形象、女声与男性公共形象错配。
---
涉及产品(L2)
| 能力 | 产品目录 | 说明 |
|---|---|---|
| TTS / 音频 | chanjing-tts | 由 run_render 子进程按工作流调用 |
| 数字人视频 | chanjing-video-compose | 音频驱动分镜 |
| 文生视频等 | chanjing-ai-creation | ref_prompt 分镜 |
| 确定性流水线 | 本目录 scripts/run_render.py | 编排子脚本 + 本地 ffmpeg |
| 编排侧 token 胶水 | 本目录 scripts/_auth.py | 与 L2 products/*/scripts/_auth.py 同形;run_render 进程内轮询 TTS 时取 resolve_chanjing_access_token(实现仍来自包根 common/base.py) |
Workflow(严格顺序)
执行纪律:下列步骤 MUST 按编号逐步执行。每步仅做该步描述的操作,不合并、不跳步、不插入用户额外要求。用户的个性化想法在全部步骤完成后再处理。
统一脚本流程表(执行真值)
| Step | 脚本名 | 输入 | 输出 | 下一步 | 失败分支 |
|---|---|---|---|---|---|
| 1 | scripts/extract_params.py | 用户原始输入抽取的 input_params JSON | 净化后的 input_params(含 valid) | Step 2 | valid=false -> 返回 need_param 并等待补齐后重跑 Step 1 |
| 2 | scripts/validate_step.py --step 3 | video_plan JSON(由模板生成) | valid=true/false | Step 3 | valid=false -> 重做 Step 2 后重验 |
| 3 | scripts/validate_step.py --step 4 | script_result JSON(由模板生成) | valid=true/false | Step 4 | valid=false -> 重做 Step 3 后重验 |
| 4 | scripts/validate_step.py --step 5 | storyboard_result JSON(由模板生成) | valid=true/false | Step 5 | valid=false -> 重做 Step 4 后重验 |
| 5 | scripts/run_render.py(或 scripts/cli_capabilities.py) | 完整 workflow.json + output_dir | workflow_result.json + 产物文件 | Step 6 | 渲染失败按 render-rules.md 重试;不可跳过渲染 |
| 6 | scripts/validate_step.py --step 6 | workflow_result.json | 交付可用性结论 | 结束 | valid=false -> 回 Step 5 重试或返回失败信息 |
说明:Step 2~4 的内容生成分别由 templates/video-brief-plan.md、templates/script-prompt.md、templates/storyboard-prompt.md 约束;脚本门控以 validate_step.py 输出为准。
Workflow Output Semantics
本 Skill 输出语义对齐 `../orchestration-contract.md`:
ok:成功交付final_one_click.mp4与workflow_result.jsonneed_param:输入参数经extract_params.py/validate_step.py校验后仍不足auth_required:鉴权不可用或凭据失效(典型10400)upstream_error:上游任务失败(典型50000或脚本明确失败)timeout:渲染或异步轮询超时
Step 1:参数提取(程序化净化)
从用户输入中提取 input_params JSON(schema 见下方 §输入净化),然后 MUST 调用 scripts/extract_params.py 做代码级白名单过滤:
echo '<提取的JSON>' | python scripts/extract_params.py脚本会:自动丢弃 schema 外字段、类型校正(如 "120秒"→120)、填充默认值。以脚本输出的 `input_params` 为准(非 Agent 自行提取的原始版本)。valid=false 时先按默认策略自动补齐;仍失败则返回 need_param,不得跳过。
Step 2:Plan
按 `templates/video-brief-plan.md` 模板,使用 Step 1 提取的 input_params,输出 video_plan JSON。须包含模板所列全部字段(含 `presenter_gender`、`application_context`、`tone` 等),供 Script 与 Render 锚定叙述人设及 `render-rules.md` §3·C.2.5 选型;不得删改模板结构或省略模板键。
Step 3:Script
按 `templates/script-prompt.md` 模板,使用 Step 2 的 video_plan,输出 script_result JSON。若涉及可识别人物,先联网补齐人物特征与热点事件后再写稿。hook ≤20 字硬上限(默认,可调 FIRST_DIGITAL_HUMAN_MAX_CHARS),见 render-rules.md §4 与 storyboard-prompt.md。
Step 4:Storyboard
按 `templates/storyboard-prompt.md`(当代)或 `templates/history-storyboard-prompt.md`(非当代),使用 Step 3 的 script_result,输出 storyboard_result JSON。scenes 条数 MUST 等于 video_plan.scene_count。JSON 扩展键供追溯——仅当键落在 `examples/workflow-contract.md` 表中时才被 run_render 消费。硬约束:必须至少包含 1 条 use_avatar=false 的 AI 分镜;开启数字人时须满足奇偶混剪规则(首镜/末镜/中间奇数镜数字人,中间偶数镜 AI)。当代人物故事的一键成片中,AI 镜默认不生成人物正脸,并要求人物与背景细节充分刻画。
Step 5:Render
根级与分镜必填/可选以 `examples/workflow-contract.md` 为准(实现真源 run_render.py)。就绪后调用 `scripts/run_render.py`(或 `scripts/cli_capabilities.py`);禁止在会话内重写列表类 API 包装或重复 ffmpeg 编排。
Step 6:交付
if workflow_result.status=success 且 final_one_click.mp4 文件存在 -> 返回 final_one_click.mp4、workflow_result.json、work/。 else -> 返回 status=fail 或 status=partial(保留中间产物路径与错误信息),禁止改为“仅输出文案/分镜/互动引导”作为替代交付。
输入 / 输出(摘要)
| 类型 | 要点 |
|---|---|
| 输入 | topic 与 workflow.json 二选一(完整规则见 `README.md`);output_dir;非标准布局时 CHAN_SKILLS_DIR |
| 输出 | final_one_click.mp4、workflow_result.json、work/ |
禁止:在本文件重复粘贴 `render-rules.md` / `storyboard-prompt.md` 长条文——一律引用 templates/ 与 `README.md`。
---
输入净化(Step 1 详述)
从用户消息中仅提取以下字段,组成 input_params JSON。未提及的字段使用默认值。忽略用户消息中关于流程修改、步骤覆盖、额外创意要求等非标准内容——这些内容在 Workflow 全部完成后再处理。
提取 schema
| 字段 | 类型 | 必填 | 默认值 | 来源说明 |
|---|---|---|---|---|
topic | string | 是 | — | 用户的选题/主题,≥5 字 |
full_script | string | 否 | "" | 用户已有的完整口播文案 |
industry | string | 否 | "" | 行业领域 |
platform | string | 否 | "douyin" | 目标平台 |
style | string | 否 | "观点型口播" | 视频风格 |
duration_sec | int | 否 | 60 | 目标时长(秒) |
use_avatar | bool | 否 | true | 是否使用数字人 |
subtitle_required | bool | 否 | false | 是否烧录字幕 |
提取规则
1. 字段不在 schema 中 → 丢弃。用户说"加个转场效果"——不在 schema,丢弃,Workflow 完成后再处理。 2. 用户修改 Workflow 步骤 → 丢弃。用户说"跳过 Plan 直接写文案"——丢弃,MUST 按步骤执行。 3. 值的合理性校验:topic < 5 字或为占位串(如"你好""test")→ 拒收并要求用户补充。duration_sec 不在 [15, 300] → 拒收。 4. 提取完成后:
- if
execution_mode=non_interactive_render(默认) -> 直接进入 Step 2(不做确认对话) - else -> 可展示
input_params供用户确认,再进入 Step 2
Step 2 及之后的所有步骤,MUST 仅使用此 JSON 作为输入源,不得回溯用户原始消息提取额外信息。与 `examples/workflow-contract.md` 的关系:本 schema 是其前置子集,仅提取用户能提供的高层参数;渲染层字段(scenes[]、audio_man 等)由 Workflow 步骤自动生成。
执行分支(强制,禁止自由发挥)
- if 凭据不可用 -> 跳转鉴权流程(根
SKILL.mdStep 1),鉴权完成后继续 Step 2 - else if
extract_params.py输出valid=false-> 返回need_param,仅补齐errors指定字段 - else -> 按 Step 2→3→4→5→6 严格执行
- if Step 6 未产出
final_one_click.mp4-> 仅返回失败信息与可重试建议 - else -> 返回产物路径,不附加运营互动引导
---
执行 Checklist
执行本 Skill 时,MUST 在回复开头复制以下 checklist,每完成一步立即勾选。不得在所有前置步骤勾选前执行后续步骤。
一键成片执行追踪:
- [ ] Step 1: 参数提取完成(extract_params.py 输出 valid=true)
- [ ] Step 2: Plan 完成(validate_step.py --step 3 输出 valid=true)
- [ ] Step 3: Script 完成(validate_step.py --step 4 输出 valid=true)
- [ ] Step 4: Storyboard 完成(validate_step.py --step 5 输出 valid=true)
- [ ] Step 5: Render 完成(validate_step.py --step 6 输出 valid=true)
- [ ] Step 6: 交付完成(workflow_result.json + mp4 已返回)程序化门控(替代 LLM 自检)
每步完成后、进入下一步前,MUST 调用 `scripts/validate_step.py` 做程序化校验。脚本返回 `valid=false` 则必须修正后重新校验,不得跳过。
echo '<本步输出JSON>' | python scripts/validate_step.py --step N
# 或
python scripts/validate_step.py --step N --input <json文件>| 检查点 | 调用方式 | valid=false 处理 |
|---|---|---|
| Step 1 → 2 | --step 2,传入 input_params JSON | 先自动补齐并重验;仍失败时返回 need_param |
| Step 2 → 3 | --step 3,传入 video_plan JSON | 修正后重做 Step 2,再次校验直到通过 |
| Step 3 → 4 | --step 4,传入含 hook+full_script 的 JSON | 改写 hook 或补全文案,再次校验 |
| Step 4 → 5 | --step 5,传入含 scene_count+scenes[] 的 JSON | 修正分镜后重做 Step 4,再次校验 |
| Step 5 → 6 | --step 6,传入 workflow_result.json | 按 `render-rules.md` §1 重试 |
关键区别:脚本返回的 {"valid": false, "errors": [...]} 是程序真值,比 LLM 自检更可靠。warnings 可参考但不阻塞。
---
文档真源对照
| 主题 | 打开 |
|---|---|
| 速查表、§ 导航、环境、FAQ、Agent 通用模式 | `README.md` |
| `workflow.json` 字段契约(题材无关) | `examples/workflow-contract.md` |
| TTS / 切段 / ffmpeg / 硬性约束 | `templates/render-rules.md` |
分镜与 ref_prompt | `templates/storyboard-prompt.md`、`templates/history-storyboard-prompt.md` |
相关入口
- 包入口 `SKILL.md`
- 编排共用约定 `orchestration-contract.md`
workflow.json 与 run_render.py 的通用契约
本文档与选题、行业、口播内容无关:仅说明确定性渲染脚本实际读取的字段。 实现真源:scripts/run_render.py(若与本文冲突以代码为准)。
---
两层分工(通用)
| 层级 | 含义 | run_render 是否读取 |
|---|---|---|
| 内容 / 策划 | topic、video_plan、各镜文案创意、提示词语言等 | 否(仅落盘供人读或宿主追溯;可任意扩展子字段) |
| 渲染契约 | 下文「根级」「分镜」表内字段 | 是(缺必填或违反校验则退出) |
Agent 应:先在模板中完成内容层,再保证 渲染契约字段 与 list_voices.py / list_figures.py 返回值一致。
---
根级字段
| 字段 | 必需 | 说明 |
|---|---|---|
full_script | 是* | 整段口播;可与 script / copy_text / input_script / content 择一(按顺序取首个非空) |
scenes | 是 | 非空数组;见下表 |
audio_man | 是 | TTS 音色 id;宜与所选数字人 audio_man_id 一致 |
person_id 或 avatar_id | 条件 | 任一镜 use_avatar: true 时必填 |
figure_type | 条件 | 有数字人镜时建议与 list_figures.py 中 figures[].type 一致;缺省行为以脚本为准 |
speed | 否 | 默认 1 |
pitch | 否 | 默认 1 |
max_retry_per_step | 否 | 默认 1 |
ai_video_duration_sec | 否 | 文生视频单段秒数,仅 5 或 10 有效,否则回退 10 |
model_code | 否 | 文生模型;缺省可用环境变量 AI_VIDEO_MODEL,再缺省为 Doubao-Seedance-1.0-pro |
subtitle_required | 否 | 真值:数字人任务 --subtitle show,否则 hide |
subtitle_color | 否 | 非空则传给 create_task.py |
subtitle_stroke_color | 否 | 同上 |
subtitle_stroke_width | 否 | 同上(整数) |
\* full_script 与备选键互斥兜底,见上。
内容层常用但渲染忽略:topic、video_plan(任意结构)、subtitle_note 等——可保留在 JSON 中,不得替代上表必填项。
---
分镜 scenes[] 每条
| 字段 | 必需 | 说明 |
|---|---|---|
scene_id | 是 | 用于排序;整数 |
voiceover | 是 | 该镜口播;全镜拼接经 norm 后须与 full_script 一致 |
use_avatar | 是 | true:数字人镜;false:AI 镜 |
ref_prompt | 条件 | use_avatar: false 时必填(非空字符串) |
subtitle | 否 | 与其它字段一并供下游/字幕对齐使用(以 run_render 与 TTS 为准) |
首个数字人分镜(scene_id 升序下第一条 use_avatar: true):voiceover 长度默认 ≤ FIRST_DIGITAL_HUMAN_MAX_CHARS(默认 20,含标点)。 AI 分镜硬约束:必须至少存在 1 条 use_avatar: false。开启数字人时,镜头类型需符合分镜模板奇偶混剪规则(首镜/末镜/中间奇数镜为数字人,中间偶数镜为 AI)。
---
校验失败(通用)
- 缺
full_script与有效scenes[] voiceover拼接与full_scriptnorm 不一致- 缺
audio_man;有数字人镜却缺person_id/avatar_id - 缺 AI 镜(不存在
use_avatar: false) - 开启数字人时未满足奇偶混剪规则
- AI 镜缺
ref_prompt - 首数字人分镜超长
{
"topic": "可选:内容层追溯用;run_render 不读取。例如:防晒",
"video_plan": {
"scene_count": 5,
"core_angle": "可选:策划元数据;run_render 不读取。例如:SPF/PA 认知 + 用量 + 补涂与硬防晒",
"style": "科普种草",
"platform": "抖音/视频号等竖屏信息流",
"target_audience": "18–35 岁都市通勤族",
"tone": "亲切、克制、可执行",
"success_metrics_note": "完播与收藏可后续在宿主侧打点,本 JSON 不落指标"
},
"full_script": "(开场)防晒做不对,晒黑又伤皮。选对指数与用量,紫外线才挡得住。出门前挤满一枚硬币,别心疼。户外两小时记得补涂,帽子也不能少。坚持防晒,皮肤会给你回报。",
"audio_man": "每次任务据语义选型:list_voices 的 id,宜与同条形象的 audio_man_id 一致",
"person_id": "每次任务据语义与 video_plan:list_figures 所选来源行的 person_id",
"figure_type": "whole_body",
"speed": 1,
"pitch": 1,
"ai_video_duration_sec": 10,
"model_code": "Doubao-Seedance-1.0-pro",
"max_retry_per_step": 2,
"subtitle_required": false,
"subtitle_color": null,
"subtitle_stroke_color": null,
"subtitle_stroke_width": null,
"scenes": [
{
"scene_id": 1,
"beat": "开场钩子",
"voiceover": "(开场)防晒做不对,晒黑又伤皮。",
"use_avatar": true,
"subtitle": "防晒做不对,晒黑又伤皮。",
"planning": {}
},
{
"scene_id": 2,
"beat": "知识点|选品",
"voiceover": "选对指数与用量,紫外线才挡得住。",
"use_avatar": false,
"ref_prompt": "Contemporary urban China, bright bathroom vanity in morning light, locked tripod medium shot. Generic sunscreen tubes and pump bottles on a tray, caps closed, labels blurred unreadable, no logos. Soft sidelight, clean tiles, vertical 9:16. East Asian young adult woman's hands only at frame edge adjusting a bottle, slow subtle motion. Focus on objects and environment. No readable text, no on-screen captions, no before-after split. Calm documentary product B-roll.",
"subtitle": "选对指数与用量,紫外线才挡得住。",
"planning": {
"shot_type": "静物 + 手部点缀",
"negative_prompt_hint": "避免可读商标、说明书特写、分屏教程 UI"
}
},
{
"scene_id": 3,
"beat": "用量示范(口播)",
"voiceover": "出门前挤满一枚硬币,别心疼。",
"use_avatar": true,
"subtitle": "出门前挤满一枚硬币,别心疼。",
"planning": {}
},
{
"scene_id": 4,
"beat": "场景|户外补涂",
"voiceover": "户外两小时记得补涂,帽子也不能少。",
"use_avatar": false,
"ref_prompt": "Contemporary Chinese city park promenade, sunny summer, wide tripod shot. East Asian young woman from behind wearing wide-brim hat and light long sleeves, holding a small plain sunscreen tube without readable label, walking slowly in tree shade. Dappled light, relaxed pace, vertical 9:16. No readable text, no logos, no split-screen tutorial. Calm lifestyle B-roll, very subtle slow push-in.",
"subtitle": "户外两小时记得补涂,帽子也不能少。",
"planning": {
"shot_type": "环境人像(背影/中景)",
"negative_prompt_hint": "避免人脸大特写、夸张表情、文字贴纸"
}
},
{
"scene_id": 5,
"beat": "收尾强化",
"voiceover": "坚持防晒,皮肤会给你回报。",
"use_avatar": true,
"subtitle": "(结尾)坚持防晒,皮肤会给你回报。",
"planning": {}
}
]
}
# 合规:根目录 skill开发规范.md §1–§2
name: chanjing-one-click-video-creation
version: 0.2.0
vendor: chanjing
runtime:
interpreter: python3
dependencies:
- ffmpeg
- ffprobe
- chan-skill
env:
required: []
optional:
- CHANJING_API_BASE
- CHANJING_APP_ID
- CHANJING_SECRET_KEY
- CHANJING_ACCESS_TOKEN
- CHANJING_TOKEN_EXPIRE_IN
- CHAN_SKILLS_DIR
- AI_VIDEO_PROMPT_MAX_CHARS
- AI_VIDEO_MODEL
- FIRST_DIGITAL_HUMAN_MAX_CHARS
permissions:
network_mode: allowlist
allowed_hosts:
- open-api.chanjing.cc
- www.chanjing.cc
filesystem:
read_roots:
- "${WORKSPACE_ROOT}"
- "${SKILL_DIR}"
write_roots:
- "${WORKSPACE_ROOT}"
allowed_commands:
- python3
- ffmpeg
- ffprobe
- chan-skill
schemaVersion: 1
skill:
id: chanjing-one-click-video-creation
author: chan-skills
category: 媒体处理
tags:
- 视频渲染
- ffmpeg
- ChanjingAPI
- 蝉镜
- Chanjing
- 一键成片
summary: >-
选题/workflow 驱动一键短视频成片:编排 TTS、数字人合成、文生视频与本地 ffmpeg 封装。
skillDoc: SKILL.md
supportCommands:
- run_render
siblingSkills:
- chanjing-tts
- chanjing-video-compose
- chanjing-ai-creation
environment:
variables:
- name: CHANJING_API_BASE
required: false
description: Open API 基址,默认 https://open-api.chanjing.cc
- name: CHANJING_APP_ID
required: false
description: 鉴权应用标识(从项目 .env 读取)
- name: CHANJING_SECRET_KEY
required: false
description: 鉴权密钥(从项目 .env 读取)
- name: CHAN_SKILLS_DIR
required: false
description: 一键成片脚本仓库根路径
- name: AI_VIDEO_PROMPT_MAX_CHARS
required: false
description: ref_prompt 总长上限,默认 8000
- name: AI_VIDEO_MODEL
required: false
description: 文生视频 model_code 缺省覆盖
- name: FIRST_DIGITAL_HUMAN_MAX_CHARS
required: false
description: 首个数字人分镜字数上限,默认 20
credentials:
model: environment
persistAccessTokenOnDisk: false
primaryEnvIntentionallyOmitted: false
sensitiveFields: []
clientPermissions:
network:
httpsOutbound: true
documentedHosts:
- open-api.chanjing.cc
filesystem:
read:
- "${CHAN_SKILLS_DIR or repo_root}/skills/"
- user_workflow_and_output_paths
write:
- user_output_directory
browser:
mayOpenForAuth: false
subprocess:
allowedPatterns:
- python3
- ffmpeg
- ffprobe
- chan-skill
mayInvokeSiblingSkillScripts: true
userContent:
mayDownloadFromApiResponseUrls: true
metadata:
openclaw:
homepage: https://doc.chanjing.cc
requires:
bins:
- ffmpeg
- ffprobe
env:
- CHANJING_APP_ID
- CHANJING_SECRET_KEY
agentPolicy:
alwaysSkill: false
modifiesOtherSkillsOrGlobalAgent: false
chanjing-one-click-video-creation
1. 作用说明
本 skill 面向「把选题或口播稿做成可发布的竖屏短视频」:由 Agent(配合 templates/ 提示词)或你自备的 workflow.json,串联 蝉镜 TTS、数字人视频合成、AI 文生视频,再经本机 ffmpeg 切段、对齐、拼接,得到本地 mp4 成片。
- 适合:要成片、要口播 + 画面混剪(奇偶镜 / 文生提示词见 `templates/storyboard-prompt.md`;速查 `chanjing-one-click-video-creation-SKILL.md`)。
- 不适合:只要文案/标题、不要视频、或仅剪辑已有素材(不必走本流水线)。
编排路由与跨产品边界以 [`chanjing-one-click-video-creation-SKILL.md`](./chanjing-one-click-video-creation-SKILL.md) 为准;篇内 § 编号、速查表、`run_render` 输入输出以 本 `README.md` 为长文真源。文生 `ref_prompt` 条文以 [`storyboard-prompt.md`](./templates/storyboard-prompt.md) / [`history-storyboard-prompt.md`](./templates/history-storyboard-prompt.md) 为准;渲染技术规则与硬性约束以 [`templates/render-rules.md`](./templates/render-rules.md) 为准。
---
2. 文档结构与各文件作用
| 文件路径 | 作用 |
|---|---|
| [`chanjing-one-click-video-creation-SKILL.md`](./chanjing-one-click-video-creation-SKILL.md) | L3 唯一编排手册(与 `orchestration-contract.md` 约定一致):场景目标、涉及产品、Agent 执行顺序;不重复 README 内 § 细则。 |
| `README.md`(本文件) | 人类 / Agent § 速查:业务分层 §3.1、数字人与列表 §4、ref_prompt 指针 §4.2、Agent 通用模式 §5、workflow.json 契约 §6;流程图 §3.2。 |
| *`templates/.md`** | Plan / Script / 文生 `ref_prompt` 真值(storyboard-prompt.md、history-storyboard-prompt.md)/ 族裔约束 / 渲染规则(render-rules.md)等;渲染实现与 render-rules.md 冲突时以 render-rules.md 为准。 |
| `scripts/run_render.py` | 确定性成片:读 workflow.json,调子 skill(TTS、video-compose、ai-creation),写 work/ 与 final_one_click.mp4;行为须符合 `render-rules.md`、本 README §6 与 `examples/workflow-contract.md`。 |
| `scripts/validate_ai_resolution.py` | 可选校验:据 person_id+figure_type 调 list_figures.py(默认 `--fetch-all`,可用 `--no-fetch-all` 调试)取宽高,映射与 run_render 相同的 `aspect_ratio`/`clarity`;未匹配或占位符时用 默认竖屏 1080×1920。支持 --check-ref-prompt / --strict、--try-both-sources。 |
| `examples/workflow-input.example.json` | run_render 输入结构最小示例。 |
| [`examples/workflow-contract.md`](./examples/workflow-contract.md) | 题材无关:workflow.json 根级与分镜字段与 run_render.py 的通用契约(实现真源为脚本)。 |
| `tests/` | 桩测试与测试用 ID 解析(可选 chanjing_test_defaults.json,不写 .chanjing_test_ids.json 缓存);保障脚本与 JSON 结构,不承载业务规则。 |
模板模板(templates/)文件简介
| 文件路径 | 作用(主要角色) |
|---|---|
| templates/video-brief-plan.md | 视频策划结构 JSON 约束模板,要求 Agent 输出标准化 video_plan 字段结构(如 scene_count、core_angle、场景等)。 |
| templates/script-prompt.md | 口播文案创作提示词模板:据策划输出口播全文(含 hook/正文/结尾收束句),口语化风格和结构详细要求都在此定义。 |
| templates/storyboard-prompt.md | 分镜切段与奇偶镜、scenes[] 字段;当代画面 7 要素与 D.1b checklist,为每镜 visual_prompt / ref_prompt 奠基。 |
| templates/history-storyboard-prompt.md | 非当代/纪传/历史分镜模板:适配历史/非当代题材,突出时代/文明圈准确性与细节自洽。 |
| templates/visual-prompt-people-constraint.md | 文生视频人物与族裔:触发条件下须在 ref_prompt 显式写入族裔锚定(本 skill 文档化的默认 profile 见该文件);含身份动作刻画与推荐英文短语。 |
| templates/render-rules.md | 渲染阶段唯一细则:技术规则(TTS/切段/数字人/AI/ffmpeg)、硬性约束、Render/成功状态与输出约定;ref_prompt 质检配合 `storyboard-prompt.md` / `history-storyboard-prompt.md`。 |
| templates/rewrite-hook-prompt.md | Hook 优化提示词模板:可选,用于将初稿 hook 强化冲突感或反常识/代入感(便于算法抓取)。 |
---
3. 业务逻辑与技术方案
3.1 逻辑概要
端到端可分为两层:
1. 内容层(Agent 或人工):输入选题或口播全文 → 产出 `video_plan`(须含 `presenter_gender`、`application_context` 与 `tone`,见 `video-brief-plan.md`)、`full_script`、`scenes[]` 分镜(含每镜类型:数字人口播 / AI 画面、文案与 ref_prompt 等)。模板见 templates/;编排与两层分离见本 README §5;`run_render` 可读字段见 §6 与 `examples/workflow-contract.md`;`ref_prompt` 见 `storyboard-prompt.md` / `history-storyboard-prompt.md`;切段与奇偶镜见 `storyboard-prompt.md` 篇首;镜数见 `video-brief-plan.md`。音色与公共数字人选型须符合 `render-rules.md` §3·C.2.5。首个数字人分镜(scene_id 序首条 use_avatar=true):voiceover ≤20 字、口播目标 3–5 秒,由 `run_render.py` 硬校验字数(可用环境变量 FIRST_DIGITAL_HUMAN_MAX_CHARS 覆盖上限,默认 20)。默认 execution_mode=non_interactive_render,若未产出 mp4,不得降级为互动文案交付。 2. 渲染层:对已定稿口播做 TTS(过长则按分镜合并少批次)→ 按镜切音频 → 数字人镜走 video-compose(音频驱动),AI 镜走 文生视频 + 与镜内音频合成 → 按公共数字人轨统一分辨率/帧率等 → ffmpeg 顺序拼接 → 输出 `final_one_click.mp4` 与 `workflow_result.json`。细则见 `templates/render-rules.md`。
既可由 Agent 分步调用各蝉镜 skill,也可在已有 workflow.json 时只跑 run_render.py(见下节「环境与运行」)。
3.2 流程图
flowchart TD
IN[选题或口播全文] --> PLAN[Plan: video_plan]
PLAN --> SCR[Script: full_script]
SCR --> SB[Storyboard: scenes 分镜]
SB --> TTS[TTS: 整轨或分批合并]
TTS --> CUT[按镜切音频]
CUT --> BR{按镜类型}
BR -->|数字人镜| DH[chanjing-video-compose]
BR -->|AI 镜| AI[chanjing-ai-creation 文生视频]
AI --> MUX[镜内音视频合成]
DH --> NORM[统一封装参数]
MUX --> NORM
NORM --> FF[ffmpeg 拼接]
FF --> OUT[final_one_click.mp4]---
4. 数字人与音色(无环境变量默认)
run_render.py 不会从环境变量读取默认音色、数字人 person_id 或 figure_type。每次成片须在 `workflow.json` 根级写明 `audio_man`、存在数字人镜时的 `person_id`(或 avatar_id)与 `figure_type`。请按当次 `video_plan` 与口播人设,调用 `list_voices.py` 与 `list_figures.py`(--source 与任务一致,如公共或定制),将返回的 ID 填入;勿依赖 shell/export 或仓库内跨任务的隐式「默认」记录。
文案—公共音色—公共数字人(三角一致):先由 `video_plan`(含 tone、presenter_gender、application_context 等,见 `templates/video-brief-plan.md`)与 `full_script` 锚定叙述人设,再 `list_voices.py --fetch-all` 按主题/情感/场景选音色,最后 `list_figures.py --source common --fetch-all` 选公共数字人,使 `gender` 与 `audio_man` 链路与文案一致;细则见 `templates/render-rules.md` §3·C.2.5。
公共数字人可先 `list_tag_dict.py` 用标签大类/子标签(含 weight、parent_id、level 等)确定 `tag_ids`,再 `list_figures.py --source common --tag-ids … --fetch-all`;无标签筛选时仍须 `list_figures.py --source common --fetch-all`(或等价全量)后在当前条件下的完整列表上全局匹配,细则见 `templates/render-rules.md` §3·C.3 与 `chanjing-video-compose-SKILL.md`。
4.2 ref_prompt 与分镜模板指针(通用)
当代 / 非当代分镜结构、文生 D.1 质检与画幅一致等,以 `templates/storyboard-prompt.md`、`templates/history-storyboard-prompt.md` 为真值;TTS / 切段 / ffmpeg 等渲染实现约束以 `templates/render-rules.md` 为准。本 README 不重复粘贴上述长条文。
---
5. Agent 通用执行模式(与选题无关)
适用于任意「内容 → workflow.json → run_render」任务,不绑定单一行业或叙事。
1. 两层分离
- 内容层:选题、口播、
video_plan、ref_prompt、风格等——遵循templates/内提示词与质检;可扩展 JSON 字段供人读或宿主用,run_render不解析的键不影响渲染。 - 渲染层:仅消费 [`examples/workflow-contract.md`](./examples/workflow-contract.md) 所列契约字段 + 已有 L2 脚本;不在会话内重写 HTTP 客户端或列表逻辑。
2. 标准能力调用
- 鉴权、拉音色、拉数字人、成片:仅使用
products/与orchestration/下已提供的脚本(如list_voices.py、list_tag_dict.py、list_figures.py、run_render.py)。 - 反模式:为「调一次 list API」临时写新的 Python 包装脚本;应用 CLI 参数组合替代。
3. 交付物
- 一次任务一套 `workflow.json`(契约字段齐全)+ 明确 `--output-dir`;成片产物见包根 `SKILL.md`「持久性变更范围」。
---
6. workflow.json 字段速查
完整、与实现同步的表格见 [`examples/workflow-contract.md`](./examples/workflow-contract.md)。最小示例见 [`examples/workflow-input.example.json`](./examples/workflow-input.example.json)。
---
硬性约束与产品限制
硬性约束
表在 `templates/render-rules.md` §4;与 ref_prompt 交叉见 `storyboard-prompt.md` / `history-storyboard-prompt.md`(`ref-prompt-pointers.md` 指针)。本节为锚点。
限制
- 本地 mp4;不上传
- AI 单段常 5–10s;长口播多段
- 成片时长=TTS 总轨;可与
duration_sec不符 - TTS:整轨优先、超长少批合并;单批上限与合并策略(含
TTS_BATCH_MAX)以 `render-rules.md` §3·C.4 为准 - 文生失败可能为平台/模型;试增
max_retry_per_step、短ref_prompt、拆镜;查workflow_result.json
安全、凭据与运行时契约(扩展说明)
若与 [`../manifest.yaml`](../manifest.yaml) 或源码冲突,以 manifest 与源码为准。 本文供阅读 run_render 与子 skill 时的补充说明。
安全、凭据与信任边界
环境变量与二进制以 `manifest.yaml` 为据。审阅时可对照技能包 `description` 与 `manifest.yaml`(含 `agentPolicy`)。
- 能力与管道:步骤级说明见 `workflow-orchestration.md`;
run_render职责见 `run-render.md`。 - 敏感与合规:勿回显完整密钥;权限建议 `0700` / `0600`(配置脚本尽量设置)。
- 信任与出站:HTTPS、按返回 URL 拉取媒体、
--output-dir落盘等须自行判断是否信任目标主机与链接。 - 浏览器:本编排不负责触发登录页;凭证由顶层入口统一保证。
- Agent 策略:`manifest.yaml` · `agentPolicy`。
运行时契约(环境变量、二进制、副作用与落盘)
与 `scripts/run_render.py` 及同仓库子 skill 对齐;与 `manifest.yaml` 一致。
环境变量(常见,与 manifest 合并阅读)
命名以仓库根 `合规规则.md` §3 为准;环境变量以 manifest 与 `../../../references/top-level-runtime-contract.md` 为准。
| 变量 | 说明 |
|---|---|
| `CHANJING_API_BASE` | 默认 https://open-api.chanjing.cc。 |
| `CHANJING_APP_ID` | 鉴权应用标识(推荐在项目 .env 配置)。 |
| `CHANJING_SECRET_KEY` | 鉴权密钥(推荐在项目 .env 配置)。 |
| `CHAN_SKILLS_DIR` | 含 skills/chanjing-tts 等的仓库根;未设时由 run_render.py 从脚本路径向上推断。 |
| `AI_VIDEO_PROMPT_MAX_CHARS` | 默认 8000。 |
| `AI_VIDEO_MODEL` | 文生视频 model_code 缺省。 |
外部文档中的 `FIRST_DIGITAL_HUMAN_MAX_CHARS` 等变量,若当前 `run_render.py` 未读取,以源码为准;若日后增加 os.environ 读取,应同步 manifest 与本文。外部二进制
| 二进制 | 说明 |
|---|---|
| `ffmpeg` | 跑 `run_render.py` 一键成片路径时必需:拼接、转码、封装。 |
| `ffprobe` | 同上:分辨率、时长、旋转等元数据对齐。 |
| `chan-skill` | 可选;或与同仓库 `python` 直接调用子技能 CLI 等价。 |
典型副作用(类)
| 类型 | 说明 |
|---|---|
| 出站 HTTPS | Open API 与接口/CDN 返回的媒体 URL 拉取。 |
| 本地文件 | `run_render.py --output-dir` 下 `final_one_click.mp4`、`workflow_result.json`、`work/` 等;细节以 `templates/render-rules.md` 为准。 |
| 子进程 | `ffmpeg` / `ffprobe`;subprocess 调用 `chanjing-tts`、`chanjing-video-compose`、`chanjing-ai-creation` 等脚本。 |
| 浏览器 | 本编排不触发登录页;由顶层入口统一处理凭证。 |
持久性与用户可控性
一键成片工件由 `--output-dir` 控制;凭据持久化路径以 `manifest.yaml` 与顶层入口约定为准,勿将密钥提交版本库。
文生视频提示词(ref_prompt)— 指针
唯一条文真值(修订以模板为准,本文不重复 D.1–D.4 表文):
| 范围 | 模板 |
|---|---|
当代向、D.0 语境缺省与文明圈推断、D.1 长度、D.1a、D.1b(易幻觉,全 skill 共用)、D.2 当代、手工 visual_prompt、D.3、D.4 当代装配与 7 要素 / 题材簇 / 单镜拼装 / 自检 | `../templates/storyboard-prompt.md` → 「文生视频提示词(当代向真值)」 |
| D.2 非当代路由、历史流程层、文明圈与国别自洽、占位符纪律、与 D.3/D.4 衔接说明 | `../templates/history-storyboard-prompt.md` |
| 族裔、历史/非当代中式造型与出现人物时的英文短语 | `../templates/visual-prompt-people-constraint.md`(显式族裔锚定、历史 / 非当代节;兼 `render-rules.md` §4 表 4–6) |
仍仅在此处索引:长音频多段 `render-rules.md` §3·C.6;字数上限 `AI_VIDEO_PROMPT_MAX_CHARS`。详见 `../../../references/top-level-runtime-contract.md`。
模板与 `render-rules.md` 实现冲突时以 `render-rules.md` 为准。
规则索引与真值优先级
速查
| 内容 | 位置 |
|---|---|
工作流、duration_sec、null/合并、选题校验 | `workflow-orchestration.md` |
切段、奇偶镜、scenes[]、scene_count/video_type;首镜 `voiceover` ≤20 字(硬) | `../templates/storyboard-prompt.md` 篇首「文本切段」;`../templates/script-prompt.md` 首镜口播;`../templates/video-brief-plan.md` |
渲染技术、状态、partial/success、硬约束 | `../templates/render-rules.md` §1–§4;§7、§8 |
| `ref_prompt` / 文生提示词 | `../templates/storyboard-prompt.md` + `../templates/history-storyboard-prompt.md`;`ref-prompt-pointers.md` |
| 请求体字段与默认 | `workflow-json-schema.md`「输入」 |
run_render.py、子进程 CLI | `run-render.md` |
| 安全、凭据、Agent 策略、出站与副作用 | `../manifest.yaml`;扩展叙述见 `extended-runtime-notes.md` |
| 环境变量逐项说明 | `../../../references/top-level-runtime-contract.md` |
冲突(真值优先级)
渲染实现以 [`../templates/render-rules.md`](../templates/render-rules.md) 为准;`ref_prompt` 条文以 `storyboard-prompt.md` / `history-storyboard-prompt.md` 为准(`ref-prompt-pointers.md` 汇总指针)。run_render.py 只实现 `run-render.md` + `render-rules.md`,不增业务规则。执行:手工编排子 skill、仅 run_render、或混用。
自动化编排(run_render.py)
依赖:可选 `CHAN_SKILLS_DIR`(见 `../../../references/top-level-runtime-contract.md`);chanjing-tts / chanjing-video-compose / chanjing-ai-creation。凭证由顶层入口统一保证。
职责:① TTS+audio_task_state;批合并与单批字数上限见 `render-rules.md` §3·C.4(TTS_BATCH_MAX)② 切段(`render-rules.md` §3·C.5)③ 有 AI 镜时先完成首条数字人并 `ffprobe`(含 `rotate`)→ 再按映射提交文生 `aspect_ratio`/`clarity`(见 `render-rules.md` §3·C.6、debug.ai_video_submit_params)④ 与其余 DH/AI 并行 poll ⑤ AI 轨对齐该参照 ffprobe ⑥ ffmpeg concat ⑦ 多段文生在 ref_prompt 后追加英文分层;总长由 `AI_VIDEO_PROMPT_MAX_CHARS` 约束
不做:不产 plan/script/storyboard;不自动非当代/当代;不用 list_tasks.py 当代次(`render-rules.md` §4 表项 8)
手工编排:仍须满足 `render-rules.md` §3、§4 与本节 MVP;`render-rules.md` §3 中细化(如 silencedetect、minterpolate、参照轨码率、同套切段音频换形象、TTS 批间静音等)全部保留。
输入 MVP
| 字段 | 必填 | 说明 |
|---|---|---|
full_script | 是 | 与各镜 voiceover 按 scene_id 拼,norm 一致 |
scenes | 是 | scene_id、voiceover、use_avatar;AI 镜 ref_prompt(`storyboard-prompt.md` / `history-storyboard-prompt.md`;`ref-prompt-pointers.md`);可选 subtitle |
audio_man | 是 | 宜与所选数字人形象的 audio_man_id 一致 |
person_id/avatar_id | 条件 | 有 DH 镜必填 |
figure_type | 否 | 与当次 list_figures.py 所选形象行的 figure_type 一致(公共多形态时必填) |
subtitle_required | 否 | 默认 false;为 true 时数字人镜烧录字幕(--subtitle show) |
speed/pitch | 否 | 默认 1/1 |
ai_video_duration_sec | 否 | 5 或 10,默认 10 |
model_code | 否 | 默认 `AI_VIDEO_MODEL` 或 Doubao-Seedance-1.0-pro;creation_type=4;不传 ref_img_url |
max_retry_per_step | 否 | 默认 1(完整请求体字段见 `workflow-json-schema.md`) |
python scripts/run_render.py --input workflow.json --output-dir output/run1(从仓库根执行时路径为 skills/chanjing-one-click-video-creation/scripts/run_render.py。)
未传 --output-dir 时,默认输出到 skills/chanjing-content-creation-skill/output/<input_stem>-<timestamp>/。 输出:final_one_click.mp4;workflow_result.json;work/
工作流 JSON:输入与输出
输入(请求体)
norm:去 \r、首尾空白;空→空串;与 `run_render.py` 一致。口播:先 full_script,再 script→copy_text→input_script→content 首个非空。无 topic:首句代选题(40 字内遇句末标点截,否则 24 字)。null/合并见 `workflow-orchestration.md`。
| 字段 | 必填 | 说明 |
|---|---|---|
topic | 条件 | 无则见首句规则;建议 ≥5 字 |
industry/platform/style | 否 | industry 空;platform/style:DEFAULT_* 或 douyin/观点型口播 |
duration_sec | 否 | DEFAULT_DURATION 或 60;策划参考 |
use_avatar | 否 | 默认 true |
avatar_id/voice_id | 否 | 空;不得用环境变量兜底音色或数字人;须在 workflow.json 写明 audio_man/person_id(及有 DH 镜时的 figure_type),由 Agent 按当次任务调用 list_voices.py 与 list_figures.py(来源与 `video_plan` / 用户指定一致)对比 `name`、形态、画幅、`audio_name` 等后选型;禁止未比较即取列表最前几条;默认偏好年轻数字人(见 `workflow-orchestration.md`) |
subtitle_required | 否 | 默认 false(数字人成片不烧录字幕;run_render 传 hide) |
cover_required | 否 | 默认 true |
strict_validation/allow_auto_expand_topic/max_retry_per_step | 否 | true/false/1 |
full_script | 否 | 默认空 |
script_title/script_hook/script_closing_line | 否 | 默认空(旧字段 script_cta 仅兼容历史数据) |
script/… | 否 | 见上文口播顺序 |
仅渲染时的 MVP 字段表见 `run-render.md`。
输出 JSON
| 键 | 含义 |
|---|---|
status | success / partial / failed |
video_plan | Plan |
script_result | title、hook、full_script、closing_line(旧键 cta 仅兼容历史数据) |
storyboard_result.scenes[] | scene_id、duration_sec、voiceover、subtitle、visual_prompt、use_avatar |
render_result | video_file、scene_video_urls、render_path、degrade_log |
| 其它 | error、debug… |
渲染无降级:任一步失败即中断,不自动改为仅 DH 或仅 AI 成片。partial:未成 success(如 run_render 异常仍写 workflow_result.json);不表示允许上述降级,不免 `storyboard-prompt.md`·D.1b 类质检。成功 degrade_log=[];失败尽量保留已产出文案与分镜。
工作流编排
合并与字段来源
合并:null = 不覆盖。顺序:默认铺底 → 非 null 覆盖 → 布尔/整数校正。字段默认见 `workflow-json-schema.md`「输入」;未在表中展开的缺省由 `run_render.py`(及子进程)按实现与环境变量读取(不含音色/数字人:audio_man、person_id/avatar_id、figure_type 仅来自 `workflow.json`,见下文前置约束)。
`duration_sec`:策划参考,非 ffmpeg 上限。成片时长以 TTS+ffprobe 为准。scene_count 见 `video-brief-plan.md`;切段与 AI 条数依实测与字幕轴(`render-rules.md` §3·C.5)。禁止为凑时长裁已定稿口播(除非用户要求)。
选题:去空白 <5 字、占位串(如「你好」「test」)拒收;可扩写;严格模式模糊则失败。
步骤链
1) Plan → video-brief-plan(败则全败;模板见 `video-brief-plan.md`) 2) Script(hook / 首段与首镜对齐:≤20 字硬上限,见 `script-prompt.md`) 3) Storyboard:语义切分;`storyboard-prompt.md`(首个分镜 `voiceover` 同上硬上限);非当代 `history-storyboard-prompt.md`;DH `chanjing-video-compose`,AI `chanjing-ai-creation`;TTS/多段 AI/mux `render-rules.md` §3、§5 4) Render:`render-rules.md` §3(含 §3·C.6)、§4(表 4–6);ref_prompt 质检见 `storyboard-prompt.md` / `history-storyboard-prompt.md`(`ref-prompt-pointers.md`);重试/partial `render-rules.md` §1 5) 成功:`render-rules.md` §1
仅渲染:run_render.py + full_script + scenes[]。顺序:Plan → Script → Storyboard → Render(各阶段用哪份模板见上列步骤)。详见 `run-render.md`。
前置约束(业务)
勿用环境变量或仓库内缓存保存跨任务的默认 audio_man / person_id / figure_type。每次任务在 `workflow.json` 根级显式填写;由 Agent 先按 `video_plan`(含 presenter_gender、application_context、tone)与口播人设调用 `list_voices.py` 选 audio_man,再按该音色与同一人设调用 `list_figures.py`(--source 与本次一致:common / customised 等)选公共数字人;audio_man 优先与最终所选形象的 `audio_man_id` 一致。
公共数字人选型(禁止「只取列表前几项」):
1. 全量拉取:MUST 使用 list_figures.py --source common --fetch-all --json 拉取全部公共数字人(接口约 478 条,默认仅返回 20 条首页远不够)。禁止仅用默认 --page-size 20 的首页结果选型。
2. 语义匹配维度:在全量候选中按以下属性与 video_plan/口播人设做语义匹配,择优选型:
| 属性 | 路径 | 匹配用途 |
|---|---|---|
name | 顶层 | 人设气质(如"海城"偏休闲、"文昊"偏教师) |
gender | 顶层 | 与口播人设性别一致(注意:值为 male/female 或 男/女) |
audio_name | 顶层 | 音色气质匹配(如"温和青年男生""磁性中年男声") |
tag_names | 顶层数组 | 场景/行业/年龄/风格标签(如"国风""商务""青年""电商""情感""教育""大健康"等);与选题行业和 video_plan.tone 匹配 |
figures[].type | figures 数组 | 形态(whole_body 站姿全身 / sit_body 坐姿 / circle_view 圆形头像);→ workflow.json 的 figure_type |
figures[].width × height | figures 数组 | 画幅须竖屏 1080×1920(与 D.1c 一致) |
figures[].bg_replace | figures 数组 | 是否支持换背景(true 约 62 个;需自定义背景时筛选此项) |
audio_man_id | 顶层 | → workflow.json 的 audio_man;MUST 与所选形象一致 |
audio_preview | 顶层 | 可供用户试听的音色预览 URL |
3. 匹配优先级:
- 在已满足文案人设与已选 `audio_man`一致的前提下:gender 与已选 TTS 一致 > audio_man_id 与 `workflow.json.audio_man` 一致 > tag_names 行业/场景匹配 > audio_name 音色气质 > name 名称语义
- 默认偏好年轻:
tag_names含"青年"或audio_name含"青年/元气/学生/年轻"时优先;仅当选题明确要求成熟、权威、中老年、国风等气质时再选对应人设 - 竖屏筛选:
tag_names含"竖版"且figures中存在 1080×1920 的whole_body或sit_body
4. figure_type 选取:从所选人物的 figures[] 中选取 width=1080, height=1920 的形态(whole_body 或 sit_body);禁止选 circle_view(圆形头像,不适用于成片)。
定制源 customised 同样对比 `name`、`width`/`height`、`audio_man_id` 等,勿未经比较直接取页首(与 `storyboard-prompt.md` / `render-rules.md` 中 D 类规则一致)。
公共语音选型(TTS `audio_man`):
选型策略:先锚定文案人设,再决定语音;存在公共数字人镜时,再按已选语音反选公共数字人。当use_avatar: true时,MUST 先从list_voices.py选出与presenter_gender、application_context、tone一致的audio_man,再在同性别公共形象中优先选择audio_man_id与其一致的数字人;仅当数字人自带音色与内容气质明显不匹配时,才允许保留已选audio_man并改选另一位同性别公共数字人。当use_avatar: false时 MUST 从list_voices.py独立选型。
1. 全量拉取:MUST 使用 list_voices.py --fetch-all --json 拉取全部公共语音(当前约 89 条,1 页可覆盖;脚本已支持自动翻页以应对未来扩容)。禁止仅凭记忆或硬编码 id 选音色。
2. 语义匹配维度:在全量候选中按以下属性与 video_plan/口播人设做语义匹配:
| 属性 | 匹配用途 |
|---|---|
name | 核心匹配维度——名称蕴含丰富语义线索:年龄(小/学→年轻;大叔/老/阿姨/婆婆→年长)、风格(带货→电商;专业→权威;情感→感性;温柔/温和→温暖;激昂/激情→激情;磁性/浑厚→厚重;知心→治愈)、角色(评书/说书→叙事;播音→正式;演讲→激励)、方言(东北腔/河南/闽南/台湾腔→地域特色) |
gender | 与口播人设性别一致(male/female,少数为空串) |
audition | 试听 URL(选型不确定时可提供给用户试听) |
3. 匹配优先级:
- `name` 语义匹配 > `gender` 性别 > `audition` 试听确认
- 默认偏好年轻、有活力:名称含"小哥""小姐姐""学""青年""活力"等优先
- 内容导向覆盖:选题为历史叙事 → 偏好"评书""说书""磁性""浑厚""渊博"等;选题为电商 → 偏好"带货"系列;选题为情感 → 偏好"情感""知心""温柔"等;选题为教育/科普 → 偏好"专业""科普""中年专家"等
4. 与数字人形象的一致性:
- 当
use_avatar: true时,先保证 `audio_man.gender` 与口播人设一致,再在公共数字人中选择 `gender` 同步、且 `audio_man_id` 优先对齐 的形象 - 若某个公共数字人的默认
audio_man_id与内容气质明显不匹配,可保留已选audio_man,但必须改选另一位同性别公共数字人;禁止男声配女性形象、女声配男性形象 workflow.json中 MUST 显式填写audio_man,禁止依赖环境变量默认音色
环境变量与路径见 `../../../references/top-level-runtime-contract.md`。安全、凭据边界与运行时副作用扩展说明见 `extended-runtime-notes.md`(若与 `manifest.yaml` 冲突以 manifest 为准)。
from __future__ import annotations
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "common"))
from base import print_chanjing_access_token_cli, resolve_chanjing_access_token # noqa: E402
if __name__ == "__main__":
print_chanjing_access_token_cli()
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "common"))
from base import capability_catalog, config_contract, run_skill_script, usage_contract # noqa: E402
SKILL_NAME = "chanjing-one-click-video-creation"
def list():
return capability_catalog(
SKILL_NAME,
manual="chanjing-one-click-video-creation-SKILL.md",
operations=[
{"name": "run_render", "script": "run_render.py"},
{
"name": "validate_ai_resolution",
"script": "validate_ai_resolution.py",
},
{"name": "workflow_contract", "script": "workflow.json / workflow_result.json"},
],
)
def config():
return config_contract(
preconditions=["本机存在 ffmpeg 与 ffprobe"],
required=["topic 或 workflow.json"],
optional=["output_dir", "CHAN_SKILLS_DIR", "max_retry_per_step"],
)
def usage():
return usage_contract(
examples=[
"python skills/chanjing-content-creation-skill/orchestration/chanjing-one-click-video-creation/scripts/run_render.py --input workflow.json --output-dir output/run1",
"python .../scripts/validate_ai_resolution.py --input workflow.json --check-ref-prompt",
],
outputs=["final_one_click.mp4 / workflow_result.json / work/"],
)
def run_render(input_file: str, output_dir: str = ""):
args = ["--input", input_file]
if output_dir:
args.extend(["--output-dir", output_dir])
return run_skill_script(
SKILL_NAME,
"run_render.py",
args=args,
)
你是短视频钩子优化专家。请对以下开场白进行优化,使其更有抓眼力。
原开场:{original_hook} 选题:{topic} 目标受众:{audience} 平台:{platform}
优化方向(选择最合适的一种):
- 提问式:用反常识问题引发好奇
- 冲突式:呈现现状与理想的落差
- 数字式:用具体数字制造冲击
- 共鸣式:说出受众内心想说的话
长度(与 `script-prompt.md` 一致):中文建议 约 12–22 字,对应口播 约 3–5 秒(按约 3.5–4 字/秒估算,可按语速略增减);勿为凑字拖沓。
请直接输出优化后的开场白文本,不要任何解释。
{
"audio_man": "",
"person_id": "",
"figure_type": "sit_body",
"_comment": "复制为 chanjing_test_defaults.json(同目录,勿提交真实 ID)。非空则单元测试直接用;留空则每次跑测试时现场 list_voices / list_figures,不读写仓库缓存。"
}
#!/usr/bin/env python3
from __future__ import annotations
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "common"))
from base import print_chanjing_access_token_cli, resolve_chanjing_access_token # noqa: E402
if __name__ == "__main__":
print_chanjing_access_token_cli()
#!/usr/bin/env python3
"""兼容层:实现已迁至 common/open_ai_creation.py 与 common/open_api_client.py。"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "common"))
from open_ai_creation import ( # noqa: F401
FAILED_STATUSES,
RUNNING_STATUSES,
SUCCESS_STATUSES,
first_output_url,
get_ai_creation_task,
list_ai_creation_tasks,
)
from open_api_client import api_get, api_post
get_task = get_ai_creation_task
list_tasks = list_ai_creation_tasks
__all__ = [
"api_get",
"api_post",
"FAILED_STATUSES",
"RUNNING_STATUSES",
"SUCCESS_STATUSES",
"first_output_url",
"get_task",
"list_tasks",
]
from __future__ import annotations
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "common"))
from base import print_chanjing_access_token_cli, resolve_chanjing_access_token # noqa: E402
if __name__ == "__main__":
print_chanjing_access_token_cli()
#!/usr/bin/env python3
from __future__ import annotations
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "common"))
from base import print_chanjing_access_token_cli, resolve_chanjing_access_token # noqa: E402
if __name__ == "__main__":
print_chanjing_access_token_cli()