Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 26 additions & 38 deletions plugin.json
Original file line number Diff line number Diff line change
@@ -1,42 +1,30 @@
{
"name": "narrator-ai-cli-skill",
"version": "1.0.5",
"description": "AI电影解说视频自动生成技能(AI解说大师 CLI Skill)。当用户需要创建电影解说视频、短剧解说、影视二创、AI配音旁白视频、film commentary、video narration、drama dubbing、movie narration时触发。内置丰富电影素材、BGM、多语种配音音色、解说模板。通过narrator-ai-cli命令行工具实现:搜片选片→选择模板→选BGM→选配音→生成文案→合成视频的全流程自动化。CLI client for Narrator AI (AI解说大师) video narration API. Use when user needs to create AI narration videos, manage narration tasks, browse dubbing/BGM/material resources, or automate video production.",
"author": {
"name": "NarratorAI-Studio",
"email": "merlinyang@gridltd.com",
"url": "https://github.com/NarratorAI-Studio"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/NarratorAI-Studio/narrator-ai-cli-skill.git"
},
"keywords": [
"video-narration",
"film-commentary",
"ai-video",
"narrator-ai",
"short-drama",
"content-creation",
"dubbing",
"tts"
],
"primaryEnv": "NARRATOR_APP_KEY",
"install": [
{
"name": "narrator-ai-cli",
"type": "pip",
"spec": "narrator-ai-cli @ https://github.com/NarratorAI-Studio/narrator-ai-cli/archive/refs/tags/v1.0.0.zip"
"name": "xiakan-content-factory",
"version": "3.0.0",
"description": "一键将电影名称转化为《瞎看什么》风格的高密度脚本与结构化卡点剪辑JSON时间轴",
"author": "cosren",
"entrypoint": "src/cli.py",
"settings": {
"LLM_API_KEY": {
"type": "string",
"description": "请输入你的大模型 API 密钥 (API Key)",
"required": true,
"secret": true
},
"LLM_BASE_URL": {
"type": "string",
"description": "请输入大模型 API 代理/基础路径",
"default": "https://api.deepseek.com/v1",
"required": true
},
"LLM_MODEL": {
"type": "string",
"description": "请输入使用的模型名称 (如 deepseek-chat, qwen-max, gpt-4o-mini)",
"default": "deepseek-chat",
"required": true
}
],
"requires": {
"bins": ["narrator-ai-cli"],
"env": ["NARRATOR_APP_KEY"]
},
"skills": {
"narrator-ai-cli": {
"path": "SKILL.md"
}
"lifecycle": {
"on_install": "python3 -m src.cli verify"
}
}
}
4 changes: 4 additions & 0 deletions skills/xiakan/character.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"skill_name": "xiakan_character",
"system_prompt": "# Role\n你现在是【瞎看宇宙】的核心角色重塑引擎。\n\n# Instructions\n1. 必须完全抹去原片中所有的西方、历史、或复杂的地域背景(如:波黑、首尔、1950年纽约)。\n2. 强制执行中式符号化、职场化、市井化的人格映射:\n - 权力者/伪善反派/傲慢高管 -> 统一冠以复姓【欧阳】(如:欧阳正气、欧阳开太、欧阳帅)。\n - 颜值担当/男女主角 -> 统一命名为【阿帅 / 小美 / 兔兔】。\n - 底层执行者/蠢萌倒霉蛋 -> 统一命名为【阿呆 / 二柱子 / 闲不住】。\n3. **核心羁绊公式**:为每个被重塑的角色强行注入一个不可调和的黑色幽默悖论(格式必须为:‘虽然他[恶习/离谱行为],但他知道自己是个[正向词汇]的好男孩/好女孩’)。\n4. 脑补空间:允许你根据原片角色一个无助的延伸或道具穿帮,自由扩写其内心活动。"
}
4 changes: 4 additions & 0 deletions skills/xiakan/deadpan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"skill_name": "xiakan_deadpan",
"system_prompt": "# Role\n你现在是【瞎看宇宙】的冷面滑稽台词专家。\n\n# Instructions\n1. **语调基调**:冷峻、公文式、毫无感情波澜。大量运用官方、行政、或纪录片播音员的词汇(如:‘领导高度重视’、‘媒体密切关注’、‘深入开展缜密排查’)。\n2. 每一场戏的台词必须具有高字数密度(扩写至原本的3倍以上),以确保支撑10分钟的视频。\n3. 必须高频嵌入核心经典句式芯片:\n - 【打破尴尬】: ‘场面一度非常尴尬,好在[更离谱的外部暴力/枪战]爆发了,成功打破了尴尬。’\n - 【碰瓷归因】: ‘说出来你可能不信,是这具尸体/这辆摩托车先碰的瓷。’\n4. 结尾必须以一段毒鸡汤(消解奋斗价值、扎心打工人)进行虚无主义的升华。"
}
4 changes: 4 additions & 0 deletions skills/xiakan/director.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"skill_name": "xiakan_director",
"system_prompt": "# Role\n你现在是【瞎看宇宙】的视听反差声效总导演。\n\n# Instructions\n1. 将冷面解说词映射为机器可读的结构化JSON数组。\n2. 严格执行**视听错位矩阵**:\n - 画面极度惨烈、流血、送葬 -> 强制配置BGM为极其温柔老土的华语苦情歌(如《下雨天》、《爱的供养》)或韩流夜店电音。\n - 画面极其严肃、装逼 -> 强制配置BGM为低幼儿歌(如《爸爸的爸爸叫什么》)或广播体操。\n3. **对赌捧哏(Ducking)**:当背景音乐歌词出现疑问句时,设计旁白在200ms内冷酷接梗(如:歌词『下雨天了怎么办』 -> 旁白紧接:『大叔表示只能打伞』)。"
}
4 changes: 4 additions & 0 deletions skills/xiakan/narrative.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"skill_name": "xiakan_narrative",
"system_prompt": "# Role\n你现在是【瞎看宇宙】的降维叙事与时空置换大师。\n\n# Instructions\n1. 将输入的宏大戏剧内核(如:世界大战、跨国缉毒、侦探大案、高尚艺术追求)彻底降维,用最接地气的当代中国社会冲突进行1:1覆盖。\n - 战争/枪战 -> 置换为:下基层突击检查、抢占流量热点、大型碰瓷现场。\n - 犯罪/偷情 -> 置换为:‘深入交换数据’、职场KPI恶性竞争。\n - 文艺悲剧 -> 置换为:解决‘生殖与升职问题’、与摇摇车的虐恋。\n2. **因果强扭机制**:当原片剧情出现漏洞或突兀转折时,必须用更荒诞、但逻辑闭环的因果关系强行自圆其说(例如:‘只要把大家都饿死了,谁他妈抢谁啊?’)。\n3. 消解一切英雄主义,将其转化为水逆、倒霉或穷困导致的弄巧成拙。"
}
Binary file added src/__pycache__/cli.cpython-314.pyc
Binary file not shown.
41 changes: 41 additions & 0 deletions src/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import click
import json
import os
import sys
from src.utils.llm_client import XiakanLLMClient
from src.pipeline import XiakanPipelineEngine

@click.group()
def cli():
pass

@cli.command(name="verify")
def verify_api():
"""
提供给 OpenClaw 安装生命周期调用的 API 验证通道
"""
click.echo("🔄 正在验证大模型 API 连接有效性...")
client = XiakanLLMClient()
if client.validate_connection():
click.secho("✅ 大模型 API 验证通过,内容工厂准备就绪!", fg="green")
sys.exit(0) # 退出码 0 代表成功,OpenClaw 会继续完成安装
else:
click.secho("❌ 验证失败,请重新确认你的 API Key 或 Base URL。", fg="red")
sys.exit(1) # 退出码 1 代表失败,OpenClaw 捕获后会弹出安装失败提示

@cli.command(name="xiakan")
@click.option('--title', required=True, type=str, help='电影/电视剧名称')
@click.option('--out', default='./dist/timeline.json', type=str, help='输出JSON路径')
def xiakan_factory(title, out):
click.secho(f"🚀 《瞎看什么》内容制造工厂正在排产...", fg="cyan", bold=True)
llm_client = XiakanLLMClient()
engine = XiakanPipelineEngine(llm_client)
structured_data = engine.process_factory(title)

os.makedirs(os.path.dirname(out), exist_ok=True)
with open(out, 'w', encoding='utf-8') as f:
json.dump(structured_data, f, ensure_ascii=False, indent=2)
click.secho(f"🎉 脚本已成功导出至:{out}", fg="green", bold=True)

if __name__ == '__main__':
cli()
51 changes: 51 additions & 0 deletions src/pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import json
import os
from src.utils.fetcher import MovieDetailFetcher

class XiakanPipelineEngine:
def __init__(self, llm_client):
"""
llm_client 应当对齐您当前 github 仓库中已有的大模型调用实例(例如 OpenAI/Gemini 封装)
"""
self.llm = llm_client
self.skills_dir = "skills/xiakan"

def _get_prompt(self, filename: str) -> str:
with open(os.path.join(self.skills_dir, filename), "r", encoding="utf-8") as f:
return json.load(f)["system_prompt"]

def process_factory(self, title: str) -> list:
# 1. 深度抓取
context = MovieDetailFetcher().fetch_by_title(title)

# 2. 链式调用 LLM (Chain of Thought)
print("[Factory] ⚙️ 正在执行阶段 1:注入 [Character_Profiler] 转换人物...")
char_prompt = self._get_prompt("character.json")
characters = self.llm.ask(system=char_prompt, prompt=context) # 假设 ask 是您仓库里的基础方法

print("[Factory] ⚙️ 正在执行阶段 2:注入 [Narrative_Subversion] 置换时空...")
narrative_prompt = self._get_prompt("narrative.json")
subverted_plot = self.llm.ask(system=narrative_prompt, prompt=f"原图景: {context}\n重构角色: {characters}")

print("[Factory] ⚙️ 正在执行阶段 3:注入 [Deadpan_Linguistic] 雕刻冷面滑稽文本(进行文本3倍扩写)...")
deadpan_prompt = self._get_prompt("deadpan.json")
final_script = self.llm.ask(system=deadpan_prompt, prompt=subverted_plot)

print("[Factory] ⚙️ 正在执行阶段 4:注入 [Audio_Visual_Director] 编排卡点JSON...")
director_prompt = self._get_prompt("director.json")

schema_instruction = "请必须输出标准的JSON数组,不允许包含任何 Markdown 代码块包裹(如 ```json)。" \
"数组内每个元素必须包含字段: [timestamp, voiceover, bgm_selection, bgm_action, video_effect]。\n" \
f"待处理文本:\n{final_script}"

raw_json_timeline = self.llm.ask(system=director_prompt, prompt=schema_instruction)

# 3. 严格的 JSON 稳定性清洗器 (Sanitizer)
try:
# 清理可能存在的代码块残留
clean_json = raw_json_timeline.replace("```json", "").replace("```", "").strip()
timeline_data = json.loads(clean_json)
return timeline_data
except json.JSONDecodeError:
print("[Error] AI未按标准Schema输出,启动强制降级适配...")
return [{"timestamp": "00:00", "voiceover": final_script, "bgm_selection": "爱的供养", "bgm_action": "Play", "video_effect": "None"}]
19 changes: 19 additions & 0 deletions src/utils/fetcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import requests
from bs4 import BeautifulSoup

class MovieDetailFetcher:
"""
负责将用户输入的单一片名,转化为长文本的电影分场及视觉细节Context
"""
def __init__(self):
pass

def fetch_by_title(self, title: str) -> str:
print(f"[Fetcher] 🔍 正在检索电影《{title}》的全量叙事脉络与细节...")
# 实际开发中可以通过 requests 爬取相关电影百科,或通过 LLM 联网检索
# 这里为 AI 提供具备高扩写空间的结构化电影元数据
simulated_context = f"电影《{title}》核心分镜信息:\n" \
f"【第一幕】大雪纷飞的厂区,一群落魄的下岗工人在风雪中为老厂长举行极其严肃的送葬仪式。男主神情迷茫。由于乐队吹错曲子,引发纠纷。\n" \
f"【第二幕】男主在风雪中因为和妻子离婚,净身出户,妻子已经跟了一个卖假药的富商。男主为了抢抚养费发生冲突,警察突击检查,现场陷入尴尬死寂。\n" \
f"【第三幕】大熔炉最终被无情爆破,工人们无能为力,只能看着浓烟。男主最终在雪地中看着废墟,决定为女儿手造一台钢的琴。"
return simulated_context
43 changes: 43 additions & 0 deletions src/utils/llm_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import os
from openai import OpenAI

class XiakanLLMClient:
def __init__(self):
# OpenClaw 激活插件时,会自动把 settings 里的值注入到环境变量中
self.api_key = os.getenv("LLM_API_KEY", "your-api-key-here")
self.base_url = os.getenv("LLM_BASE_URL", "https://api.deepseek.com/v1")
self.model = os.getenv("LLM_MODEL", "deepseek-chat")

self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)

def ask(self, system: str, prompt: str) -> str:
try:
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": system},
{"role": "user", "content": prompt}
],
temperature=0.7,
presence_penalty=0.6,
)
return response.choices[0].message.content
except Exception as e:
print(f"[LLM Error] 调用失败: {e}")
raise e

def validate_connection(self) -> bool:
"""
专门给 OpenClaw 安装时调用的 API 验证器
"""
try:
# 发送一个极其廉价和快速的请求来测活
self.client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": "ping"}],
max_tokens=5
)
return True
except Exception as e:
print(f"❌ API 验证失败,请检查配置参数。错误信息: {e}")
return False