@clawhub-gitgily-1b48fb3f77
快导(KD) - 短视频脚本批量生成与管理。当用户需要批量生成多平台短视频脚本、管理文案库时使用。
---
name: kd
version: 1.1.0
description: "快导(KD) - 短视频脚本批量生成与管理。当用户需要批量生成多平台短视频脚本、管理文案库时使用。"
metadata:
requires:
bins: ["python"]
pypi_packages: ["openpyxl"]
skills:
- "web_search"
optional:
bins: ["lark-cli"]
skills:
- "lark-doc"
- "lark-wiki"
env:
required: [] # 没有必需的环境变量
optional:
- name: "LARK_CLI_TOKEN"
description: "飞书 CLI 认证 token(仅当使用飞书上传功能时需要)"
sensitive: true
files:
- "config/platforms.json"
- "config/user_config.json"
cliHelp: |
# 查看帮助
python kd.py --help
# 执行完整快导流程
python kd.py run --platform xiaohongshu
# 执行单步
python kd.py step --platform xiaohongshu --step 6
# 规则更新
python kd.py rules --platform xiaohongshu
# 活动管理(v1.1.0新增)
python kd.py activity --command "添加活动:春节特惠,关键词:春节、过年"
python kd.py activity --command "列出所有活动"
configPaths:
- "~/.agents/skills/kd/config/"
configPaths.optional:
- "~/.agents/skills/kd/references/platform_rules/"
---
# 快导 (KD)
快导(KD)是一个跨平台、跨行业的短视频脚本批量生成与管理工具。
## ⚠️ CRITICAL — 首次使用必读
**使用前必须完成以下配置:**
1. **安装依赖:** `pip install openpyxl`
2. **配置搜索关键词:** 编辑 `config/platforms.json`
3. **设置文案库路径:** 编辑 `config/user_config.json`
详见 [README.md](./README.md) 完整文档。
## 核心功能
| 功能 | 说明 |
|:---|:---|
| **快导系列** | 10步流程生成多平台短视频脚本 |
| **规则更新** | 生成/更新平台运营规则文档 |
## Shortcuts(快速使用参考)
| 用户说 | 我执行 | 说明 |
|:---|:---|:---|
| "快导小红书,生成5条" | 执行完整10步流程 | 自动执行搜索→生成→写入→报告全流程 |
| "快导第6步" | 执行单步生成脚本 | 仅执行Step 6,需前置步骤数据 |
| "配置关键词" | 引导配置 platforms.json | 设置平台搜索关键词池 |
| "配置文案库路径" | 引导配置 user_config.json | 设置Excel文案库保存位置 |
| "配置飞书空间" | 引导配置 report_space_id | 设置报告上传的飞书知识库 |
| "检查文案库格式" | 执行Step 5格式扫描 | 读取Excel实际格式参数 |
| "查看平台规则" | 读取 rules 文档 | 查看已生成的平台运营规则 |
| "更新小红书规则" | 执行规则更新系列 | 搜索最新规则并更新文档 |
| "测试快导" | 运行 test_workflow.py | 快速测试核心功能是否正常 |
## 执行模式
### 自动模式(默认)
```python
from scripts import WorkflowManager
workflow = WorkflowManager('xiaohongshu')
result = workflow.run_full()
```
### 交互模式(每步完成后暂停)
```python
from scripts import WorkflowManager
# 启用交互模式
workflow = WorkflowManager('xiaohongshu', interactive=True)
# 执行,每步完成后返回等待
result = workflow.run_full()
if result.get('paused'):
print(result['message']) # "Step X 完成,是否继续?"
# 用户确认后继续
result = workflow.resume()
```
### 回调模式(即时回复每步结果)
```python
def on_step_complete(step_num, result, should_pause):
print(f"Step {step_num} 完成") # 即时回复用户
workflow = WorkflowManager('xiaohongshu')
workflow.run_full(callback=on_step_complete)
```
## 核心模块
- `ConfigManager` - 配置管理
- `ScriptGenerator` - 脚本生成
- `ExcelManager` - Excel操作
**详细用法和示例见 [README.md](./README.md)**
## 版本
| 版本 | 日期 | 说明 |
|:---:|:---:|:---|
| 1.1.0 | 2026-04-27 | 新增:智能时长计算、活动管理、详细内容格式、BGM生成、Excel行高100 |
| 1.0.0 | 2026-04-24 | 初始版本,支持3平台 |
---
## v1.1.0 新功能详解
### 1. 智能时长计算
系统会自动计算并调整每个分镜的时间段:
- **台词时长**:字数 × 0.25秒
- **运镜时长**:固定2秒、推拉3秒、摇移4秒、复杂6秒、航拍8秒
- **建议时长**:max(台词, 运镜) + 1秒缓冲
- **自动调整**:使用建议时长更新B列时间段
**配置**(`config/platforms.json`):
```json
"duration_policy": {
"xiaohongshu": {
"auto_adjust": true, // 开启自动调整
"calculation_rules": {
"speech_rate": 0.25,
"motion_base": { "static": 2, "push_pull": 3, ... },
"buffer_seconds": 1
}
}
}
```
### 2. 活动管理
支持自然语言管理活动:
- 添加活动:`添加活动:春节特惠,关键词:春节、过年`
- 列出活动:`列出所有活动`
- 查询活动:`查询活动:春节`
- 删除活动:`删除活动:春节特惠`
系统会根据脚本主题自动匹配活动,填入L列(关联活动)。
### 3. 详细内容格式
分镜内容现在支持更详细的描述:
- **C列-镜头**:设备/焦段/内容
- **D列-运镜**:方式/轨迹/速度
- **E列-技巧**:光线/ISO/对焦/色调
- **F列-画面**:场景/构图/色彩/氛围
### 4. BGM详细生成
I列BGM现在要求详细格式:
```
音乐名:《稻香》周杰伦
风格:田园治愈、轻快温暖
使用时机:0-30秒前奏,30-60秒主歌,60-90秒副歌
```
### 5. Excel行高统一
数据行统一设置为100,标题行20.4:
```json
"row_height": {
"title_row": 20.4,
"data_row": 100
}
```
### 6. 外网搜索自动收集
`user_config.json` 新增 `external_search` 配置:
```json
{
"external_search": {
"auto_collect": true, // 空关键词时自动获取热门
"collect_count": 20,
"platforms": ["TikTok", "YouTube"],
"default_keywords": [] // 备用关键词(可选)
}
}
```
**三种使用场景:**
1. `external_keywords` 有值 → 使用用户配置的关键词
2. `external_keywords` 为空,`default_keywords` 有值 → 使用默认关键词
3. 两者都为空 + `auto_collect: true` → 调用 web_search 自动获取当前热门
**注意:** 场景3如果搜索失败会直接报错,不使用备用数据。
---
## 联系我们
如有问题或建议,欢迎交流:
- **微信号:** huitouyoujianta
- **用途:** 快导(KD)技能使用咨询、功能建议、技术交流
欢迎加微信交流学习!
FILE:config/feishu_permissions.json
{
"scopes": {
"tenant": [
"aily:file:read",
"aily:file:write",
"application:application.app_message_stats.overview:readonly",
"application:application:self_manage",
"application:bot.menu:write",
"cardkit:card:read",
"cardkit:card:write",
"contact:user.employee_id:readonly",
"corehr:file:download",
"event:ip_list",
"im:chat.access_event.bot_p2p_chat:read",
"im:chat.members:bot_access",
"im:message",
"im:message.group_at_msg:readonly",
"im:message.p2p_msg:readonly",
"im:message:readonly",
"im:message:send_as_bot",
"im:resource",
"drive:file:read",
"drive:file:write",
"wiki:space:read",
"wiki:space:write",
"docx:document:read",
"docx:document:write",
"sheets:spreadsheet:read",
"sheets:spreadsheet:write"
],
"user": [
"aily:file:read",
"aily:file:write",
"im:chat.access_event.bot_p2p_chat:read",
"drive:file:read",
"drive:file:write",
"wiki:space:read",
"docx:document:read",
"docx:document:write",
"sheets:spreadsheet:read",
"sheets:spreadsheet:write"
]
},
"explanation": {
"tenant": {
"description": "应用身份权限(tenant_access_token)",
"required_for": [
"读取/写入云文档",
"读取/写入知识库",
"发送消息",
"上传文件"
]
},
"user": {
"description": "用户身份权限(user_access_token)",
"required_for": [
"以用户身份操作文档",
"访问用户个人资源"
]
}
}
}
FILE:config/platforms.json
{
"metadata": {
"version": "1.1.0",
"description": "快导(KD) Skill - 平台参数配置",
"last_updated": "2026-04-27"
},
"platforms": {
"xiaohongshu": {
"name": "小红书",
"name_en": "Xiaohongshu",
"user_profile": "女性、种草决策",
"content_style": "攻略型、图文结合、实用价值",
"theme_direction": [],
"theme_direction_note": "用户首次使用时需配置主题方向",
"title_rules": {
"max_length": 20,
"style": "攻略感强",
"emoji": true
},
"duration": {
"total": "2-3min",
"total_seconds": { "min": 120, "max": 180 },
"segment": "6-12s",
"segment_seconds": { "min": 6, "max": 12 },
"segments_count": 18
},
"content_requirements": {
"columns": {
"A": { "name": "视频文案", "min_chars": 0, "story_required": true },
"B": { "name": "时间段", "format": "精确到秒" },
"C": { "name": "镜头", "min_chars": 20 },
"D": { "name": "运镜", "min_chars": 15 },
"E": { "name": "拍摄技巧", "min_chars": 15 },
"F": { "name": "画面", "min_chars": 20 },
"G": { "name": "台词", "min_chars": 30 },
"H": { "name": "音效", "min_chars": 15 },
"I": { "name": "推荐音乐/BGM" },
"J": { "name": "文案标签" },
"K": { "name": "使用状态", "default": "待使用" },
"L": { "name": "关联活动" },
"M": { "name": "发布日期" },
"N": { "name": "备注" }
}
},
"keywords": [],
"keywords_note": "用户首次使用时需配置搜索关键词"
},
"douyin": {
"name": "抖音",
"name_en": "Douyin",
"user_profile": "年轻人、公域流量",
"content_style": "快节奏、黄金3秒、视觉冲击",
"theme_direction": [],
"theme_direction_note": "用户首次使用时需配置主题方向",
"title_rules": {
"max_length": 50,
"style": "吸睛、话题性强",
"emoji": true
},
"duration": {
"total": "1min",
"total_seconds": { "min": 45, "max": 60 },
"segment": "3-5s",
"segment_seconds": { "min": 3, "max": 5 },
"segments_count": 12
},
"content_requirements": {
"columns": {
"A": { "name": "视频文案", "story_required": true },
"B": { "name": "时间段", "min_chars": 0 },
"C": { "name": "镜头", "min_chars": 15 },
"D": { "name": "运镜", "min_chars": 10 },
"E": { "name": "拍摄技巧", "min_chars": 10 },
"F": { "name": "画面", "min_chars": 15 },
"G": { "name": "台词", "min_chars": 20 },
"H": { "name": "音效", "min_chars": 10 },
"I": { "name": "推荐音乐/BGM" },
"J": { "name": "文案标签" },
"K": { "name": "使用状态", "default": "待使用" },
"L": { "name": "关联活动" },
"M": { "name": "发布日期" },
"N": { "name": "备注" }
}
},
"keywords": [],
"keywords_note": "用户首次使用时需配置搜索关键词"
},
"shipinhao": {
"name": "视频号",
"name_en": "Channels",
"user_profile": "中老年、私域社交",
"content_style": "温情叙事、真实人设、完播率",
"theme_direction": [],
"theme_direction_note": "用户首次使用时需配置主题方向",
"title_rules": {
"max_length": 16,
"no_punctuation": true,
"style": "温情、真实"
},
"duration": {
"total": "1-3min",
"total_seconds": { "min": 60, "max": 180 },
"segment": "5-10s",
"segment_seconds": { "min": 5, "max": 10 },
"segments_count": 15
},
"content_requirements": {
"columns": {
"A": { "name": "视频文案", "story_required": true },
"B": { "name": "时间段" },
"C": { "name": "镜头", "min_chars": 18 },
"D": { "name": "运镜", "min_chars": 12 },
"E": { "name": "拍摄技巧", "min_chars": 12 },
"F": { "name": "画面", "min_chars": 18 },
"G": { "name": "台词", "min_chars": 25 },
"H": { "name": "音效", "min_chars": 12 },
"I": { "name": "推荐音乐/BGM" },
"J": { "name": "文案标签" },
"K": { "name": "使用状态", "default": "待使用" },
"L": { "name": "关联活动" },
"M": { "name": "发布日期" },
"N": { "name": "备注" }
}
},
"keywords": [],
"keywords_note": "用户首次使用时需配置搜索关键词"
}
},
"industry_templates": {
"note": "用户可在此添加自定义行业模板"
},
"global_settings": {
"copy_library_path": "",
"copy_library_path_note": "用户首次使用时需配置文案库路径",
"file_naming": "{platform}文案库.xlsx",
"report_space_id": "",
"report_space_id_note": "用户首次使用时需配置飞书空间ID",
"rules_path": "",
"rules_path_note": "用户首次使用时需配置平台规则文档保存路径(默认:references/platform_rules/)",
"max_scripts_per_task": 5,
"skip_conditions": {
"no_trending": "如无爆款可选,则取消本次任务执行",
"duplicate_theme": "避开已有脚本主题"
},
"row_height": {
"title_row": 20.4,
"data_row": 100
},
"row_height_note": "Excel行高设置:标题行20.4,数据行100(符合嘉泰苑标准)"
},
"activities": {
"list": [
{
"id": "example",
"name": "日常推广",
"keywords": [],
"applicable_platforms": ["xiaohongshu", "douyin", "shipinhao", "pyq"],
"note": "示例活动,请删除后添加自己的活动"
}
],
"default": "日常推广,当前未设置activities",
"user_configured": false,
"tutorial_note": "请参考SKILL.md中的'如何配置活动'章节"
},
"bgm_library": {
"categories": {
"food_display": {
"name": "美食展示",
"tracks": [
{"name": "《小美满》周深", "style": "治愈轻快", "suitable": "全程"},
{"name": "《风吹一夏》", "style": "轻快活泼", "suitable": "卡点"},
{"name": "《世间美好与你环环相扣》", "style": "温暖治愈", "suitable": "全程"}
]
},
"emotional_narrative": {
"name": "情感叙事",
"tracks": [
{"name": "《父亲写的散文诗》许飞", "style": "温情叙事", "suitable": "高潮"},
{"name": "《成都》赵雷", "style": "民谣怀旧", "suitable": "全程"},
{"name": "《光阴的故事》罗大佑", "style": "经典怀旧", "suitable": "回忆段落"}
]
},
"weekend_leisure": {
"name": "周末休闲",
"tracks": [
{"name": "《想去海边》夏日入侵企画", "style": "轻快青春", "suitable": "开头+全程"},
{"name": "《稻香》周杰伦", "style": "田园治愈", "suitable": "全程"},
{"name": "《平凡之路》朴树", "style": "励志治愈", "suitable": "转场+高潮"}
]
},
"nostalgic": {
"name": "怀旧回忆",
"tracks": [
{"name": "《童年》罗大佑", "style": "经典怀旧", "suitable": "回忆片段"},
{"name": "《外婆的澎湖湾》", "style": "温情经典", "suitable": "家庭场景"},
{"name": "《故乡的云》费翔", "style": "思乡情怀", "suitable": "返乡场景"}
]
}
},
"update_note": "每月更新热门抖音BGM,保持时效性"
},
"duration_policy": {
"xiaohongshu": {
"target_duration": { "min": 120, "max": 180, "default": 150 },
"segment_duration": { "min": 6, "max": 15 },
"auto_calculate": true,
"auto_adjust": true,
"adjust_mode": "recommended",
"calculation_rules": {
"speech_rate": 0.25,
"motion_base": {
"static": 2,
"push_pull": 3,
"pan_tilt": 4,
"complex": 6,
"drone": 8
},
"buffer_seconds": 1,
"min_motion_time": 2
}
},
"douyin": {
"target_duration": { "min": 45, "max": 60, "default": 55 },
"segment_duration": { "min": 3, "max": 8 },
"auto_calculate": true,
"auto_adjust": true,
"adjust_mode": "recommended",
"calculation_rules": {
"speech_rate": 0.25,
"motion_base": {
"static": 2,
"push_pull": 2,
"pan_tilt": 3,
"complex": 5,
"drone": 6
},
"buffer_seconds": 1,
"min_motion_time": 2
}
},
"shipinhao": {
"target_duration": { "min": 60, "max": 180, "default": 120 },
"segment_duration": { "min": 5, "max": 12 },
"auto_calculate": true,
"auto_adjust": true,
"adjust_mode": "recommended",
"calculation_rules": {
"speech_rate": 0.25,
"motion_base": {
"static": 2,
"push_pull": 3,
"pan_tilt": 4,
"complex": 6,
"drone": 8
},
"buffer_seconds": 1,
"min_motion_time": 2
}
}
}
}
FILE:config/templates.json
{
"metadata": {
"name": "快导(KD) - 脚本生成模板",
"version": "1.0.0",
"description": "提示词模板配置,由用户自定义"
},
"prompt_templates": {
"subtask6_generate": {
"name": "生成脚本",
"template": "【角色设定】(Role)\n你是一位拥有5年经验的资深短视频编导\n\n【任务目标】(Task)\n请为我撰写一份关于[主题]的短视频完整脚本\n\n【输入来源】\n- 主题:[TRENDING_TITLES]\n- 平台规则:[PLATFORM_RULES]\n- 平台配置:[PLATFORM_CONFIG]\n- 分镜数量:[SEGMENTS_COUNT](根据总时长自动计算)\n- 视频时长:[TOTAL_DURATION]\n- 分镜时长范围:[SEGMENT_DURATION_RANGE]\n- 需避开方向:[AVOID_THEMES]\n\n【核心要素】(Core Elements)\n- 目标平台:[PLATFORM_NAME]\n- 分镜时长范围:[SEGMENT_DURATION_RANGE]\n- 目标受众:[TARGET_AUDIENCE]\n- 风格语气:[CONTENT_STYLE]\n\n【脚本要求】(Requirements)\n- 数量:[SCRIPT_COUNT]条\n- 分镜数量:[SEGMENTS_COUNT]个/条(自动计算)\n- 分镜时长:每个[SEGMENT_DURATION_RANGE],遵循'开头快、中间稳、结尾慢'\n- 总时长:[TOTAL_DURATION]\n- 关系:[SCRIPT_COUNT]条脚本必须差异化\n\n【分镜时长分配模式】\n- 开场分镜:3-5秒(快速吸引)\n- 主体分镜:6-10秒(内容展开)\n- 结尾分镜:10-12秒(升华收尾)\n\n【详细要求】\n1. A列-视频文案:必须包含完整故事叙述(起承转合),不只是标题+标签\n2. B列-时间段:精确到秒,格式如'0-5秒',每个分镜按分配模式设置\n3. C列-镜头:详细描述景别、角度、主体\n4. D列-运镜:详细说明运镜方式、速度、轨迹\n5. E列-拍摄技巧:详细说明设备、参数、手法\n6. F列-画面:详细描述光影效果、构图方式、色调风格\n7. G列-台词:包含完整台词+语气/停顿说明\n8. H列-音效:详细说明音效类型、音量、节奏\n9. I列-BGM:推荐音乐名称、风格、卡点位置\n10. J列-标签:#话题标签,附选择理由\n11. K列-状态:待使用\n12. L列-活动:关联活动(如有)\n13. M列-日期:计划发布日期\n14. N列-备注:拍摄注意事项\n\n【故事结构要求】\n- 黄金3秒钩子\n- 痛点引入\n- 核心内容\n- 互动引导\n\n【质量检查】\n- [ ] 每个分镜时长是否符合分配模式\n- [ ] 总时长是否控制在[TOTAL_DURATION]\n- [ ] 台词是否口语化(非书面化)\n- [ ] B-C列时间段与镜头是否设计合理\n- [ ] A列是否包含完整故事叙述\n- [ ] C-H列内容是否详细\n\n【输出格式】\n按JSON格式输出[SCRIPT_COUNT]条脚本,每条包含完整的[SEGMENTS_COUNT]个分镜。",
"template_note": "用户可通过编辑prompts/step6_generate_scripts.md自定义提示词模板",
"variables": [
"TRENDING_TITLES",
"EXTERNAL_TITLE",
"PLATFORM_RULES",
"PLATFORM_CONFIG",
"SEGMENTS_COUNT",
"TOTAL_DURATION",
"SEGMENT_DURATION_RANGE",
"AVOID_THEMES",
"SCRIPT_COUNT",
"PLATFORM_NAME",
"TARGET_AUDIENCE",
"CONTENT_STYLE"
]
},
"subtask7_validate": {
"name": "合理性检查",
"template": "【任务】对生成的[SCRIPT_COUNT]条脚本进行批量检查\n\n【检查维度】\n1. 时间合理性:时间段分配是否合理,总时长是否符合[TOTAL_DURATION]\n2. 内容合理性:内容是否连贯,逻辑是否通顺\n3. 场景合理性:场景是否真实可拍\n4. 台词合理性:台词是否口语化,是否符合人设\n5. 分镜时长限制:每个分镜是否在[SEGMENT_DURATION_RANGE]范围内,是否遵循分配模式\n6. 技术描述合理性:运镜、技巧、画面描述是否足够详细\n7. 标题合理性:标题是否符合平台限制\n8. 格式合理性:14列内容是否完整\n9. 故事叙述:A列是否包含完整的起承转合\n\n【处理方式】\n发现不合理项 → 直接修改脚本 → 记录修改内容\n\n【输出格式】\n表格形式输出检查结果和修改记录。",
"template_note": "用户可通过编辑prompts/step7_validate_scripts.md自定义检查标准"
}
},
"script_templates": {
"structure": {
"note": "脚本结构模板,由用户通过编辑prompts/step6_generate_scripts.md自定义",
"columns": ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N"],
"columns_description": "用户可在step6中自定义每列的详细要求",
"example": {
"A": "视频文案(标题+故事+标签)",
"B": "时间段(0-5秒)",
"C": "镜头描述",
"D": "运镜描述",
"E": "拍摄技巧",
"F": "画面描述",
"G": "台词",
"H": "音效",
"I": "BGM",
"J": "标签",
"K": "状态",
"L": "活动",
"M": "日期",
"N": "备注"
}
}
},
"duration_distribution": {
"note": "分镜时长分配模式由用户在step6中自定义,默认遵循'开头快、中间稳、结尾慢'原则",
"calculation_method": "总时长 ÷ 平均分镜时长 = 分镜数量,然后按分配模式分配",
"example": {
"description": "例如:2分钟视频,16个分镜",
"pattern": [3, 5, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 10, 10, 12],
"note": "以上为示例,实际由用户自行配置"
}
},
"user_customization": {
"说明": "本文件所有模板内容均可由用户自定义",
"自定义方式": [
"1. 直接编辑本文件",
"2. 编辑prompts/目录下的对应.md文件",
"3. 使用kd config命令设置"
],
"首次使用": "首次使用时需要通过以上方式配置模板内容"
}
}
FILE:config/user_config.json
{
"_comment": "快导(KD) 用户配置文件 - 首次使用时请填写以下配置项",
"_comment2": "或运行: kd setup 进行交互式配置",
"copy_libraries": {
"xiaohongshu": "",
"douyin": "",
"shipinhao": "",
"pyq": ""
},
"copy_libraries_note": "各平台文案库Excel文件路径,示例: {\"xiaohongshu\": \"F:\\\\vlog\\\\JT\\\\idea\\\\小红书文案库.xlsx\"}",
"report_space_id": "",
"report_space_id_note": "飞书知识库空间ID,用于保存执行报告",
"rules_files": {
"xiaohongshu": "references/platform_rules/xiaohongshu_rules.md",
"douyin": "references/platform_rules/douyin_rules.md",
"shipinhao": "references/platform_rules/shipinhao_rules.md",
"pyq": "references/platform_rules/pyq_rules.md"
},
"rules_files_note": "各平台规则文档路径,可按平台自定义",
"external_keywords": [],
"external_keywords_note": "外网搜索关键词池(可选),示例: [\"food\", \"cooking\", \"life\", \"vlog\"]",
"external_search": {
"auto_collect": true,
"collect_count": 20,
"platforms": ["TikTok", "YouTube"],
"default_keywords": []
},
"external_search_note": "外网搜索配置:auto_collect=true时空关键词自动收集热门视频(default_keywords为空时将自动生成)",
"user_info": {
"name": "",
"email": ""
}
}
FILE:config/workflow.json
{
"metadata": {
"name": "快导(KD) - 脚本生成工作流",
"version": "1.0.0",
"description": "10步完整执行流程",
"total_steps": 10
},
"workflow": {
"name": "快导系列",
"description": "生成多平台短视频脚本的10步流程",
"execution_mode": "step_by_step",
"error_handling": "stop_and_notify",
"step_timeout": 300,
"user_config_required": [
"copy_library_path",
"report_space_id",
"platform_keywords",
"rules_path"
],
"steps": {
"step1": {
"name": "搜索目标平台爆款",
"description": "搜索并选定平台爆款视频",
"note": "关键词池从用户配置的platforms.json读取",
"execution": {
"method": "random_sample",
"sample_size": "用户配置",
"results_per_search": 20,
"final_selection": "用户配置"
},
"output": {
"selected_keywords": [],
"trending_videos": [],
"final_selection": []
}
},
"step2": {
"name": "读取平台规则",
"description": "读取平台运营规则文档",
"note": "规则文档由用户通过'规则更新系列'生成",
"rules_summary": [
"时长要求",
"核心指标",
"引流限制",
"原创要求",
"互动率要求",
"违规红线",
"文案要求"
],
"output": {
"platform_rules": {},
"key_requirements": []
}
},
"step3": {
"name": "搜索外网平台",
"description": "搜索TikTok/YouTube爆款",
"note": "关键词池从用户配置的外部关键词设置读取",
"execution": {
"method": "random_sample",
"sample_size": 3,
"results_per_search": 20,
"final_selection": 1
},
"skip_condition": "如无爆款可选,则取消本次任务执行"
},
"step4": {
"name": "同质化检查",
"description": "检查避免与已有脚本重复",
"note": "从用户配置的文案库路径读取已有脚本",
"action": "读取文案库,输出已有脚本详情",
"output": {
"existing_scripts_count": 0,
"existing_scripts": [],
"themes_to_avoid": []
}
},
"step5": {
"name": "格式检查",
"description": "现场读取Excel实际格式",
"action": "使用Python openpyxl读取,禁止凭记忆",
"check_items": {
"title_row": ["字体", "字号", "粗体", "字体颜色", "填充类型", "填充颜色", "水平对齐", "垂直对齐", "自动换行", "边框", "行高"],
"data_row_A": ["字体", "字号", "粗体", "填充类型", "填充颜色", "水平对齐", "垂直对齐", "自动换行", "边框", "行高", "合并"],
"data_row_B_H": ["字体", "字号", "粗体", "填充类型", "水平对齐", "垂直对齐", "自动换行", "边框", "行高", "合并"],
"data_row_I_N": ["字体", "字号", "粗体", "填充类型", "水平对齐", "垂直对齐", "自动换行", "边框", "行高", "合并"],
"summary": ["总行数", "总列数", "标题行格式", "数据行A列格式", "数据行B-H列格式", "数据行I-N列格式", "合并单元格范围"]
},
"output": {
"format_confirmed": false,
"format_details": {}
}
},
"step6": {
"name": "生成脚本",
"description": "生成差异化脚本",
"note": "分镜数量根据总时长和分镜时长自动计算",
"requirements": {
"script_count": "用户配置,默认5条",
"segments_per_script": "auto",
"segment_duration": "from_platform_config",
"content_detail": "from_platform_config"
},
"key_rules": [
"每个分镜时长必须符合平台配置",
"A列必须包含完整故事叙述(起承转合)",
"C-H列内容必须详细(满足字数要求)",
"脚本必须差异化"
]
},
"step7": {
"name": "合理性检查",
"description": "对生成的脚本进行批量检查",
"check_dimensions": [
"时间合理性",
"内容合理性",
"场景合理性",
"台词合理性",
"分镜时长限制",
"技术描述合理性",
"标题合理性",
"格式合理性",
"故事叙述"
],
"action_on_fail": "直接修改脚本,记录修改内容"
},
"step8": {
"name": "更新文案库",
"description": "将脚本写入Excel文案库",
"execution": {
"method": "split_write",
"scripts_per_batch": 1,
"write_mode": "append",
"preserve_format": true
},
"output": {
"write_status": [],
"start_row": 0,
"end_row": 0
}
},
"step9": {
"name": "全面检查对比",
"description": "验证写入后的文件",
"method": "python_openpyxl",
"visual_confirmation": "禁止",
"check_dimensions": [
"格式正确性",
"内容完整性",
"写入位置正确性"
],
"error_handling": {
"first_fail": "自主修改格式问题",
"after_fix": "重新执行step9",
"second_fail": "删除step8写入的所有内容,回退到step8重新执行"
}
},
"step10": {
"name": "提交报告",
"description": "生成并保存执行报告",
"note": "保存到用户配置的飞书空间ID",
"report_format": "markdown",
"save_location": "飞书知识库",
"naming": "日期-快导-平台名称"
}
}
},
"rule_update_workflow": {
"name": "规则更新系列",
"description": "生成/更新平台规则文档(单次任务,非周期)",
"execution_mode": "manual",
"steps": [
"搜索平台规则变化",
"搜索违规案例",
"搜索爆款趋势",
"生成规则文档"
],
"output": {
"rules_document": "保存到用户配置的rules_path目录(默认:references/platform_rules/)"
}
}
}
FILE:config/__init__.py
# config 包初始化
# 快导(KD) 配置文件目录
FILE:entry_template.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
KD Skill 入口脚本模板
使用此模板创建你自己的 KD Skill 入口脚本
编码修复已内置,支持 Windows 中文正常显示
⚠️ 安全提示:
1. 修改下方 SKILL_PATH 为你实际的安装路径
2. 修改 OUTPUT_PATH 为你希望保存输出的目录
3. 首次运行前请检查所有路径配置
"""
import sys
import io
# ========== Windows 编码修复 - 必须在所有导入之前 ==========
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# ==========================================================
# ========== 配置区域 - 修改为你自己的路径 ==========
# KD Skill 安装路径
SKILL_PATH = r'C:\Users\YOUR_USERNAME\.agents\skills\kd' # <-- 修改这里
# 输出文件保存路径(请确保目录存在)
OUTPUT_PATH = r'C:\Users\YOUR_USERNAME\Documents\kd_output.txt' # <-- 修改这里
# ==================================================
# 安全提示
print("⚠️ 安全提示:首次运行前请确认:")
print(f" 1. SKILL_PATH: {SKILL_PATH}")
print(f" 2. OUTPUT_PATH: {OUTPUT_PATH}")
print(" 3. 这些路径是你期望的")
print()
response = input("是否继续? (yes/no): ")
if response.lower() != 'yes':
print("已取消")
sys.exit(0)
# 添加 KD Skill 到路径
sys.path.insert(0, SKILL_PATH)
# 导入 KD Skill 模块
from scripts import (
ConfigManager,
ScriptGenerator,
ExcelManager,
WorkflowManager,
FormatChecker
)
# ========== 你的代码从这里开始 ==========
def main():
# 使用用户指定的输出路径
output_file = OUTPUT_PATH
with open(output_file, 'w', encoding='utf-8') as f:
f.write("=" * 50 + "\n")
f.write("快导(KD) Skill 入口脚本\n")
f.write("=" * 50 + "\n")
# 示例:加载配置
f.write("\n[1] 加载平台配置\n")
config = ConfigManager()
platform = config.get_platform_config('xiaohongshu')
f.write(f" 平台: {platform['name']}\n")
f.write(f" 用户画像: {platform['user_profile']}\n")
f.write(f" 内容风格: {platform['content_style']}\n")
# 示例:创建脚本生成器
f.write("\n[2] 创建脚本生成器\n")
generator = ScriptGenerator('xiaohongshu')
f.write(f" 分镜数量: {generator.segments_count}\n")
f.write("\n" + "=" * 50 + "\n")
f.write("运行成功!\n")
f.write("=" * 50 + "\n")
print(f"输出已保存到: {output_file}")
if __name__ == '__main__':
main()
FILE:kd.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
快导(KD) - 短视频脚本批量生成与管理
使用方法:
kd run --platform xiaohongshu # 执行完整任务链
kd step --platform xiaohongshu --step 6 # 执行单个子任务
kd rules --platform xiaohongshu # 更新平台规则
kd config show # 查看配置
"""
import sys
import os
import argparse
from pathlib import Path
# Windows 编码修复 - 必须在所有导入之前
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# 添加scripts目录到路径
SCRIPT_DIR = Path(__file__).parent / "scripts"
sys.path.insert(0, str(SCRIPT_DIR))
from config_manager import ConfigManager
from script_generator import ScriptGenerator
def main():
parser = argparse.ArgumentParser(
description="快导(KD) - 短视频脚本批量生成与管理",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
kd run --platform xiaohongshu # 执行完整任务链
kd run --platform xiaohongshu --duration "2-3min" # 指定时长
kd step --platform xiaohongshu --step 6 # 执行单个子任务
kd rules --platform xiaohongshu # 更新平台规则
kd config show # 查看配置
kd config validate # 验证配置
kd config set-keywords --platform xiaohongshu --keywords "美食,探店"
kd config set-external-keywords --keywords "food,cooking"
kd config set copy_library_path "F:\\文案库\\"
kd config set report_space_id "7627134963053235418"
"""
)
subparsers = parser.add_subparsers(dest="command", help="可用命令")
# run 命令
run_parser = subparsers.add_parser("run", help="执行完整任务链(10步)")
run_parser.add_argument("--platform", required=True,
choices=["xiaohongshu", "douyin", "shipinhao"],
help="目标平台")
run_parser.add_argument("--duration",
help="总时长(如:2-3min, 1min)")
run_parser.add_argument("--count", type=int, default=5,
help="生成脚本数量(默认5)")
# step 命令
step_parser = subparsers.add_parser("step", help="执行单个子任务")
step_parser.add_argument("--platform", required=True,
choices=["xiaohongshu", "douyin", "shipinhao"],
help="目标平台")
step_parser.add_argument("--step", type=int, required=True,
choices=range(1, 11),
help="子任务编号(1-10)")
# rules 命令
rules_parser = subparsers.add_parser("rules", help="更新平台规则")
rules_parser.add_argument("--platform", required=True,
choices=["xiaohongshu", "douyin", "shipinhao"],
help="目标平台")
# config 命令
config_parser = subparsers.add_parser("config", help="配置管理")
config_subparsers = config_parser.add_subparsers(dest="config_cmd")
# config show
show_parser = config_subparsers.add_parser("show", help="查看配置")
show_parser.add_argument("--platform",
choices=["xiaohongshu", "douyin", "shipinhao"],
help="指定平台")
# config validate
validate_parser = config_subparsers.add_parser("validate", help="验证配置")
validate_parser.add_argument("--platform",
choices=["xiaohongshu", "douyin", "shipinhao"],
help="指定平台")
# config set
set_parser = config_subparsers.add_parser("set", help="设置配置项")
set_parser.add_argument("key", help="配置键名")
set_parser.add_argument("value", help="配置值")
# config set-keywords
keywords_parser = config_subparsers.add_parser("set-keywords", help="设置平台关键词")
keywords_parser.add_argument("--platform", required=True,
choices=["xiaohongshu", "douyin", "shipinhao"],
help="目标平台")
keywords_parser.add_argument("--keywords", required=True,
help="关键词列表(逗号分隔)")
# config set-external-keywords
ext_keywords_parser = config_subparsers.add_parser("set-external-keywords",
help="设置外网关键词")
ext_keywords_parser.add_argument("--keywords", required=True,
help="关键词列表(逗号分隔)")
# refs 命令
refs_parser = subparsers.add_parser("refs", help="文案库管理")
refs_subparsers = refs_parser.add_subparsers(dest="refs_cmd")
# refs show
refs_show_parser = refs_subparsers.add_parser("show", help="查看文案库")
refs_show_parser.add_argument("--platform", required=True,
choices=["xiaohongshu", "douyin", "shipinhao"],
help="目标平台")
# setup 命令
setup_parser = subparsers.add_parser("setup", help="安装和配置")
setup_parser.add_argument("--check-deps", action="store_true",
help="检查依赖")
setup_parser.add_argument("--install-deps", action="store_true",
help="安装依赖")
args = parser.parse_args()
if not args.command:
parser.print_help()
return 0
# 处理命令
if args.command == "run":
return cmd_run(args)
elif args.command == "step":
return cmd_step(args)
elif args.command == "rules":
return cmd_rules(args)
elif args.command == "config":
return cmd_config(args)
elif args.command == "refs":
return cmd_refs(args)
elif args.command == "setup":
return cmd_setup(args)
return 0
def cmd_run(args):
"""执行完整任务链"""
print(f"🚀 启动快导系列任务")
print(f" 平台: {args.platform}")
if args.duration:
print(f" 时长: {args.duration}")
print(f" 脚本数: {args.count}")
print()
# TODO: 实现完整任务链
print("⚠️ 完整任务链执行功能开发中...")
print(" 当前可用: kd step --platform {} --step <编号>".format(args.platform))
return 0
def cmd_step(args):
"""执行单个子任务"""
print(f"🚀 执行子任务 {args.step}")
print(f" 平台: {args.platform}")
print()
# TODO: 实现单个子任务执行
step_names = {
1: "搜索目标平台爆款",
2: "读取平台规则",
3: "搜索外网平台",
4: "同质化检查",
5: "格式检查",
6: "生成脚本",
7: "合理性检查",
8: "更新文案库",
9: "全面检查对比",
10: "提交报告"
}
print(f" 任务: {step_names.get(args.step, '未知')}")
print("⚠️ 单个子任务执行功能开发中...")
print(" 当前可用Python API:")
print(" from kd import ScriptGenerator")
print(" gen = ScriptGenerator(platform='{}')".format(args.platform))
return 0
def cmd_rules(args):
"""更新平台规则"""
print(f"📝 更新平台规则")
print(f" 平台: {args.platform}")
print()
# TODO: 实现规则更新
print("⚠️ 规则更新功能开发中...")
print(" 请手动编辑: references/platform_rules/{}_rules.md".format(args.platform))
return 0
def cmd_config(args):
"""配置管理"""
config = ConfigManager()
if not hasattr(args, 'config_cmd') or not args.config_cmd:
print("📋 配置管理")
print()
print("可用子命令:")
print(" kd config show 查看配置")
print(" kd config validate 验证配置")
print(" kd config set <key> <val> 设置配置")
print(" kd config set-keywords 设置平台关键词")
print(" kd config set-external-keywords 设置外网关键词")
return 0
if args.config_cmd == "show":
print("📋 当前配置")
print()
print(config.get_all())
elif args.config_cmd == "validate":
print("✅ 验证配置")
print()
# TODO: 实现配置验证
print("⚠️ 配置验证功能开发中...")
elif args.config_cmd == "set":
print(f"⚙️ 设置配置: {args.key} = {args.value}")
# TODO: 实现配置设置
print("⚠️ 配置设置功能开发中...")
elif args.config_cmd == "set-keywords":
keywords = [k.strip() for k in args.keywords.split(",")]
print(f"⚙️ 设置 {args.platform} 关键词: {keywords}")
# TODO: 实现关键词设置
print("⚠️ 关键词设置功能开发中...")
print(" 请手动编辑: config/platforms.json")
elif args.config_cmd == "set-external-keywords":
keywords = [k.strip() for k in args.keywords.split(",")]
print(f"⚙️ 设置外网关键词: {keywords}")
# TODO: 实现外网关键词设置
print("⚠️ 外网关键词设置功能开发中...")
print(" 请手动编辑: config/platforms.json")
return 0
def cmd_refs(args):
"""文案库管理"""
if not hasattr(args, 'refs_cmd') or not args.refs_cmd:
print("📚 文案库管理")
print()
print("可用子命令:")
print(" kd refs show --platform <平台> 查看文案库")
return 0
if args.refs_cmd == "show":
print(f"📚 查看 {args.platform} 文案库")
print()
# TODO: 实现文案库查看
print("⚠️ 文案库查看功能开发中...")
return 0
def cmd_setup(args):
"""安装和配置"""
print("🔧 安装和配置")
print()
if args.check_deps:
print("检查依赖...")
# TODO: 实现依赖检查
print("⚠️ 依赖检查功能开发中...")
elif args.install_deps:
print("安装依赖...")
# TODO: 实现依赖安装
print("⚠️ 依赖安装功能开发中...")
print(" 请手动安装: pip install openpyxl")
else:
print("首次使用配置指南:")
print()
print("1. 设置文案库路径:")
print(' kd config set copy_library_path "F:\\vlog\\JT\\idea\\"')
print()
print("2. 设置飞书空间ID:")
print(" kd config set report_space_id \"你的空间ID\"")
print()
print("3. 配置平台关键词:")
print(" kd config set-keywords --platform xiaohongshu \\")
print(' --keywords "美食,探店,农家菜,采摘"')
print()
print("4. 验证配置:")
print(" kd config validate")
print()
print("5. 执行快导任务:")
print(" kd run --platform xiaohongshu")
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:prompts/README.md
# Prompts 目录说明
本目录存放快导(KD) Skill 10步流程的子任务提示词模板。
## 目录结构
```
prompts/
├── step1_search_trending.md # 子任务1:搜索目标平台爆款
├── step2_read_rules.md # 子任务2:读取平台规则
├── step3_search_external.md # 子任务3:搜索外网平台
├── step4_check_duplicates.md # 子任务4:同质化检查
├── step5_scan_format.md # 子任务5:格式检查
├── step6_generate_scripts.md # 子任务6:生成脚本
├── step7_validate_scripts.md # 子任务7:合理性检查
├── step8_append_to_excel.md # 子任务8:更新文案库
├── step9_verify_write.md # 子任务9:全面检查对比
├── step10_generate_report.md # 子任务10:提交报告
└── README.md # 本文件
```
## 文件命名规则
```
step{序号}_{任务简称}.md
```
**示例:**
- `step1_search_trending.md` - 子任务1,搜索爆款
- `step6_generate_scripts.md` - 子任务6,生成脚本
## 文件内容结构
每个提示词文件包含以下部分:
1. **任务目标** - 该子任务的目标说明
2. **输入变量** - 需要动态填充的数据(`{{变量名}}`)
3. **执行流程** - 该子任务的执行步骤
4. **输出格式** - 期望的输出结构和示例
5. **注意事项** - 执行时的关键点
6. **错误处理** - 异常情况的处理方式
## 使用方式
提示词文件由 `ScriptGenerator` 类动态读取和使用:
```python
from script_generator import ScriptGenerator
# 生成脚本时自动加载 step6_generate_scripts.md
generator = ScriptGenerator(platform="xiaohongshu")
prompt = generator.load_prompt("step6", {
"platform": "xiaohongshu",
"trending_titles": [...],
"rules_summary": {...}
})
```
## 10步流程概览
| 步骤 | 名称 | 核心任务 | 输出 |
|:---:|:---|:---|:---|
| 1 | 搜索目标平台爆款 | 搜索并选定4个爆款 | 爆款列表+主题 |
| 2 | 读取平台规则 | 提取7项关键规则 | 规则摘要 |
| 3 | 搜索外网平台 | 搜索并选定1个外网爆款 | 外网爆款(可选)|
| 4 | 同质化检查 | 检查已有脚本,确定避开方向 | 需避开主题 |
| 5 | 格式检查 | 读取Excel实际格式 | 格式参数 |
| 6 | 生成脚本 | 生成4-5条差异化脚本 | 完整脚本 |
| 7 | 合理性检查 | 检查7项合理性,直接修改 | 检查清单+修改记录 |
| 8 | 更新文案库 | 脚本写入Excel | 写入确认 |
| 9 | 全面检查对比 | 验证格式、内容、位置 | 验证报告 |
| 10 | 提交报告 | 生成并保存执行报告 | Markdown报告 |
## 提示词变量规范
### 通用变量
| 变量名 | 说明 | 示例 |
|:---|:---|:---|
| `{{platform}}` | 平台英文标识 | xiaohongshu, douyin, shipinhao |
| `{{platform_name}}` | 平台中文名 | 小红书, 抖音, 视频号 |
| `{{task_date}}` | 任务日期 | 2026-04-22 |
### 子任务特定变量
每个提示词文件会定义自己的输入变量,详见各文件。
## 自定义提示词
您可以根据实际运营经验修改提示词:
```bash
# 编辑子任务6的提示词
nano prompts/step6_generate_scripts.md
# 编辑子任务7的提示词
nano prompts/step7_validate_scripts.md
```
**注意:** 修改提示词可能影响生成脚本的质量,建议先备份原文件。
## 版本管理
提示词文件版本与Skill版本一致:
| Skill版本 | 提示词版本 | 更新日期 |
|:---:|:---:|:---:|
| 1.0.0 | 1.0.0 | 2026-04-22 |
## 相关文件
- **配置:** `config/templates.json` - 提示词模板配置
- **脚本:** `scripts/script_generator.py` - 提示词加载器
- **流程:** `config/workflow.json` - 10步流程定义
## 常见问题
**Q: 提示词文件可以删除吗?**
A: 不建议删除,会导致对应子任务无法执行。可以修改内容,但不要删除文件。
**Q: 如何添加新的子任务提示词?**
A: 按照命名规则创建新文件(如 `step11_xxx.md`),并在 `config/workflow.json` 中添加对应步骤。
**Q: 提示词中的变量如何填充?**
A: 由 `ScriptGenerator` 类自动从子任务结果中提取并填充,无需手动处理。
FILE:prompts/step10_generate_report.md
# 子任务10:提交报告
## 任务目标
生成Markdown格式报告,包含每个子任务的关键输出和执行过程记录,保存到飞书文案库wiki。
## 输入变量
| 变量名 | 说明 | 来源 |
|:---|:---|:---|
| `{{task_date}}` | 任务执行日期 | 当前日期 |
| `{{platform}}` | 目标平台 | 子任务2 |
| `{{platform_name}}` | 平台中文名 | 配置 |
| `{{report_space_id}}` | 飞书空间ID | 用户配置 |
| `{{subtask_results}}` | 各子任务结果 | 子任务1-9 |
| `{{generated_scripts}}` | 生成的脚本列表 | 子任务7 |
| `{{write_positions}}` | 写入位置信息 | 子任务8 |
## 报告命名格式
```
{{task_date}}-快导-{{platform_name}}.md
```
**示例:**
- `2026-04-22-快导-小红书.md`
- `2026-04-22-快导-抖音.md`
- `2026-04-22-快导-视频号.md`
## 报告内容结构
```markdown
# 快导任务执行报告
## 基本信息
- **日期:** {{task_date}}
- **任务:** 快导系列
- **平台:** {{platform_name}} ({{platform}})
- **执行结果:** ✅ 成功 / ❌ 失败
---
## 各子任务关键输出
### 子任务1:搜索目标平台爆款
- **抽取的关键词:** [关键词1]、[关键词2]、[关键词3]
- **搜索1结果:** [爆款标题1(前20条中选取)]
- **搜索2结果:** [爆款标题2(前20条中选取)]
- **搜索3结果:** [爆款标题3(前20条中选取)]
- **最终选定4个爆款:** [主题1]、[主题2]、[主题3]、[主题4]
### 子任务2:读取平台规则
- **7项关键规则摘要:**
- 时长要求:[内容]
- 核心指标:[内容]
- 引流限制:[内容]
- 原创要求:[内容]
- 互动率要求:[内容]
- 违规红线:[内容]
- 文案要求:[内容]
### 子任务3:搜索外网平台
- **抽取的关键词:** [关键词1]、[关键词2]、[关键词3]
- **搜索1结果:** [爆款标题1(前20条中选取)]
- **搜索2结果:** [爆款标题2(前20条中选取)]
- **搜索3结果:** [爆款标题3(前20条中选取)]
- **最终选定1个外网爆款:** [主题]
- **状态:** ✅ 已选定 / ➖ 无合适爆款(如取消)
### 子任务4:同质化检查
- **已有脚本数量:** [数量]
- **脚本详情:** [标题+主题列表]
- **需避开方向:** [方向1]、[方向2]...
### 子任务5:格式检查
- **实际格式参数:** [字体、颜色、边框等]
- **确认结果:** ✅ 已确认
### 子任务6:生成脚本
- **生成脚本数量:** [4条/5条]
- **脚本主题清单:**
- 脚本1:[主题1] - 基于[爆款1]
- 脚本2:[主题2] - 基于[爆款2]
- 脚本3:[主题3] - 基于[爆款3]
- 脚本4:[主题4] - 基于[爆款4]
- 脚本5:[主题5] - 基于[外网爆款](如适用)
### 子任务7:合理性检查
- **检查结果:** ✅ 通过 / ❌ 修改
- **修改记录:**
- 脚本[X]:[修改内容]
- ...
### 子任务8:更新文案库
- **写入脚本数量:** [4条/5条]
- **写入位置:**
- 脚本1:第[X]行 - 第[Y]行
- 脚本2:第[X]行 - 第[Y]行
- ...
- **写入状态:** ✅ 成功
### 子任务9:全面检查对比
- **格式正确性:** ✅ 通过
- **内容完整性:** ✅ 通过
- **写入位置正确性:** ✅ 通过
- **整体结果:** ✅ 通过
---
## 执行过程记录
### 时间线
| 时间 | 事件 |
|:---:|:---|
| [开始时间] | 任务启动 |
| [时间1] | 子任务1完成 |
| [时间2] | 子任务2完成 |
| ... | ... |
| [结束时间] | 任务完成 |
### 遇到的问题
| 问题 | 解决方案 |
|:---|:---|
| [问题描述] | [如何解决] |
### 修改记录
| 位置 | 原内容 | 修改后 | 原因 |
|:---:|:---|:---|:---|
| [位置] | [原内容] | [新内容] | [原因] |
---
## 最终交付
### 生成脚本汇总
| 序号 | 主题 | Excel位置 | 状态 |
|:---:|:---|:---:|:---:|
| 1 | [主题1] | 第[X-Y]行 | ✅ 已写入 |
| 2 | [主题2] | 第[X-Y]行 | ✅ 已写入 |
| 3 | [主题3] | 第[X-Y]行 | ✅ 已写入 |
| 4 | [主题4] | 第[X-Y]行 | ✅ 已写入 |
| 5 | [主题5] | 第[X-Y]行 | ✅ 已写入/➖ 无 |
### 文件位置
- **文案库:** {{excel_path}}
- **本报告:** {{report_space_id}}/{{task_date}}-快导-{{platform_name}}.md
---
## 附录
### 平台配置快照
{{platform_config_json}}
### 规则摘要快照
{{rules_summary_json}}
### 生成脚本快照
{{generated_scripts_json}}
```
## 保存到飞书
### 命令
```bash
lark-cli docs +create \
--title "{{task_date}}-快导-{{platform_name}}" \
--wiki-space "{{report_space_id}}" \
--markdown "[报告内容]" \
--as user
```
### 保存位置
- **知识库:** 用户配置的 `report_space_id`
- **文档标题:** `YYYY-MM-DD-快导-平台名称`
- **文档格式:** Markdown
## 报告用途
1. **任务记录** - 完整记录本次执行过程
2. **问题追溯** - 出现问题时可查看历史执行
3. **经验积累** - 多次执行后分析优化点
4. **团队协作** - 分享给团队成员参考
## 注意事项
1. ✅ 包含**每个子任务**的关键输出
2. ✅ 记录**执行过程**(时间、问题、修改)
3. ✅ 保存到**飞书知识库**
4. ✅ 使用**标准命名格式**
5. ❌ 不要省略关键步骤的记录
6. ❌ 不要只记录成功,失败和问题也要记录
## 错误处理
```
如果 飞书保存失败:
本地保存报告到:reports/{{task_date}}-快导-{{platform_name}}.md
提示用户手动上传
记录错误原因
如果 报告生成失败:
输出简要执行摘要
提示用户查看各子任务的单独输出
```
## 报告查看命令
```bash
# 查看最近报告
kd report list --limit 5
# 查看指定日期报告
kd report show --date {{task_date}} --platform {{platform}}
# 下载报告到本地
kd report download --date {{task_date}} --platform {{platform}} --output ./reports/
```
FILE:prompts/step1_search_trending.md
# 子任务1:搜索目标平台爆款
## 任务目标
从用户设置的关键词池中随机抽取3个,分别搜索目标平台的爆款内容,最终选定4个最优爆款作为脚本生成参考。
## 输入变量
| 变量名 | 说明 | 来源 | 如何设置 |
|:---|:---|:---|:---|
| `{{platform}}` | 目标平台名称 | 子任务参数 | 执行时传入,如:xiaohongshu |
| `{{platform_en}}` | 平台英文标识 | 子任务参数 | 执行时传入,如:xiaohongshu |
| `{{keywords_pool}}` | 用户设置的关键词池 | 用户配置 | `kd config set-keywords` 或编辑 platforms.json |
| `{{search_count}}` | 每个关键词搜索结果数 | 用户配置 | 默认20,可配置 |
| `{{select_count}}` | 最终选定爆款数 | 用户配置 | 默认4,可配置 |
## 变量设置说明
### 如何设置关键词池
**方式1:使用命令行**
```bash
kd config set-keywords --platform xiaohongshu --keywords "关键词1,关键词2,关键词3"
```
**方式2:直接编辑配置文件**
编辑 `config/platforms.json`:
```json
{
"platforms": {
"xiaohongshu": {
"keywords": ["美食", "探店", "农家菜", "采摘"]
}
}
}
```
**方式3:在Python脚本中设置**
```python
from config_manager import ConfigManager
config = ConfigManager()
config.set_platform_keywords("xiaohongshu", ["美食", "探店", "农家菜"])
```
### 关键词池为空时的处理
**如果 `{{keywords_pool}}` 为空数组 `[]`:**
- ⚠️ 输出警告:"关键词池为空,无法进行搜索"
- ❌ 终止子任务1
- 💡 提示用户先配置关键词池
**用户配置示例:**
```bash
# 配置小红书关键词池
kd config set-keywords --platform xiaohongshu \
--keywords "美食,探店,农家菜,采摘,生活,乡村,慢生活,周末"
```
## 执行流程
1. 检查 `{{keywords_pool}}` 是否为空
2. 如为空,输出警告并终止
3. 从关键词池中**随机抽取3个**
4. 用"平台 + 关键词"分别搜索3次
5. 每个关键词取前 `{{search_count}}` 条结果(默认20)
6. 从结果中选取最优的 `{{select_count}}` 个爆款(默认4)
7. 记录最终选中的爆款
## 输出格式
```markdown
## 搜索结果
### 关键词池状态
- 平台:{{platform}}
- 关键词池:{{keywords_pool}}
- 状态:[✅ 已配置 / ❌ 为空]
### 抽取的关键词
关键词1、关键词2、关键词3
### 搜索1结果(关键词1)
| 排名 | 标题 | 播放量/互动量 | 选择理由 |
|:---:|:---|:---|:---|
| 1 | [标题] | [数据] | [原因] |
| 2 | [标题] | [数据] | |
| ... | ... | ... | |
### 搜索2结果(关键词2)
(同上格式)
### 搜索3结果(关键词3)
(同上格式)
## 最终选定的{{select_count}}个爆款
| 序号 | 标题 | 来源关键词 | 核心亮点 |
|:---:|:---|:---|:---|
| 1 | [爆款标题1] | [关键词] | [亮点] |
| 2 | [爆款标题2] | [关键词] | [亮点] |
| 3 | [爆款标题3] | [关键词] | [亮点] |
| 4 | [爆款标题4] | [关键词] | [亮点] |
## 主题提取
基于{{select_count}}个爆款,提取以下主题方向:
1. [主题1] - 基于[爆款标题]
2. [主题2] - 基于[爆款标题]
3. [主题3] - 基于[爆款标题]
4. [主题4] - 基于[爆款标题]
```
## 选择标准
| 维度 | 权重 | 说明 |
|:---|:---:|:---|
| 播放量/互动量 | 40% | 数据表现好 |
| 内容质量 | 30% | 制作精良、有创意 |
| 与品牌相关性 | 20% | 与嘉泰苑业务相关 |
| 可复制性 | 10% | 可借鉴但非抄袭 |
## 注意事项
1. ✅ **随机抽取**关键词,不固定顺序
2. ✅ 记录抽取过程,确保可追溯
3. ✅ 爆款选择需说明理由
4. ❌ 不要只选数据最高的,要综合考虑
5. ❌ 避免选择与已有脚本雷同的主题
## 使用示例
**场景1:关键词池已配置**
输入:
- platform: xiaohongshu
- keywords_pool: ["美食", "探店", "农家菜", "采摘", "生活", "乡村"]
- search_count: 20
- select_count: 4
输出:
```
## 搜索结果
### 关键词池状态
- 平台:xiaohongshu
- 关键词池:["美食", "探店", "农家菜", "采摘", "生活", "乡村"]
- 状态:✅ 已配置(6个关键词)
### 抽取的关键词
采摘、美食、乡村
搜索1结果(采摘):
...
最终选定4个爆款:
...
```
**场景2:关键词池为空**
输出:
```
## 搜索结果
### 关键词池状态
- 平台:xiaohongshu
- 关键词池:[]
- 状态:❌ 为空
### 错误
⚠️ 关键词池为空,无法进行搜索
### 解决方法
请配置关键词池:
kd config set-keywords --platform xiaohongshu \
--keywords "关键词1,关键词2,关键词3"
```
FILE:prompts/step2_read_rules.md
# 子任务2:读取平台规则
## 任务目标
读取目标平台运营规则文档,输出关键规则摘要(7项),用于指导脚本生成。
## 输入变量
| 变量名 | 说明 | 来源 | 如何设置 |
|:---|:---|:---|:---|
| `{{platform}}` | 目标平台名称 | 子任务参数 | 执行时传入,如:xiaohongshu |
| `{{platform_en}}` | 平台英文标识 | 子任务参数 | 执行时传入,如:xiaohongshu |
| `{{rules_file}}` | 规则文档路径 | 用户配置 | 默认:`references/platform_rules/{platform}_rules.md` |
| `{{rules_exists}}` | 规则文档是否存在 | 自动检测 | 系统自动检查文件是否存在 |
## 变量设置说明
### 如何设置规则文档
**方式1:使用命令行生成规则**
```bash
# 生成小红书规则
kd rules --platform xiaohongshu
# 生成抖音规则
kd rules --platform douyin
# 生成视频号规则
kd rules --platform shipinhao
```
**方式2:直接编辑规则文档**
```bash
nano references/platform_rules/xiaohongshu_rules.md
```
**方式3:在Python脚本中设置**
```python
from config_manager import ConfigManager
config = ConfigManager()
config.set_rules_path("references/platform_rules/")
```
### 规则文档不存在时的处理
**如果 `{{rules_exists}}` 为 false:**
- ⚠️ 输出警告:"规则文档不存在,请先运行规则更新系列"
- ❌ 终止子任务2
- 💡 提示用户生成规则文档
**用户生成规则示例:**
```bash
kd rules --platform xiaohongshu
```
## 规则文档格式(用户自定义)
规则文档由用户自行定义,建议包含以下内容:
```markdown
# 小红书平台运营规则
## 平台基础信息
- 用户画像:女性、种草决策
- 内容风格:攻略型、图文结合
- 总时长:2-3分钟
- 分镜时长:6-12秒
## 7项关键规则
| 规则项 | 内容 |
|:---|:---|
| 时长要求 | 2-3分钟,单分镜6-12秒 |
| 核心指标 | 完播率>40%,互动率>5% |
| 引流限制 | 允许主页引流,禁止直接留微信 |
| 原创要求 | 原创占比≥60% |
| 互动率要求 | 冷启动需≥5% |
| 违规红线 | 虚假宣传、侵权内容 |
| 文案要求 | 攻略感强,可用表情符号 |
```
**注意:** 以上为示例格式,内容由用户根据实际运营经验填写
## 执行流程
1. 检查 `{{rules_file}}` 是否存在
2. 如不存在,输出警告并终止
3. 读取规则文档内容
4. 提取7项关键规则
5. 输出规则摘要
## 输出格式
```markdown
## 平台规则摘要
### 文档来源
- 平台:{{platform}}
- 规则文档:{{rules_file}}
- 文档存在:{{rules_exists}}
- 生成日期:[文档中的日期,如不存在则为空]
- 版本:[文档中的版本,如不存在则为空]
### 7项关键规则
| 规则项 | 内容 |
|:---|:---|
| **时长要求** | [从规则文档提取] |
| **核心指标** | [从规则文档提取] |
| **引流限制** | [从规则文档提取] |
| **原创要求** | [从规则文档提取] |
| **互动率要求** | [从规则文档提取] |
| **违规红线** | [从规则文档提取] |
| **文案要求** | [从规则文档提取] |
### 平台特殊限制
| 限制项 | 要求 |
|:---|:---|
| [从规则文档提取] | [说明] |
## 脚本生成指导
基于以上规则,脚本生成应注意:
1. [从规则文档提取注意事项]
2. ...
```
## 7项关键规则说明
由用户自行定义,通常包括:
1. **时长要求** - 总时长、单分镜时长、分镜数量
2. **核心指标** - 完播率、互动率标准、优先级排序
3. **引流限制** - 允许和禁止的引流方式
4. **原创要求** - 原创占比、内容重复判定
5. **互动率要求** - 冷启动标准、互动类型优先级
6. **违规红线** - 绝对禁止内容、谨慎处理内容
7. **文案要求** - 标题字数限制、正文风格、禁忌词汇
## 注意事项
1. ✅ 如规则文档不存在,必须提示用户先生成
2. ✅ 摘要要精炼,只保留关键信息
3. ✅ 特殊限制必须突出提示
4. ❌ 不要复制整个规则文档,只提取7项关键规则
## 使用示例
**场景1:规则文档已存在**
输入:
- platform: xiaohongshu
- rules_file: references/platform_rules/xiaohongshu_rules.md
- rules_exists: true
输出:
```markdown
## 平台规则摘要
### 文档来源
- 平台:xiaohongshu
- 规则文档:references/platform_rules/xiaohongshu_rules.md
- 文档存在:true
- 生成日期:2026-04-22
- 版本:v1.0.0
### 7项关键规则
| 规则项 | 内容 |
|:---|:---|
| 时长要求 | 2-3分钟,单分镜6-12秒 |
| 核心指标 | 完播率>40%,互动率>5% |
| 引流限制 | 允许主页引流,禁止直接留微信 |
| 原创要求 | 原创占比≥60% |
| 互动率要求 | 冷启动需≥5% |
| 违规红线 | 虚假宣传、侵权内容 |
| 文案要求 | 攻略感强,可用表情符号 |
### 平台特殊限制
| 限制项 | 要求 |
|:---|:---|
| 无特殊限制 | - |
## 脚本生成指导
基于以上规则,脚本生成应注意:
1. 时长控制在2-3分钟
2. 单分镜6-12秒
3. 攻略型内容优先
```
**场景2:规则文档不存在**
输出:
```
## 平台规则摘要
### 文档来源
- 平台:xiaohongshu
- 规则文档:references/platform_rules/xiaohongshu_rules.md
- 文档存在:false
### 错误
⚠️ 规则文档不存在,请先运行规则更新系列
### 解决方法
请生成规则文档:
kd rules --platform xiaohongshu
或手动创建:
nano references/platform_rules/xiaohongshu_rules.md
```
FILE:prompts/step3_search_external.md
# 子任务3:搜索外网平台
## 任务目标
从用户设置的外网关键词池中随机抽取3个,搜索TikTok/YouTube爆款,最终选定1个外网爆款作为创意参考。
## 输入变量
| 变量名 | 说明 | 来源 | 如何设置 |
|:---|:---|:---|:---|
| `{{platform}}` | 目标平台名称 | 子任务参数 | 执行时传入,如:xiaohongshu |
| `{{external_keywords_pool}}` | 用户设置的外网关键词池 | 用户配置 | `kd config set-external-keywords` 或编辑配置文件 |
| `{{search_count}}` | 每个关键词搜索结果数 | 用户配置 | 默认20,可配置 |
| `{{select_count}}` | 最终选定爆款数 | 用户配置 | 默认1,可配置 |
## 变量设置说明
### 如何设置外网关键词池
**方式1:使用命令行**
```bash
kd config set-external-keywords --keywords "关键词1,关键词2,关键词3"
```
**方式2:直接编辑配置文件**
编辑 `config/platforms.json` 或用户配置文件:
```json
{
"external_keywords": ["food", "cooking", "life", "vlog", "story"]
}
```
**方式3:在Python脚本中设置**
```python
from config_manager import ConfigManager
config = ConfigManager()
config.set_external_keywords(["food", "cooking", "life", "vlog", "story"])
```
### 外网关键词池为空时的处理
**如果 `{{external_keywords_pool}}` 为空数组 `[]`:**
- ⚠️ 输出提示:"外网关键词池为空,跳过外网搜索"
- ⏭️ 直接跳到子任务4(不终止任务)
- 💡 提示用户可配置外网关键词(可选)
**注意:** 外网搜索是可选步骤,即使为空也不影响主流程
**用户配置示例(可选):**
```bash
# 配置外网关键词池(可选)
kd config set-external-keywords \
--keywords "food,cooking,life,vlog,story,family,memory"
```
## 执行流程
1. 检查 `{{external_keywords_pool}}` 是否为空
2. 如为空,输出提示并跳到子任务4
3. 从外网关键词池中**随机抽取3个**
4. 用"TikTok/YouTube + 关键词"分别搜索3次
5. 每个关键词取前 `{{search_count}}` 条结果(默认20)
6. 从结果中选取最优的 `{{select_count}}` 个爆款(默认1)
7. 记录最终选中的外网爆款
## 输出格式
```markdown
## 外网搜索结果
### 关键词池状态
- 外网关键词池:{{external_keywords_pool}}
- 状态:[✅ 已配置 / ➖ 为空]
### 抽取的关键词(如已配置)
关键词1、关键词2、关键词3
### 搜索1结果(TikTok - 关键词1)
| 排名 | 标题 | 播放量 | 创意亮点 |
|:---:|:---|:---:|:---|
| 1 | [标题] | [数据] | [亮点] |
| 2 | [标题] | [数据] | |
| ... | ... | ... | |
### 搜索2结果(YouTube - 关键词2)
(同上格式)
### 搜索3结果(TikTok - 关键词3)
(同上格式)
## 最终选定的{{select_count}}个外网爆款(如已配置)
| 平台 | 标题 | 关键词 | 创意亮点 | 可借鉴点 |
|:---|:---|:---|:---|:---|
| TikTok/YouTube | [爆款标题] | [关键词] | [亮点] | [借鉴思路] |
## 创意提取(如已配置)
基于外网爆款,提取以下创意元素:
- 叙事结构: [结构类型]
- 视觉风格: [风格描述]
- 情绪节奏: [节奏特点]
- 可本土化适配: [适配建议]
```
## 选择标准
| 维度 | 权重 | 说明 |
|:---|:---:|:---|
| 创意新颖度 | 40% | 创意独特、有启发性 |
| 制作水准 | 30% | 拍摄、剪辑精良 |
| 情感共鸣 | 20% | 有情感感染力 |
| 可本土化 | 10% | 可适配到国内平台 |
## 注意事项
1. ✅ **随机抽取**关键词,不固定顺序
2. ✅ 重点关注创意和叙事手法,非直接搬运
3. ✅ 记录可本土化的创意点
4. ❌ 如无合适爆款可选,可取消本次外网搜索
5. ❌ 避免选择与目标平台风格差异过大的内容
## 使用示例
**场景1:外网关键词池已配置**
输入:
- platform: xiaohongshu
- external_keywords_pool: ["food", "cooking", "life", "vlog", "story"]
- search_count: 20
- select_count: 1
输出:
```markdown
## 外网搜索结果
### 关键词池状态
- 外网关键词池:["food", "cooking", "life", "vlog", "story"]
- 状态:✅ 已配置(5个关键词)
### 抽取的关键词
life, vlog, story
搜索1结果(TikTok - life):
...
最终选定1个外网爆款:
- 平台: TikTok
- 标题: 「A day in my countryside life」
- 关键词: life
- 创意亮点: 乡村慢生活vlog风格
- 可借鉴点: 可本土化为嘉泰苑慢生活场景
创意提取:
- 叙事结构: 一天时间线叙事
- 视觉风格: 自然光、暖色调
- 情绪节奏: 舒缓、放松
- 可本土化适配: 嘉泰苑的一天(采摘+就餐+休闲)
```
**场景2:外网关键词池为空**
输出:
```markdown
## 外网搜索结果
### 关键词池状态
- 外网关键词池:[]
- 状态:➖ 为空(跳过此步骤)
### 提示
⏭️ 外网关键词池为空,跳过外网搜索
### 可选配置
如需启用外网搜索,可配置关键词池:
kd config set-external-keywords \
--keywords "food,cooking,life,vlog,story"
### 后续流程
直接跳转到子任务4:同质化检查
```
FILE:prompts/step4_check_duplicates.md
# 子任务4:同质化检查
## 任务目标
读取现有文案库,输出已有脚本详情,确定需避开的主题方向,避免生成重复或雷同的脚本。
## 输入变量
| 变量名 | 说明 | 示例 |
|:---|:---|:---|
| `{{platform}}` | 目标平台名称 | 小红书、抖音、视频号 |
| `{{excel_path}}` | 文案库Excel路径 | [用户配置的路径] |
| `{{sheet_name}}` | 工作表名称 | 文案库 |
## 执行流程
1. 读取文案库Excel文件
2. 提取所有已有脚本的标题和主题
3. 分析主题分布
4. 确定需避开的方向
5. 输出同质化检查报告
## 输出格式
```markdown
## 同质化检查报告
### 已有脚本统计
| 项目 | 数值 |
|:---|:---:|
| 总脚本数 | {{total_scripts}} |
| 本月新增 | {{monthly_new}} |
| 待使用脚本 | {{pending_scripts}} |
| 已使用脚本 | {{used_scripts}} |
### 已有脚本列表
| 序号 | 标题 | 主题 | 状态 | 发布日期 |
|:---:|:---|:---|:---:|:---:|
| 1 | [脚本标题1] | [主题1] | 待使用/已使用 | 2026-04-15 |
| 2 | [脚本标题2] | [主题2] | 待使用/已使用 | 2026-04-10 |
| ... | ... | ... | ... | ... |
### 主题分布分析
| 主题类别 | 脚本数量 | 占比 | 趋势 |
|:---|:---:|:---:|:---:|
| [主题类别1] | [数量] | [百分比] | 饱和/正常/稀少 |
| [主题类别2] | [数量] | [百分比] | 饱和/正常/稀少 |
| ... | ... | ... | ... |
### 需避开的主题方向
以下主题已有多条脚本,建议避开:
| 主题 | 已有脚本数 | 建议 |
|:---|:---:|:---|
| [主题1] | [数量] | ⚠️ 饱和,建议避开 |
| [主题2] | [数量] | ⚠️ 饱和,建议避开 |
| [主题3] | [数量] | ⚠️ 相似度高,建议避开 |
### 推荐的主题方向
以下主题暂无或较少,可优先考虑:
| 主题 | 当前数量 | 建议 |
|:---|:---:|:---|
| [主题A] | 0 | ✅ 空白,可优先 |
| [主题B] | 1 | ✅ 较少,可考虑 |
| [主题C] | 2 | ✅ 适中,可补充 |
### 差异化建议
基于子任务1和3确定的爆款主题,建议:
1. **爆款主题**: [主题1]
- 已有脚本: [数量]
- 差异化方向: [具体建议]
2. **爆款主题**: [主题2]
- 已有脚本: [数量]
- 差异化方向: [具体建议]
3. **外网爆款**: [主题]
- 本土化方向: [具体建议]
## 主题判定标准
### 饱和判定
- 同一主题 ≥ 3条脚本 → 饱和,建议避开
- 相似主题 ≥ 5条脚本 → 饱和,建议避开
### 相似度判定
- 标题相似度 ≥ 70% → 视为相似
- 核心场景/人物相同 → 视为相似
## 注意事项
1. ✅ 使用Python openpyxl读取,禁止凭记忆
2. ✅ 只读取A列(标题)和主题列
3. ✅ 相似度判定要客观
4. ❌ 不要因1-2条相似就判定为饱和
5. ❌ 避免过度避开的导致主题单一
## 错误处理
```
如果 Excel文件不存在:
输出: "⚠️ 文案库不存在,路径: {{excel_path}}"
输出: "请确认路径配置正确:kd config set copy_library_path "你的路径""
终止任务(可选继续,但提示风险)
如果 文案库为空:
输出: "✅ 文案库为空,无同质化风险"
继续执行
```
FILE:prompts/step5_scan_format.md
# 子任务5:格式检查
## 任务目标
现场读取Excel实际格式,记录实际格式参数,输出格式确认清单,确保后续写入格式正确。
## 输入变量
| 变量名 | 说明 | 示例 |
|:---|:---|:---|
| `{{platform}}` | 目标平台名称 | 小红书、抖音、视频号 |
| `{{excel_path}}` | 文案库Excel路径 | [用户配置的路径] |
| `{{sheet_name}}` | 工作表名称 | 文案库 |
## 执行流程
1. 使用Python openpyxl读取Excel文件
2. 扫描标题行格式(字体、颜色、边框等)
3. 扫描数据行格式(A列、B-H列、I-N列)
4. 记录合并单元格规则
5. 输出格式确认清单
## 输出格式
```markdown
## 格式检查报告
### 基本信息
| 项目 | 数值 |
|:---|:---:|
| 文件路径 | {{excel_path}} |
| 工作表名 | {{sheet_name}} |
| 总行数 | {{total_rows}} |
| 总列数 | {{total_columns}} |
| 标题行 | 第1行 |
| 数据起始行 | 第2行 |
### 标题行格式(第1行)
| 参数 | 实际值 |
|:---|:---|
| 字体 | [微软雅黑/宋体/etc] |
| 字号 | [14/12/etc] |
| 粗体 | [是/否] |
| 字体颜色 | [RGB值] |
| 填充类型 | [solid/pattern] |
| 填充颜色 | [RGB值] |
| 水平对齐 | [center/left/right] |
| 垂直对齐 | [center/top/bottom] |
| 自动换行 | [True/False] |
| 边框 | [thin/medium/none] |
| 行高 | [20.4/etc] |
### 数据行A列格式(视频文案列)
| 参数 | 实际值 |
|:---|:---|
| 字体 | [宋体/etc] |
| 字号 | [11/etc] |
| 粗体 | [是/否] |
| 填充类型 | [solid/none] |
| 填充颜色 | [RGB值] |
| 水平对齐 | [left/center/right] |
| 垂直对齐 | [center/top/bottom] |
| 自动换行 | [True/False] |
| 边框 | [thin/etc] |
| 行高 | [49.95/etc] |
| 合并规则 | [跨行合并/不合并] |
### 数据行B-H列格式(分镜详情列)
| 参数 | 实际值 |
|:---|:---|
| 字体 | [宋体/etc] |
| 字号 | [11/etc] |
| 粗体 | [是/否] |
| 填充类型 | [solid/none] |
| 填充颜色 | [RGB值/无] |
| 水平对齐 | [left/center/right] |
| 垂直对齐 | [center/top/bottom] |
| 自动换行 | [True/False] |
| 边框 | [thin/etc] |
| 行高 | [49.95/etc] |
| 合并规则 | [每行独立/跨行合并] |
### 数据行I-N列格式(BGM等列)
| 参数 | 实际值 |
|:---|:---|
| 字体 | [宋体/etc] |
| 字号 | [11/etc] |
| 粗体 | [是/否] |
| 填充类型 | [solid/none] |
| 填充颜色 | [RGB值/无] |
| 水平对齐 | [left/center/right] |
| 垂直对齐 | [center/top/bottom] |
| 自动换行 | [True/False] |
| 边框 | [thin/etc] |
| 行高 | [49.95/etc] |
| 合并规则 | [跨行合并/不合并] |
### 合并单元格规则
| 列范围 | 合并方式 | 说明 |
|:---|:---|:---|
| A列 (1) | [跨行合并/不合并] | 视频文案列 |
| B-H列 (2-8) | [每行独立] | 分镜详情列 |
| I-N列 (9-14) | [跨行合并/不合并] | BGM等列 |
### 格式确认清单
| 检查项 | 结果 | 说明 |
|:---:|:---:|:---|
| 标题行格式确认 | ✅/❌ | [说明] |
| 数据行A列格式确认 | ✅/❌ | [说明] |
| 数据行B-H列格式确认 | ✅/❌ | [说明] |
| 数据行I-N列格式确认 | ✅/❌ | [说明] |
| 合并单元格规则确认 | ✅/❌ | [说明] |
| **整体格式确认** | ✅/❌ | [说明] |
## 完整格式参数(用于子任务6)
```python
format_params = {
"header_row": {
"font": "[字体]",
"size": [字号],
"bold": [True/False],
"font_color": "[RGB]",
"fill_type": "[类型]",
"fill_color": "[RGB]",
"align_horizontal": "[对齐]",
"align_vertical": "[对齐]",
"wrap_text": [True/False],
"border": "[样式]",
"row_height": [高度]
},
"data_row_A": { ... },
"data_row_B_H": { ... },
"data_row_I_N": { ... },
"merge_rules": {
"A": [True/False],
"B_H": [False],
"I_N": [True/False]
}
}
```
## 注意事项
1. ✅ **必须现场读取**,禁止凭记忆
2. ✅ 记录每个细节(行高、对齐、边框等)
3. ✅ 确认合并单元格的实际规则
4. ❌ 不要假设格式与标准一致
5. ❌ 不要省略任何格式参数
## 常见格式差异
| 项 | 可能差异 | 处理方式 |
|:---|:---|:---|
| 字体 | 微软雅黑/宋体 | 记录实际值 |
| 字号 | 10/11/12 | 记录实际值 |
| 颜色 | RGB值差异 | 记录实际RGB |
| 合并 | 跨1行/多行 | 现场确认 |
## 错误处理
```
如果 Excel文件不存在:
输出: "⚠️ Excel文件不存在: {{excel_path}}"
终止任务
如果 格式读取失败:
输出: "⚠️ 格式读取失败: [错误详情]"
尝试使用默认格式(提示风险)
```
FILE:prompts/step6_generate_scripts.md
# 子任务6:生成脚本
## 任务目标
基于子任务1和3确定的主题,基于子任务2的规则,生成差异化的短视频脚本。
## 输入变量
| 变量名 | 说明 | 来源 |
|:---|:---|:---|
| `{{platform}}` | 目标平台 | 子任务2 |
| `{{platform_config}}` | 平台配置 | config/platforms.json |
| `{{trending_titles}}` | 4个爆款标题 | 子任务1 |
| `{{external_title}}` | 1个外网爆款 | 子任务3 |
| `{{rules_summary}}` | 7项关键规则 | 子任务2 |
| `{{avoid_themes}}` | 需避开主题 | 子任务4 |
| `{{format_params}}` | Excel格式参数 | 子任务5 |
| `{{count}}` | 生成脚本数 | 平台配置(4-5条) |
## 生成要求
### 数量要求
- 基于子任务1的4个爆款 → 生成**4条脚本**(不同主题)
- 基于子任务3的1个外网爆款 → 生成**1条脚本**(如无则只生成4条)
- **总计:4条或5条脚本**
### 差异化要求
**5条脚本必须差异化**,包括但不限于:
- 不同主题方向
- 不同叙事角度
- 不同场景设置
- 不同情感基调
- 不同目标受众
## 提示词模板
```
【角色设定】(Role)
你是一位拥有5年经验的资深短视频编导,擅长{{platform}}平台内容创作。
【任务目标】(Task)
请为我撰写{{count}}份关于[主题]的短视频完整脚本。
【输入来源】
- 主题来源:{{trending_titles}}(4个)+ {{external_title}}(1个)
- 平台规则:{{rules_summary}}
- 需避开:{{avoid_themes}}
- 平台配置:{{platform_config}}
【核心要素】
- 目标平台:{{platform}}
- 视频时长:{{platform_config.duration.total}}
- 分镜数量:{{platform_config.duration.segments_count}}
- 单分镜时长:{{platform_config.duration.segment}}
【时长匹配要求】(系统自动处理)
### 时间段标注(系统自动计算)
**默认行为:系统自动计算并调整时间段**
生成脚本时,系统会:
1. 根据台词字数计算台词时长(0.25秒/字)
2. 根据运镜方式计算运镜时长(固定2秒、推拉3秒、摇移4秒、复杂6秒、航拍8秒)
3. 取最大值 + 1秒缓冲 = 建议时长
4. **自动更新时间段为建议时长**(累加计算:0-Xs, X-Ys, Y-Zs...)
**用户操作:**
- ✅ 无需手动计算,系统自动优化
- ✅ 生成的B列时间段仅作为初始参考
- ⚠️ 如需关闭自动调整,见SKILL.md配置说明
### 平台时长标准
| 平台 | 总时长 | 分镜数 | 分镜时长范围 | 说明 |
|:---|:---:|:---:|:---:|:---|
| 抖音 | 45-60秒 | 12个 | 3-8秒 | 节奏快,切换频繁 |
| 小红书 | 120-180秒 | 18个 | 6-12秒 | 内容详细,时长较长 |
| 视频号 | 60-180秒 | 15个 | 5-10秒 | 叙事性强,节奏适中 |
### 分镜时长分配模式
遵循"开头快、中间稳、结尾慢"原则:
- **开场**(前20%分镜):短时长,快速切入
- **主体**(中间60%分镜):标准时长,内容展开
- **结尾**(后20%分镜):稍长,升华收尾
**提示:** 系统会自动计算并调整(auto_adjust),无需手动控制时间段。如需关闭自动调整,在 platforms.json 中设置 `"auto_adjust": false`。
【脚本要求】
1. 数量:{{count}}条
2. 关系:{{count}}条脚本必须差异化
3. 差异化维度:主题、角度、场景、情感、受众
4. 避开:{{avoid_themes}}
5. 符合:{{rules_summary}}
【结构与约束】
- 结构框架:黄金3秒钩子 → 痛点引入 → 核心内容 → 互动引导
- 内容约束:
* 禁用"首先、其次、因此"等书面连接词
* 必须包含一个反常识观点或冲突
* 标题符合{{platform_config.title_rules}}
* 避开{{avoid_themes}}
* 符合{{rules_summary.违规红线}}
【输出格式】
按照{{format_params}}输出14列,每条脚本包含{{platform_config.duration.segments_count}}个分镜:
列A - 视频文案:
标题:[符合平台规则的标题]
故事:[200字以上的完整故事,起承转合]
标签:[#标签1 #标签2 #标签3]
【分镜内容详细格式】(必须遵守)
**C列-镜头描述**(最少{{platform_config.content_requirements.columns.C.min_chars}}字):
- 设备/焦段/内容 (如:索尼A7M4 / 24-70mm / 果园全景)
- 示例:大疆Air3航拍 / 4K 30fps / 俯瞰太平镇枇杷园全貌,展现整片金黄
**D列-运镜描述**(最少{{platform_config.content_requirements.columns.D.min_chars}}字):
- 方式/轨迹/速度 (如:向前推进 / 直线 / 缓慢)
- 示例:环绕跟拍 / 弧线 / 中速,跟随采摘动作360度展示
**E列-拍摄技巧**(最少{{platform_config.content_requirements.columns.E.min_chars}}字):
- 光线/ISO/对焦/色调(如:自然光 / ISO100 / 自动对焦 / 暖色调)
- 示例:侧逆光+反光板补光 / ISO200 / 人脸追踪对焦 / 高饱和度暖黄
**F列-画面描述**(最少{{platform_config.content_requirements.columns.F.min_chars}}字):
- 场景/构图/色彩/氛围(如:枇杷树下 / 三分法构图 / 金黄翠绿 / 温馨田园)
- 示例:木质凉亭内 / 对角线构图 / 暖黄灯光+深绿背景 / 家庭聚餐温馨感
**G列-台词**(最少{{platform_config.content_requirements.columns.G.min_chars}}字):
- 要求:口语化、真实感、无书面语
- 禁忌:❌"首先、其次、综上所述" → ✅"先、再说、所以说"
**H列-音效**(最少{{platform_config.content_requirements.columns.H.min_chars}}字):
- 环境音/音乐/节奏 (如:鸟鸣虫叫 / 轻快吉他 / 跟随画面节奏)
- 示例:风吹树叶沙沙声+远处狗吠 / 民谣风格 / 渐强后淡出
**I列-BGM**(必须详细):
- 格式:音乐名:XXX,风格:XXX,使用时机:XXX
- 示例:
* 音乐名:《稻香》周杰伦
* 风格:田园治愈、轻快温暖
* 使用时机:0-30秒前奏铺垫,30-60秒主歌展开,60-90秒副歌高潮
**L列-关联活动**(自动匹配或默认):
- 系统会根据脚本主题自动匹配活动配置
- 如无匹配:"日常推广"
- 如未配置活动:"日常推广,当前未设置activities"
列I-N(跨行合并):
I-BGM:[推荐音乐名称、风格、卡点位置]
J-标签:[#话题标签,附选择理由]
K-状态:待使用
L-活动:[关联活动(如有)]
M-日期:[计划发布日期]
N-备注:[拍摄注意事项、替代方案等]
【质量检查】(Quality Check)
生成后自检以下项:
- [ ] 标题是否符合平台限制
- [ ] 时长是否控制在规定范围
- [ ] 台词是否口语化(非书面化)
- [ ] B-C列时间段与镜头是否设计合理
- [ ] 内容是否与已有脚本不重复
- [ ] {{count}}条脚本是否差异化
```
## 输出格式
```markdown
## 脚本生成报告
### 生成概览
| 项目 | 数值 |
|:---|:---:|
| 生成脚本数 | {{count}} |
| 平台 | {{platform}} |
| 总时长 | {{platform_config.duration.total}} |
| 分镜数/脚本 | {{platform_config.duration.segments_count}} |
### 脚本列表
#### 脚本1:[主题]
**主题来源:** [来源爆款标题]
**差异化点:** [与其他的区别]
| 列 | 内容 |
|:---|:---|
| A-视频文案 | [标题]\n[故事]\n[标签] |
| B-时间段 | 0-{{duration1}}秒 |
| C-镜头 | [详细描述] |
| D-运镜 | [详细描述] |
| E-拍摄技巧 | [详细描述] |
| F-画面 | [详细描述] |
| G-台词 | [口语化台词] |
| H-音效 | [音效描述] |
| I-BGM | [音乐信息] |
| J-标签 | #标签 |
| K-状态 | 待使用 |
| L-活动 | [活动] |
| M-日期 | [日期] |
| N-备注 | [备注] |
... 分镜2、3、...、{{platform_config.duration.segments_count}} ...
#### 脚本2:[主题]
...
#### 脚本3:[主题]
...
#### 脚本4:[主题]
...
#### 脚本5:[主题](如适用)
...
### 差异化说明
| 脚本 | 主题 | 角度 | 场景 | 情感 | 受众 |
|:---:|:---|:---|:---|:---|:---|
| 1 | [主题1] | [角度1] | [场景1] | [情感1] | [受众1] |
| 2 | [主题2] | [角度2] | [场景2] | [情感2] | [受众2] |
| ... | ... | ... | ... | ... | ... |
### 与爆款的关系
| 脚本 | 参考爆款 | 创新点 |
|:---:|:---|:---|
| 1 | [爆款1] | [创新说明] |
| 2 | [爆款2] | [创新说明] |
| ... | ... | ... |
```
## 注意事项
1. ✅ **必须差异化**,不能只是改标题
2. ✅ 基于平台规则设计内容
3. ✅ B-C列时间段与镜头必须合理匹配
4. ✅ 每个分镜内容必须丰富详细
5. ❌ 禁止使用书面连接词
6. ❌ 避开已饱和的主题
## 特殊平台要求
### 视频号(重要)
- **标题 ≤ 16字**(硬性限制)
- **标题不能含标点**(硬性限制)
- 示例:「樱桃园里吃到饱这口甜等了一年」(12字)✅
## 错误处理
```
如果 生成脚本数 ≠ {{count}}:
补充生成缺失的脚本
如果 某条脚本不符合规则:
重新生成该条脚本
如果 差异化检查未通过:
修改脚本,确保差异化
```
FILE:prompts/step7_validate_scripts.md
# 子任务7:合理性检查
## 任务目标
对子任务6生成的5条(或4条)脚本进行批量检查,检查7项合理性,输出检查清单,直接修改不合理项。
## 输入变量
| 变量名 | 说明 | 来源 |
|:---|:---|:---|
| `{{scripts}}` | 生成的脚本列表 | 子任务6 |
| `{{count}}` | 脚本数量 | 4或5 |
| `{{platform}}` | 目标平台 | 子任务2 |
| `{{rules_summary}}` | 平台规则摘要 | 子任务2 |
| `{{format_params}}` | Excel格式参数 | 子任务5 |
## 检查维度(7项)
| 检查项 | 检查内容 | 权重 |
|:---|:---|:---:|
| **时间合理性** | 时间段分配是否合理,总时长是否符合 | 20% |
| **内容合理性** | 内容是否连贯,逻辑是否通顺 | 15% |
| **场景合理性** | 场景是否真实可拍(嘉泰苑实际场景) | 15% |
| **台词合理性** | 台词是否口语化,是否符合人设 | 15% |
| **时长合理性** | 总时长是否在规则范围内 | 15% |
| **标题合理性** | 标题是否符合平台限制(视频号16字) | 10% |
| **格式合理性** | 14列内容是否完整,格式是否正确 | 10% |
## 检查细则
### 1. 时间合理性
**检查点:**
- 时间段是否连续无重叠
- 总时长是否符合平台要求
- 单分镜时长是否在允许范围
**标准:**
- 小红书:6-12秒/分镜
- 抖音:3-5秒/分镜
- 视频号:5-10秒/分镜
### 2. 内容合理性
**检查点:**
- 故事是否有起承转合
- 分镜之间是否连贯
- 是否存在逻辑漏洞
**标准:**
- 故事完整,≥200字
- 逻辑通顺,无跳跃
- 有情感递进
### 3. 场景合理性
**检查点:**
- 场景是否在嘉泰苑存在
- 场景是否可实际拍摄
- 画面描述是否可实现
**嘉泰苑实际场景:**
- 8亩枇杷园
- 5亩樱桃园
- 3亩葡萄园
- 4亩桃园
- 6个包间(含2个高档包间)
- 麻将、赏花、萌宠区域
### 4. 台词合理性
**检查点:**
- 是否口语化(非书面语)
- 是否符合人设
- 字数是否达标
**禁忌词汇:**
- ❌ "首先"、"其次"、"因此"、"综上所述"
- ❌ 过于书面化的表达
- ❌ 过于生硬的转折
### 5. 时长合理性
**检查点:**
- 总时长是否在范围内
- 各分镜时长分配是否合理
**标准:**
- 小红书:2-3分钟
- 抖音:≤1分钟
- 视频号:1-3分钟
### 6. 标题合理性
**检查点(视频号特别重要):**
- 字数是否符合限制
- 是否包含标点(视频号禁止)
- 是否吸睛、有吸引力
**限制:**
- 视频号:≤16字,无标点
- 小红书:建议≤20字
- 抖音:建议≤15字
### 7. 格式合理性
**检查点:**
- 14列是否都有数据
- 合并单元格规则是否正确
- 格式参数是否符合
## 输出格式
```markdown
## 合理性检查报告
### 检查概览
| 项目 | 数值 |
|:---|:---:|
| 检查脚本数 | {{count}} |
| 通过脚本数 | [数量] |
| 需修改脚本数 | [数量] |
| 整体结果 | ✅通过/❌需修改 |
### 详细检查结果
| 脚本 | 时间 | 内容 | 场景 | 台词 | 时长 | 标题 | 格式 | 结果 | 修改说明 |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| 脚本1 | ✅/❌ | ✅/❌ | ✅/❌ | ✅/❌ | ✅/❌ | ✅/❌ | ✅/❌ | 通过/不通过 | [修改内容] |
| 脚本2 | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 脚本3 | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 脚本4 | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 脚本5 | ... | ... | ... | ... | ... | ... | ... | ... | ... |
### 问题汇总
#### 脚本[X]问题
| 检查项 | 问题描述 | 修改方案 | 修改后状态 |
|:---|:---|:---|:---:|
| [检查项] | [具体问题] | [如何修改] | ✅已修改 |
### 修改记录
#### 脚本1修改
**原问题:** [问题描述]
**修改内容:** [具体修改]
**修改后:** [修改后的内容]
#### 脚本2修改
...
### 最终脚本确认
| 脚本 | 主题 | 状态 | 备注 |
|:---:|:---|:---:|:---|
| 1 | [主题1] | ✅通过 | - |
| 2 | [主题2] | ✅通过 | [如有修改说明] |
| ... | ... | ... | ... |
### 与提示词模板【质量检查】的关系
- 【质量检查】= 生成时的自检(提示词内)
- 【合理性检查】= 生成后的复核(本子任务)
```
## 处理方式
```
发现不合理项 → 直接修改脚本 → 重新检查 → 记录修改
```
**修改原则:**
1. 小问题(格式、错别字):直接修改
2. 中问题(时间、台词):修改后标注
3. 大问题(主题、场景):评估后决定是否重写
## 注意事项
1. ✅ 对5条(或4条)脚本**批量检查**
2. ✅ 检查7项**全部维度**
3. ✅ 发现不合理**直接修改**,不等待
4. ✅ 在"修改说明"列记录修改内容
5. ❌ 不要只检查不修改
6. ❌ 不要跳过任何一条脚本
## 错误处理
```
如果 某条脚本问题严重(如场景完全不存在):
标记为"需重写"
提示用户决定是否继续
如果 多条脚本存在同类问题:
批量修改
记录共性问题
```
FILE:prompts/step8_append_to_excel.md
# 子任务8:更新文案库
## 任务目标
将子任务7检查通过的脚本写入文案库Excel。如果文案库不存在,按照示例格式创建。
## 输入变量
| 变量名 | 说明 | 来源 | 如何设置 |
|:---|:---|:---|:---|
| `{{scripts}}` | 检查通过的脚本列表 | 子任务7 | 上一步输出 |
| `{{count}}` | 脚本数量 | 子任务7 | 4或5 |
| `{{platform}}` | 目标平台 | 子任务2 | 执行时传入 |
| `{{excel_path}}` | 文案库Excel路径 | 用户配置 | `kd config set copy_library_path` |
| `{{format_params}}` | Excel格式参数 | 子任务5 | 现场扫描获取 |
## 变量设置说明
### 如何设置文案库路径
**方式1:使用命令行**
```bash
kd config set copy_library_path "F:\\vlog\\JT\\idea\\xiaohongshu_scripts.xlsx"
```
**方式2:直接编辑配置文件**
编辑 `config/user_config.json`:
```json
{
"copy_library_path": "F:\\vlog\\JT\\idea\\xiaohongshu_scripts.xlsx"
}
```
**方式3:在Python脚本中设置**
```python
from config_manager import ConfigManager
config = ConfigManager()
config.set_copy_library_path("F:\\vlog\\JT\\idea\\xiaohongshu_scripts.xlsx")
```
### 文案库不存在时的处理
**如果 `{{excel_path}}` 指向的文件不存在:**
- 🆕 按照示例格式创建新的Excel文件
- ✅ 应用默认格式参数
- 💡 提示用户:"文案库不存在,已按默认格式创建"
**默认格式示例:**
| 列 | 内容 | 格式 |
|:---|:---|:---|
| A | 视频文案 | 宋体,浅绿色背景,跨行合并 |
| B-H | 分镜详情 | 宋体,不合并 |
| I-N | BGM等 | 宋体,跨行合并 |
**注意:** 实际格式可在子任务5中扫描确认
## 执行流程
1. 检查 `{{excel_path}}` 是否存在
2. 如不存在,按示例格式创建新文件
3. 打开文案库Excel文件
4. 确定写入起始行(已有数据最后一行+1)
5. **拆分执行**:脚本分别独立写入
6. 每个脚本按格式参数写入14列
7. 处理合并单元格(A列、I-N列)
8. 保存文件
## 拆分执行方案
| 子任务 | 脚本 | 起始行 | 结束行 | 状态 |
|:---:|:---:|:---:|:---:|:---:|
| 8-1 | 脚本1 | 第X行 | 第X+Y行 | 待执行 |
| 8-2 | 脚本2 | 第X+Y+1行 | 第X+Y+Z+1行 | 待执行 |
| 8-3 | 脚本3 | ... | ... | 待执行 |
| 8-4 | 脚本4 | ... | ... | 待执行 |
| 8-5 | 脚本5(如有) | ... | ... | 待执行 |
**说明:**
- Y = 脚本1的分镜数 - 1
- Z = 脚本2的分镜数 - 1
- 以此类推
## 写入内容
### A列 - 视频文案(跨行合并)
```
[标题]
[故事内容,200字以上]
#[标签1] #[标签2] #[标签3]
```
**合并规则:**
- 起始行到结束行合并
- 垂直居中对齐
- 自动换行开启
### B-H列 - 分镜详情(每行独立)
每行一个分镜,不合并:
| 列 | 内容 |
|:---|:---|
| B-时间段 | 0-{{duration}}秒 |
| C-镜头 | [详细描述] |
| D-运镜 | [详细描述] |
| E-拍摄技巧 | [详细描述] |
| F-画面 | [详细描述] |
| G-台词 | [口语化台词] |
| H-音效 | [音效描述] |
### I-N列 - 附加信息(跨行合并)
| 列 | 内容 |
|:---|:---|
| I-BGM | [音乐信息] |
| J-标签 | #[标签] |
| K-状态 | 待使用 |
| L-活动 | [关联活动] |
| M-日期 | [计划发布日期] |
| N-备注 | [拍摄注意事项] |
**合并规则:**
- 起始行到结束行合并(I-N列一起合并)
- 垂直居中对齐
- 自动换行开启
## 格式应用
### 写入前
```python
# 应用A列格式
apply_format(format_params['data_row_A'], start_row, end_row, col=1)
# 应用B-H列格式
for row in range(start_row, end_row + 1):
apply_format(format_params['data_row_B_H'], row, row, col=2, end_col=8)
# 应用I-N列格式
apply_format(format_params['data_row_I_N'], start_row, end_row, col=9, end_col=14)
```
## 输出格式
```markdown
## 文案库更新报告
### 文案库状态
| 项目 | 数值 |
|:---|:---|
| 文件路径 | {{excel_path}} |
| 文件存在 | [✅ 存在 / 🆕 新创建] |
| 创建时间 | [如新创建,显示时间] |
### 写入概览
| 项目 | 数值 |
|:---|:---:|
| 写入脚本数 | {{count}} |
| 起始行 | 第[起始]行 |
| 结束行 | 第[结束]行 |
| 总占用行数 | [行数] |
### 拆分执行详情
| 子任务 | 脚本 | 写入状态 | 起始行 | 结束行 | 分镜数 | 备注 |
|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| 8-1 | 脚本1 | ✅成功/❌失败 | 第X行 | 第Y行 | [数量] | [备注] |
| 8-2 | 脚本2 | ✅成功/❌失败 | 第X行 | 第Y行 | [数量] | [备注] |
| 8-3 | 脚本3 | ✅成功/❌失败 | 第X行 | 第Y行 | [数量] | [备注] |
| 8-4 | 脚本4 | ✅成功/❌失败 | 第X行 | 第Y行 | [数量] | [备注] |
| 8-5 | 脚本5 | ✅成功/❌失败 | 第X行 | 第Y行 | [数量] | [备注] |
### 脚本位置确认
| 脚本 | 主题 | Excel行号 | 状态 |
|:---:|:---|:---:|:---:|
| 1 | [主题1] | 第X-Y行 | ✅已写入 |
| 2 | [主题2] | 第X-Y行 | ✅已写入 |
| 3 | [主题3] | 第X-Y行 | ✅已写入 |
| 4 | [主题4] | 第X-Y行 | ✅已写入 |
| 5 | [主题5] | 第X-Y行 | ✅已写入/➖无 |
### 格式应用确认
| 列范围 | 合并状态 | 格式状态 | 备注 |
|:---|:---:|:---:|:---|
| A列 | ✅已合并 | ✅已应用 | 视频文案 |
| B-H列 | ➖不合并 | ✅已应用 | 分镜详情 |
| I-N列 | ✅已合并 | ✅已应用 | 附加信息 |
### 写入后文件信息
| 项目 | 数值 |
|:---|:---|
| 文件路径 | {{excel_path}} |
| 总行数 | [更新后行数] |
| 总脚本数 | [更新后脚本数] |
| 文件大小 | [大小] |
```
## 备份说明
- 仅写入主文案库即可
- 不额外备份到"永久保存脚本库"
- 如需备份,用户可自行复制Excel文件
## 注意事项
1. ✅ **拆分执行**:脚本分别独立写入,避免超时
2. ✅ **追加写入**:在已有数据最后一行后追加,不删除、不覆盖
3. ✅ **格式保持**:严格按照子任务5确定的实际格式写入
4. ✅ **合并单元格**:按实际格式处理A列、I-N列的合并
5. ✅ **自动创建**:如文案库不存在,按默认格式创建
6. ❌ 不要一次性写入所有脚本
7. ❌ 不要改变已有数据的格式
## 首次使用配置
如文案库路径未配置,需先设置:
```bash
# 配置文案库路径(示例)
kd config set copy_library_path "F:\\vlog\\JT\\idea\\xiaohongshu_scripts.xlsx"
```
或创建新的文案库:
```bash
# 创建新的文案库(自动按默认格式创建)
kd excel create --platform xiaohongshu --output "F:\\vlog\\JT\\idea\\xiaohongshu_scripts.xlsx"
```
## 使用示例
**场景1:文案库已存在**
输入:
- excel_path: "F:\vlog\JT\idea\xiaohongshu_scripts.xlsx"
- scripts: [脚本1, 脚本2, 脚本3, 脚本4]
- count: 4
输出:
```
## 文案库更新报告
### 文案库状态
| 项目 | 数值 |
|:---|:---|
| 文件路径 | F:\vlog\JT\idea\xiaohongshu_scripts.xlsx |
| 文件存在 | ✅ 存在 |
### 写入概览
| 项目 | 数值 |
|:---|:---:|
| 写入脚本数 | 4 |
| 起始行 | 第156行 |
| 结束行 | 第212行 |
...
```
**场景2:文案库不存在(自动创建)**
输出:
```
## 文案库更新报告
### 文案库状态
| 项目 | 数值 |
|:---|:---|
| 文件路径 | F:\vlog\JT\idea\xiaohongshu_scripts.xlsx |
| 文件存在 | 🆕 新创建 |
| 创建时间 | 2026-04-22 14:30:00 |
### 提示
🆕 文案库不存在,已按默认格式创建
### 后续配置
如需自定义格式,可在子任务5中扫描现有格式
```
FILE:prompts/step9_verify_write.md
# 子任务9:全面检查对比
## 任务目标
检查写入后的Excel文件,验证3项正确性(格式、内容、位置),与子任务7原始脚本对比,确保无丢失、无变形。
## 输入变量
| 变量名 | 说明 | 来源 |
|:---|:---|:---|
| `{{excel_path}}` | 文案库Excel路径 | 用户配置 |
| `{{original_scripts}}` | 子任务7原始脚本 | 子任务7 |
| `{{write_positions}}` | 写入位置信息 | 子任务8 |
| `{{format_params}}` | Excel格式参数 | 子任务5 |
## 检查维度(3项)
| 检查项 | 检查内容 | 权重 |
|:---|:---|:---:|
| **格式正确性** | 是否与子任务5记录的格式一致 | 40% |
| **内容完整性** | 14列是否都有数据,合并单元格是否正确 | 40% |
| **写入位置正确性** | 是否按子任务8确认的起始/结束行号写入 | 20% |
## 检查细则
### 1. 格式正确性
**检查点:**
- 字体(名称、大小、颜色)
- 对齐方式(水平、垂直)
- 填充颜色(背景色RGB值)
- 边框(样式、颜色)
- 自动换行
- 行高
**对比对象:**
- 子任务5记录的格式参数
- 实际写入后的单元格格式
**通过标准:**
- 关键参数(字体、对齐、颜色)一致
- 次要参数(行高)允许微小偏差
### 2. 内容完整性
**检查点:**
- 14列是否都有数据
- 合并单元格范围是否正确
- 数据是否与原始脚本一致
- 有无丢失或变形
**验证方式:**
```python
# 读取写入后的内容
written_data = read_excel_rows(start_row, end_row)
# 对比原始数据
for i, script in enumerate(original_scripts):
if written_data[i] != script:
report_diff(i, script, written_data[i])
```
### 3. 写入位置正确性
**检查点:**
- 起始行号是否匹配
- 结束行号是否匹配
- 脚本间是否有重叠或间隙
**验证方式:**
```python
# 检查每个脚本的实际位置
for script_id, expected in write_positions.items():
actual = find_script_in_excel(script_id)
if actual != expected:
report_position_error(script_id, expected, actual)
```
## 输出格式
```markdown
## 全面检查对比报告
### 检查概览
| 检查项 | 结果 | 说明 |
|:---:|:---:|:---|
| 格式正确性 | ✅通过/❌不通过 | [说明] |
| 内容完整性 | ✅通过/❌不通过 | [说明] |
| 写入位置正确性 | ✅通过/❌不通过 | [说明] |
| **整体结果** | ✅通过/❌不通过 | [说明] |
### 详细检查结果
#### 1. 格式正确性检查
| 检查项 | 预期值 | 实际值 | 状态 | 说明 |
|:---|:---|:---|:---:|:---|
| 标题行字体 | [预期] | [实际] | ✅/❌ | |
| 标题行颜色 | [预期] | [实际] | ✅/❌ | |
| 数据行A列字体 | [预期] | [实际] | ✅/❌ | |
| 数据行A列颜色 | [预期] | [实际] | ✅/❌ | |
| 数据行B-H列对齐 | [预期] | [实际] | ✅/❌ | |
| 数据行I-N列合并 | [预期] | [实际] | ✅/❌ | |
| ... | ... | ... | ... | |
#### 2. 内容完整性检查
| 脚本 | 标题 | 分镜数 | 内容检查 | 合并检查 | 结果 | 差异说明 |
|:---:|:---|:---:|:---:|:---:|:---:|:---|
| 脚本1 | [标题] | [数量] | ✅/❌ | ✅/❌ | 通过/不通过 | [差异] |
| 脚本2 | [标题] | [数量] | ✅/❌ | ✅/❌ | 通过/不通过 | [差异] |
| 脚本3 | [标题] | [数量] | ✅/❌ | ✅/❌ | 通过/不通过 | [差异] |
| 脚本4 | [标题] | [数量] | ✅/❌ | ✅/❌ | 通过/不通过 | [差异] |
| 脚本5 | [标题] | [数量] | ✅/❌ | ✅/❌ | 通过/不通过 | [差异] |
##### 内容对比详情(脚本1)
| 列 | 原始内容 | 写入后内容 | 状态 |
|:---:|:---|:---|:---:|
| A | [原始] | [实际] | ✅/❌ |
| B | [原始] | [实际] | ✅/❌ |
| C | [原始] | [实际] | ✅/❌ |
| ... | ... | ... | ... |
#### 3. 写入位置正确性检查
| 脚本 | 预期起始行 | 预期结束行 | 实际起始行 | 实际结束行 | 状态 | 偏差 |
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 脚本1 | [行号] | [行号] | [行号] | [行号] | ✅/❌ | [说明] |
| 脚本2 | [行号] | [行号] | [行号] | [行号] | ✅/❌ | [说明] |
| 脚本3 | [行号] | [行号] | [行号] | [行号] | ✅/❌ | [说明] |
| 脚本4 | [行号] | [行号] | [行号] | [行号] | ✅/❌ | [说明] |
| 脚本5 | [行号] | [行号] | [行号] | [行号] | ✅/❌ | [说明] |
### 差异汇总
#### 严重差异(需修正)
| 类型 | 位置 | 问题 | 建议 |
|:---|:---|:---|:---|
| [类型] | [位置] | [问题] | [建议] |
#### 轻微差异(可接受)
| 类型 | 位置 | 问题 | 说明 |
|:---|:---|:---|:---|
| [类型] | [位置] | [问题] | [说明] |
## 3层错误恢复机制
如检查发现错误,按以下层级处理:
### 第1层:自动修复
**适用场景:** 格式小问题(如边框、对齐)
**处理方式:**
```python
# 自动修复格式问题
for issue in format_issues:
if issue.can_auto_fix:
auto_fix(issue)
log_fix(issue)
```
### 第2层:重新验证
**适用场景:** 自动修复后
**处理方式:**
```python
# 重新执行全面检查
re_check_result = run_full_check()
if re_check_result.passed:
continue_to_step_10()
else:
goto_layer_3()
```
### 第3层:删除回退
**适用场景:** 严重错误无法自动修复
**处理方式:**
```python
# 删除已写入的行
delete_rows(start_row, end_row)
# 回退到子任务8
rollback_to_step_8()
# 提示用户
notify_user("发现严重错误,已回退到子任务8,请修正后重新执行")
```
## 与子任务7的区别
| 维度 | 子任务7 | 子任务9 |
|:---|:---|:---|
| **检查对象** | 生成的脚本内容 | 写入Excel后的实际数据 |
| **检查层面** | 内容层面(时间、场景、台词) | 技术层面(格式、数据完整性、位置) |
| **对比基准** | 平台规则和合理标准 | 子任务7原始脚本和子任务5格式参数 |
| **发现问题** | 内容问题 | 写入过程中的技术问题 |
| **处理方式** | 直接修改脚本 | 自动修复或回退重写 |
## 注意事项
1. ✅ 检查3项**全部维度**
2. ✅ **对比原始脚本**,确保无丢失、无变形
3. ✅ 执行3层错误恢复机制
4. ✅ 错误严重时**回退到子任务8**
5. ❌ 不要忽略轻微差异(可能累积成大问题)
6. ❌ 不要跳过任何一条脚本的检查
## 通过标准
**全部通过条件:**
- 格式正确性:≥90%一致
- 内容完整性:100%无丢失
- 写入位置正确性:100%准确
**部分通过处理:**
- 轻微差异:记录并继续
- 严重差异:执行3层恢复机制
FILE:README.md
# 快导 (KD) - 短视频脚本批量生成
**版本:** 1.1.0(2026年4月更新)
快导(KD)是一个跨平台、跨行业的短视频脚本批量生成与管理工具,支持单次任务执行。
---
## ⚠️ 安装前必读(安全检查清单)
在安装和运行快导之前,请确认以下事项:
### 1. 依赖检查
- [ ] Python 3.7+ 已安装
- [ ] `openpyxl` 已安装 (`pip install openpyxl`)
- [ ] `web_search` skill 可用(用于搜索平台爆款)
- [ ] 可选:`lark-cli`(仅当需要飞书上传功能时)
### 2. 路径配置安全
- [ ] `copy_library_path` 指向你**控制的目录**
- [ ] `rules_path` 指向你**控制的目录**
- [ ] 避免使用系统关键目录(如 C:\Windows, /usr/bin 等)
### 3. 飞书上传(可选)
- [ ] 了解 `feishu_permissions.json` 中的 36 个权限 scope
- [ ] 仅提供 `LARK_CLI_TOKEN` 如果你**确实需要**上传报告
- [ ] token 将存储在 `lark-cli` 配置中,**不在快导内**
### 4. 示例脚本安全
- [ ] `entry_template.py` 包含**占位符路径**(YOUR_USERNAME)
- [ ] **运行前必须修改**路径配置
- [ ] 首次运行时会提示确认路径
### 5. 网络行为知情
- [ ] `web_search` 会发送搜索查询到外部服务
- [ ] `external_search.auto_collect` 启用时会自动搜索热门
- [ ] 不会自动上传任何数据到第三方服务
**确认以上事项后,再继续安装。**
---
## 项目结构
```
kd/
├── kd.py # 主入口文件
├── test_workflow.py # 工作流测试脚本
├── entry_template.py # 入口模板示例
├── config/ # 配置文件目录
│ ├── platforms.json # 平台参数配置(时长、关键词等)
│ ├── workflow.json # 工作流配置(10步流程定义)
│ ├── templates.json # 模板配置(提示词模板)
│ ├── user_config.json # 用户配置(文案库路径、飞书空间ID等)
│ └── feishu_permissions.json # 飞书权限配置
├── scripts/ # 核心模块
│ ├── __init__.py # 模块初始化,导出核心类
│ ├── config_manager.py # 配置管理(ConfigManager)
│ ├── excel_manager.py # Excel操作(ExcelManager)
│ ├── script_generator.py # 脚本生成(ScriptGenerator)
│ ├── workflow_manager.py # 工作流管理(WorkflowManager)
│ ├── format_checker.py # 格式检查(FormatChecker)
│ └── feishu_permission_helper.py # 飞书权限开通助手
├── prompts/ # 提示词模板
│ ├── step6_generate_scripts.md # 子任务6:生成脚本提示词
│ └── step7_validate_scripts.md # 子任务7:合理性检查提示词
├── references/ # 参考文档
│ ├── platform_rules/ # 平台规则文档(自动生成)
│ │ ├── xiaohongshu_rules.md
│ │ ├── douyin_rules.md
│ │ └── shipinhao_rules.md
│ └── README.md # 参考资料说明
├── tests/ # 测试脚本
│ ├── config_manager_win.py # Windows配置管理测试
│ ├── excel_manager_win.py # Windows Excel操作测试
│ ├── format_checker_win.py # Windows格式检查测试
│ ├── script_generator_win.py # Windows脚本生成测试
│ ├── __init__.py
│ ├── mac/ # macOS测试脚本
│ │ ├── config_manager_mac.py
│ │ ├── excel_manager_mac.py
│ │ ├── format_checker_mac.py
│ │ └── script_generator_mac.py
│ └── linux/ # Linux测试脚本
│ ├── config_manager_linux.py
│ ├── excel_manager_linux.py
│ ├── format_checker_linux.py
│ └── script_generator_linux.py
├── requirements.txt # Python依赖
├── setup.py # 安装配置
├── .gitignore # Git忽略配置
├── SKILL.md # OpenClaw Skill文档
└── README.md # 本文件(用户使用手册)
```
---
## 核心类说明
| 类名 | 文件 | 用途 |
|:---|:---|:---|
| `ConfigManager` | `config_manager.py` | 管理所有配置文件(platforms.json、user_config.json等) |
| `ExcelManager` | `excel_manager.py` | 读写Excel文案库,格式扫描与保持 |
| `ScriptGenerator` | `script_generator.py` | 生成短视频脚本内容 |
| `WorkflowManager` | `workflow_manager.py` | 协调执行10步快导流程 |
| `FormatChecker` | `format_checker.py` | 检查Excel格式、内容合理性 |
---
---
## ⚠️ CRITICAL — 首次使用必读
**用户:**"我要用快导生成小红书脚本"
**我:**好的,使用快导之前,需要先完成几项配置。我来引导你:
---
### 第0步:开通飞书机器人权限(如使用飞书功能)
**用户:**"我需要保存报告到飞书吗?"
**我:**"如果你希望自动上传执行报告到飞书知识库,需要先开通机器人权限。如果只用本地保存,可以跳过这步。"
**开通权限(可选):**
```bash
# 运行权限开通助手
python scripts/feishu_permission_helper.py
```
或手动开通:
1. 进入飞书开发者后台:https://open.feishu.cn/app
2. 选择你的应用 → 权限管理 → 开通权限
3. 点击"批量导入/导出权限"
4. 粘贴 `config/feishu_permissions.json` 中的内容
5. 申请开通并发布应用
**需要的权限包括:**
- 云文档读写(保存报告)
- 知识库访问(上传到 wiki)
- 消息发送(通知功能)
---
### 第1步:安装依赖
**用户:**"我已经装好了 Python,接下来呢?"
**我:**安装 openpyxl,用于操作 Excel 文案库:
```bash
pip install openpyxl
```
---
### 第2步:配置搜索关键词和主题方向
**用户:**"我是做农家乐的,怎么配置?"
**我:**请告诉我你的搜索关键词和创作主题方向:
- **搜索关键词:** 用于搜索平台爆款视频(例如:农家乐、采摘、成都周边、周末去哪)
- **主题方向:** 你希望创作的内容方向(可选,为空则不限制主题。例如:采摘攻略、亲子游玩、美食探店)
我会帮你配置到 `config/platforms.json` 中。
**示例:**
```json
{
"platforms": {
"xiaohongshu": {
"keywords": ["农家乐", "采摘", "成都周边"],
"theme_direction": ["采摘攻略", "亲子游玩"]
}
}
}
```
**注意:** `theme_direction`(主题方向)可为空,不填则不限制创作主题。
---
### 第3步:设置文案库保存位置
**用户:**"生成的脚本保存在哪里?"
**我:**请告诉我你希望保存到哪个文件夹,例如:
> "保存到 F:\\vlog\\JT\\idea\\ 目录,文件名叫小红书文案库.xlsx"
我会帮你配置到 `config/user_config.json` 中。
**配置项说明:**
| 配置项 | 你要告诉我的内容 | 用途 |
|:---|:---|:---|
| 文案库路径 | "F:\\vlog\\JT\\idea\\小红书文案库.xlsx" | 保存生成的脚本 |
| 飞书空间ID | 你的飞书知识库空间ID(可选)| 自动上传报告到飞书 |
| 规则文档路径 | 留空即可(可选)| 保存平台规则文档 |
---
### 第4步:生成平台规则(可选)
**用户:**"什么是平台规则?需要吗?"
**我:**平台规则包含小红书的运营规范,例如:
- 视频时长要求(2-3分钟)
- 标题字数限制(20字以内)
- 违规内容红线
**你可以选择:**
- ✅ **帮我生成默认规则** — 我基于平台通用规则创建
- ❌ **先跳过** — 后续在生成的脚本中手动调整
如果选择生成,告诉我:"帮我生成小红书规则文档"
---
### 完成!现在可以开始使用了
**用户:**"配置好了,开始生成脚本吧"
**我:**好的,执行快导系列任务,告诉我:
1. 目标平台(小红书/抖音/视频号)
2. 生成几条脚本(默认5条)
我们开始吧!
---
## ⚠️ 安全与隐私说明
### 数据存储
- **所有生成的脚本**保存在本地配置的 Excel 文件路径
- **执行报告**默认保存到本地 `reports/` 目录
- **不会自动上传到任何云服务**
### 飞书上传(可选)
- 如需上传报告到飞书知识库,**需手动配置**
- 步骤:
1. 安装并配置 `lark-cli`(见下方"飞书权限开通"章节)
2. 手动运行上传命令
- **默认不上传**,仅在用户明确要求时才执行
### 网络访问
- **web_search**:搜索平台爆款时使用(必需)
- **无其他外部网络调用**
- 所有脚本执行在本地完成
### 敏感信息
- 如需配置飞书认证,token 保存在 `lark-cli` 的配置中
- 快导 skill **不存储任何凭证**
---
## 使用说明(重要)
**当前版本状态:**
- ✅ **Python API**:完整可用,推荐通过 Python 代码调用
- 🚧 **CLI 命令行**:框架已搭建,具体功能开发中
**建议使用方式:** 直接导入 Python 模块使用(见下方"Python API 使用"章节)
---
## 快导十步流程说明
### 简略版(默认)
**用户:**"快导的十步分别是什么?"
**我:**快导系列包含10个步骤:
| 步骤 | 名称 | 一句话说明 |
|:---:|:---|:---|
| 1 | 搜索平台爆款 | 搜你行业的爆款视频参考 |
| 2 | 读取平台规则 | 读取平台运营规范 |
| 3 | 搜外网爆款 | 搜TikTok/YouTube灵感 |
| 4 | 查重复 | 避开已有脚本主题 |
| 5 | 查格式 | 扫描Excel实际格式 |
| 6 | 写脚本 | 生成差异化脚本 |
| 7 | 检查 | 检查脚本合理性 |
| 8 | 保存 | 写入Excel文案库 |
| 9 | 验证 | 验证写入结果 |
| 10 | 出报告 | 生成执行报告 |
完整执行就是走这10步,也可以单独执行某一步。
---
### 详细版
**用户:**"详细说说每一步做什么?"
**我:**
**第1步:搜索平台爆款**
- 用你配置的关键词搜小红书/抖音/视频号
- 随机抽3个关键词,各搜20条
- 最终选定4个最优爆款
**第2步:读取平台规则**
- 读取平台运营规范(时长、标题、违规红线等)
- 输出7项关键规则摘要
**第3步:搜外网爆款**
- 搜TikTok/YouTube找灵感
- **三种模式:**
1. 使用 `external_keywords` 配置的关键词搜索
2. 使用 `default_keywords` 预设默认关键词搜索
3. `auto_collect=true` 时自动获取当前热门视频(无需关键词)
- 最终选定1个外网爆款
**配置方式:** 在 `user_config.json` 中配置 `external_search`
**第4步:查重复**
- 读取你文案库已有脚本
- 确定需避开的主题方向
**第5步:查格式**
- 扫描Excel实际格式(字体、颜色、边框等)
- 记录格式参数,确保写入时格式一致
**第6步:写脚本**
- 基于第1、3步的爆款,生成差异化脚本
- 每个脚本包含完整的分镜、台词、BGM等14列
**第7步:检查**
- 检查时长、内容、场景、台词、格式等合理性
- 发现问题直接修改
**第8步:保存**
- 将脚本写入Excel文案库
- 严格按第5步记录的格式写入
**第9步:验证**
- 用Python读取Excel验证
- 检查格式、内容、位置正确性
**第10步:出报告**
- 生成Markdown执行报告
- 保存到飞书知识库(如配置了空间ID)
---
## 安装
### 方式1:通过 clawhub 安装(推荐)
```bash
# 安装 skill
clawhub install kd
# 进入 skill 目录
cd ~/.agents/skills/kd
# 安装依赖
pip install -r requirements.txt
```
### 方式2:手动下载
```bash
# 下载到本地
cd ~/.agents/skills/
git clone <repository-url> kd
# 安装依赖
cd kd
pip install -r requirements.txt
```
---
## Python API 使用(推荐)
### 快导系列 - 生成脚本
```python
from kd.scripts import ScriptGenerator, ExcelManager, ConfigManager
# 1. 加载配置
config = ConfigManager()
config.load_platform('xiaohongshu') # 或 'douyin', 'shipinhao'
# 2. 生成脚本
generator = ScriptGenerator(platform='xiaohongshu')
scripts = generator.generate(
trending_titles=['爆款标题1', '爆款标题2'], # 子任务1结果
rules_summary={...}, # 子任务2结果
avoid_themes=['已有主题1'], # 子任务4结果
count=5
)
# 3. 写入文案库
with ExcelManager('path/to/文案库.xlsx') as em:
format_info = em.scan_format() # 子任务5
for script in scripts:
em.append_script(script) # 子任务8
```
### 规则更新系列
```python
from kd.scripts import ConfigManager
# 读取平台规则(需用户自行创建规则文档)
config = ConfigManager()
rules = config.load_rules('xiaohongshu')
```
---
## Shortcuts(快速使用参考)
| 用户说 | 我执行 | 对应模块 |
|:---|:---|:---|
| "帮我配置小红书关键词" | 引导配置 platforms.json | config_manager |
| "设置文案库路径" | 引导配置 user_config.json | config_manager |
| "生成小红书规则" | 创建 xiaohongshu_rules.md | rules_update |
| "快导小红书,生成5条" | 执行完整10步流程 | 快导系列 |
| "快导第6步,生成脚本" | 执行单步子任务6 | step6_generate |
| "检查文案库格式" | 执行 step5 格式检查 | excel_manager |
| "验证写入结果" | 执行 step9 验证 | excel_manager |
### 额外问答
**用户:**"快导的十步分别是什么?"
**我:**(提供简略版或详细版,见上方"快导十步流程说明"章节)
---
## 执行模式
### 自动模式(默认)
**说明:** 连续执行所有步骤,完成后一次性返回结果。
```python
from kd.scripts import WorkflowManager
# 创建实例
workflow = WorkflowManager('xiaohongshu')
# 执行完整流程
result = workflow.run_full()
if result['success']:
print(f"✅ 完成!生成 {len(result['completed_steps'])} 步")
else:
print(f"❌ 失败:{result.get('error', '未知错误')}")
```
---
### 交互模式
**说明:** 每完成一步后暂停,等待用户确认后再继续。
```python
from kd.scripts import WorkflowManager
# 启用交互模式
workflow = WorkflowManager('xiaohongshu', interactive=True)
# 执行
result = workflow.run_full()
# 如果暂停了,提示用户
if result.get('paused'):
print(result['message'])
# 输出示例: "Step 1 完成,是否继续执行 Step 2?"
# 用户回复后继续
workflow.resume()
```
**用户交互示例:**
| Agent 回复 | 用户回复 | 说明 |
|:---|:---|:---|
| "Step 1 完成,选定4个爆款。继续?" | "继续" / "跳过" / "停止" | 选择继续执行、跳过下一步、或停止 |
| "Step 6 完成,生成5条脚本。继续?" | "继续" | 进入检查和保存阶段 |
---
### 回调模式(即时回复)
**说明:** 每完成一步立即回调,适合需要即时向用户展示进度的场景。
```python
from kd.scripts import WorkflowManager
def on_step_complete(step_num, result, should_pause):
"""每步完成后的回调"""
print(f"✅ Step {step_num} 完成")
# 如果是关键步骤,展示更多信息
if step_num == 1:
trending = result.get('final_selection', [])
print(f" 选定爆款: {len(trending)} 个")
elif step_num == 6:
scripts = result.get('scripts', [])
print(f" 生成脚本: {len(scripts)} 条")
# 如果需要暂停,提示用户
if should_pause:
print(" 输入 '继续' 执行下一步")
# 执行,使用回调
workflow = WorkflowManager('xiaohongshu')
result = workflow.run_full(callback=on_step_complete)
```
---
## 详细命令示例
### 示例1:完整快导流程
**用户:**"快导小红书,生成5条脚本"
**我:**执行完整10步流程:
```python
from kd.scripts import ConfigManager, ScriptGenerator, ExcelManager
# Step 1-5: 准备阶段
config = ConfigManager()
platform_config = config.load_platform('xiaohongshu')
# Step 6: 生成脚本
generator = ScriptGenerator(platform='xiaohongshu')
scripts = generator.generate(
trending_titles=['爆款标题1', '爆款标题2', '爆款标题3', '爆款标题4'],
external_title='外网爆款标题',
rules_summary={...},
avoid_themes=['已有主题'],
count=5
)
# Step 7: 合理性检查(自动生成时已完成)
# Step 8-9: 写入并验证
with ExcelManager('path/to/文案库.xlsx') as em:
for script in scripts:
em.append_script(script)
em.validate_write()
# Step 10: 生成报告
print(f"✅ 成功生成 {len(scripts)} 条脚本")
```
---
### 示例2:单步执行
**用户:**"快导第6步,生成脚本"
**我:**仅执行脚本生成:
```python
from kd.scripts import ScriptGenerator
generator = ScriptGenerator(platform='xiaohongshu')
scripts = generator.generate(
trending_titles=[...], # 用户需提供
count=5
)
```
---
### 示例3:检查文案库格式
**用户:**"检查文案库格式"
**我:**扫描Excel格式:
```python
from kd.scripts import ExcelManager
with ExcelManager('path/to/文案库.xlsx') as em:
format_info = em.scan_format()
print(format_info)
```
---
### 示例4:配置搜索关键词
**用户:**"帮我配置小红书关键词"
**我:**更新 platforms.json:
```python
from kd.scripts import ConfigManager
config = ConfigManager()
config.set_platform_keywords(
platform='xiaohongshu',
keywords=['农家乐', '采摘', '成都周边'],
theme_direction=['采摘攻略', '亲子游玩'] # 可选
)
```
---
## 常见错误处理
### 配置类错误
**错误1:关键词未配置**
- **用户:**"快导小红书"
- **报错:**`"keywords is empty"`
- **我:**"先告诉我你的搜索关键词,例如:农家乐、采摘、成都周边..."
**错误2:文案库路径未设置**
- **用户:**"开始生成"
- **报错:**`"copy_library_path not set"`
- **我:**"请告诉我文案库保存到哪里,例如:F:\\vlog\\JT\\idea\\小红书文案库.xlsx"
---
### Step 1 错误:搜索平台爆款
**错误1:没有搜索 skill**
- **报错:**`"web_search skill not available"`
- **我:**"当前没有配置搜索功能,请手动提供4个爆款标题,或者安装 web_search skill"
**错误2:搜索返回空结果**
- **报错:**`"no trending videos found"`
- **我:**"用你配置的关键词没搜到爆款,要不要换几个关键词试试?"
---
### Step 2 错误:读取平台规则
**错误1:规则文档不存在**
- **报错:**`"rules file not found: xiaohongshu_rules.md"`
- **我:**"规则文档不存在,要我帮你生成默认规则吗?"
---
### Step 3 错误:搜索外网爆款
**错误1:没有外网搜索 skill**
- **报错:**`"external search not available"`
- **我:**"当前无法搜索外网爆款(需要相应skill),可以继续用平台爆款生成,或者你提供一个外网参考链接?"
**错误2:网络问题**
- **报错:**`"network error: cannot access TikTok/YouTube"`
- **我:**"网络访问受限,搜不到外网爆款。这一步可以跳过,继续用平台爆款生成脚本?"
**错误3:外网关键词未配置**
- **报错:**`"external_keywords is empty"`
- **我:**"没有配置外网搜索关键词,告诉我几个英文关键词,例如:food, farm, family?或者直接跳过这步?"
---
### Step 4 错误:同质化检查
**错误1:文案库文件不存在**
- **报错:**`"copy library file not found"`
- **我:**"文案库文件还没创建,第一次使用吧?直接生成新脚本,不用查重复了"
---
### Step 5 错误:格式检查
**错误1:Excel 格式异常**
- **报错:**`"cannot read Excel format: file corrupted"`
- **我:**"Excel文件读取失败,可能是格式问题。要不要我创建一个新的标准格式文案库?"
---
### Step 6 错误:生成脚本
**错误1:生成超时**
- **报错:**`"script generation timeout"`
- **我:**"生成脚本超时了,可能是网络问题。要不要减少生成数量再试?"
**错误2:内容不合规**
- **报错:**`"generated content violates platform rules"`
- **我:**"生成的内容可能不符合平台规范,需要我调整后再生成吗?"
---
### Step 7 错误:合理性检查
**错误1:时长不达标**
- **报错:**`"total duration mismatch: expected 120-180s, got 90s"`
- **我:**"总时长不够,需要增加分镜或延长单镜时长,要我自动调整吗?"
**错误2:内容同质化**
- **报错:**`"scripts are not differentiated enough"`
- **我:**"脚本差异化不够,重新生成一批?"
---
### Step 8 错误:更新文案库
**错误1:写入失败**
- **报错:**`"failed to write to Excel: permission denied"`
- **我:**"Excel文件被占用了(可能你正在打开),关闭Excel后再试?"
**错误2:格式不匹配**
- **报错:**`"format mismatch with step5 scan result"`
- **我:**"写入格式和扫描的格式不一致,需要我重新扫描再写入?"
---
### Step 9 错误:全面检查对比
**错误1:内容丢失**
- **报错:**`"content validation failed: missing columns"`
- **我:**"验证发现内容有缺失,回退到Step 8重新写入?"
**错误2:位置错误**
- **报错:**`"position mismatch: expected row 15, found row 14"`
- **我:**"写入位置不对,检查后发现偏移了一行,需要修正?"
---
### Step 10 错误:提交报告
**错误1:飞书空间未配置**
- **报错:**`"report_space_id not set"`
- **我:**"没有配置飞书空间ID,报告保存到本地目录可以吗?"
**错误2:保存失败**
- **报错:**`"failed to upload to lark wiki"`
- **我:**"飞书上传失败了,报告已保存到本地,需要手动上传吗?"
---
## v1.1.0 新功能说明(2026年4月更新)
### 1. 智能时长计算(auto_adjust)
**功能:** 根据台词字数自动计算并调整分镜时长
**配置:**
```json
// config/platforms.json
{
"duration_policy": {
"xiaohongshu": {
"target_duration": {"min": 120, "max": 180},
"segment_duration": {"min": 6, "max": 15},
"auto_adjust": true, // 开启自动调整
"calculation_rules": {
"speech_rate": 0.25, // 0.25秒/字
"motion_base": {...}, // 运镜基础时长
"buffer_seconds": 1 // 缓冲时间
}
}
}
}
```
**说明:**
- `auto_adjust: true` → 系统自动计算(默认)
- `auto_adjust: false` → 完全手动控制时间段
- 台词时长 = 字数 × speech_rate(0.25秒/字)
- 总时长必须 ≥ 台词时长的60%
---
### 2. 活动配置管理(Activity Manager)
**功能:** 自动匹配脚本关联活动
**配置方式1:自然语言**
```
用户:"添加活动:春节特惠,关键词:春节、过年、团圆"
Agent:✓ 活动"春节特惠"已添加,包含3个关键词
用户:"列出所有活动"
Agent:当前有2个活动:春节特惠、五一采摘节
用户:"删除活动:春节特惠"
Agent:✓ 已删除
```
**配置方式2:直接编辑JSON**
```json
// config/platforms.json
{
"activities": {
"list": [
{
"id": "spring_festival",
"name": "春节特惠",
"keywords": ["春节", "过年", "团圆", "年夜饭"],
"applicable_platforms": ["xiaohongshu", "douyin"]
},
{
"id": "labor_day",
"name": "五一采摘节",
"keywords": ["五一", "采摘", "出游"],
"applicable_platforms": ["xiaohongshu", "douyin", "shipinhao"]
}
],
"default": "日常推广"
}
}
```
**自动匹配规则:**
1. 关键词匹配 → 脚本内容包含活动关键词
2. 时间匹配 → 当前日期在活动有效期内
3. 平台匹配 → 活动适用于当前平台
**无匹配时:** 自动填写"日常推广"
---
### 3. BGM详细生成
**功能:** 生成具体的音乐名称、风格和使用时机
**配置:**
```json
// config/platforms.json
{
"bgm_library": {
"categories": {
"food_display": {
"name": "美食展示",
"tracks": [
{"name": "《小美满》周深", "style": "治愈轻快", "suitable": "全程"}
]
},
"emotional_narrative": {
"name": "情感叙事",
"tracks": [...]
}
}
}
}
```
**输出格式:**
```
I列(推荐音乐/BGM):
音乐名:《小美满》周深
风格:治愈轻快
使用时机:全程
```
---
### 4. Excel行高统一100
**功能:** 数据行统一设置为100,标题行20.4
**配置:**
```json
// config/platforms.json
{
"global_settings": {
"row_height": {
"title_row": 20.4,
"data_row": 100
}
}
}
```
---
### 5. 外网搜索自动收集(auto_collect)
**功能:** `external_keywords` 为空时,自动获取热门视频
**配置:**
```json
// config/user_config.json
{
"external_search": {
"auto_collect": true, // 开启自动收集
"collect_count": 20,
"platforms": ["TikTok", "YouTube"],
"default_keywords": [] // 为空时自动获取
}
}
```
**三种使用场景:**
1. **手动配置** → `external_keywords: ["food", "cooking"]` → 使用这些关键词
2. **默认预设** → `default_keywords: ["viral food"]` → 使用预设关键词
3. **完全自动** → 两者都为空 + `auto_collect: true` → 自动获取热门
---
## 参考文档
### references/ 目录说明
`references/` 目录存放快导运行过程中生成的参考文档:
| 子目录 | 用途 | 生成方式 |
|:---|:---|:---|
| `platform_rules/` | 平台运营规则文档 | 规则更新系列生成 |
| `trending/` | 平台爆款缓存 | 快导系列自动保存 |
| `external/` | 外网爆款参考 | 快导系列自动保存 |
**用户:**"references 目录是干什么的?"
**我:**"这是存放规则文档和数据缓存的地方。platform_rules/ 存平台规则,trending/ 存搜到的爆款,external/ 存外网参考。具体说明可以看 references/README.md"
---
### 规则文档位置
**用户:**"平台规则存在哪里?"
**我:**"默认保存在 `references/platform_rules/` 目录,包含:
- `xiaohongshu_rules.md` - 小红书规则
- `douyin_rules.md` - 抖音规则
- `shipinhao_rules.md` - 视频号规则
可以修改保存路径:
```python
config.set_rules_path("你的自定义路径")
```"
---
## CLI 命令行(开发中)
CLI 功能当前仅作为框架存在,具体功能正在开发中。
```bash
# 查看帮助(可用)
kd --help
# 以下命令框架存在,功能开发中:
kd run --platform xiaohongshu # 执行完整快导系列
kd step --platform xiaohongshu --step 6 # 执行单步
kd rules --platform xiaohongshu # 规则更新
kd config show # 配置管理
```
**当前推荐使用 Python API 直接调用。**
---
| 软件 | 用途 | 安装命令 |
|:---|:---|:---|
| **Python 3.7+** | 运行脚本 | [官网下载](https://www.python.org/downloads/) |
| **lark-cli** | 飞书API操作 | `npm install -g @larksuite/cli` |
### Python依赖包
| 包名 | 用途 | 安装命令 |
|:---|:---|:---|
| **openpyxl** | Excel文件操作 | `pip install openpyxl` |
### 自动安装
如检测到未安装依赖,系统将提示并协助安装:
```bash
# 检查并安装Python依赖
kd setup --check-deps
# 自动安装(需用户确认)
kd setup --install-deps
```
**注意:** 首次使用前请确保已安装Python和lark-cli。
## 系统支持
| 系统 | 支持状态 | 测试脚本位置 |
|:---|:---:|:---|
| **Windows** | ✅ 默认支持 | `tests/` |
| **macOS** | ✅ 支持 | `tests/mac/` |
| **Linux** | ✅ 支持 | `tests/linux/` |
**注意:** Windows版本默认已配置UTF-8编码支持。macOS和Linux用户使用对应目录下的测试脚本。
## 软件依赖
### 必需软件
| 软件 | 用途 | 安装命令 |
|:---|:---|:---|
| **Python 3.7+** | 运行脚本 | [官网下载](https://www.python.org/downloads/) |
| **lark-cli** | 飞书API操作 | `npm install -g @larksuite/cli` |
### Python依赖包
| 包名 | 用途 | 安装命令 |
|:---|:---|:---|
| **openpyxl** | Excel文件操作 | `pip install openpyxl` |
### 自动安装
如检测到未安装依赖,系统将提示并协助安装:
```bash
# 检查并安装Python依赖
kd setup --check-deps
# 自动安装(需用户确认)
kd setup --install-deps
```
**注意:** 首次使用前请确保已安装Python和lark-cli。
## 功能模块
| 模块 | 说明 | 触发命令 |
|:---|:---|:---|
| **快导系列** | 生成多平台短视频脚本(10步流程) | `kd run --platform <平台>` |
| **规则更新系列** | 更新平台运营规则文档 | `kd rules --platform <平台>` |
## 支持平台
| 平台 | 标识 | 总时长 | 分镜数 |
|:---|:---|:---:|:---:|
| 小红书 | `xiaohongshu` | 2-3分钟 | 自动计算 |
| 抖音 | `douyin` | 1分钟内 | 自动计算 |
| 视频号 | `shipinhao` | 1-3分钟 | 自动计算 |
**平台基础信息**(可修改:`config/platforms.json`)
- 小红书:女性、种草决策、攻略型
- 抖音:年轻人、公域流量、快节奏
- 视频号:中老年、私域社交、温情叙事
## 用户配置变量说明
### 配置变量总览
| 变量名 | 用途 | 是否必需 | 为空时的处理 |
|:---|:---|:---:|:---|
| `copy_library_path` | 文案库Excel路径 | ✅ 必需 | 提示用户配置 |
| `report_space_id` | 飞书知识库空间ID | ✅ 必需 | 提示用户配置 |
| `rules_path` | 规则文档保存路径 | ⚠️ 建议配置 | 使用默认值 `references/platform_rules/` |
| `platform_keywords` | 平台搜索关键词池 | ⚠️ 建议配置 | 子任务1终止,提示配置 |
| `external_keywords` | 外网搜索关键词池 | ❌ 可选 | 子任务3跳过(不影响主流程) |
| `theme_direction` | 内容主题方向 | ❌ 可选 | 脚本生成时不限制主题 |
### 变量详细说明
#### 1. copy_library_path(文案库路径)
**用途:** 指定文案库Excel文件的保存位置
**配置方式:**
```bash
# 方式1:命令行
kd config set copy_library_path "F:\\vlog\\JT\\idea\\xiaohongshu_scripts.xlsx"
# 方式2:编辑配置文件
# 编辑 config/user_config.json
{
"copy_library_path": "F:\\vlog\\JT\\idea\\xiaohongshu_scripts.xlsx"
}
# 方式3:Python脚本
from kd import ConfigManager
config = ConfigManager()
config.set_copy_library_path("F:\\vlog\\JT\\idea\\xiaohongshu_scripts.xlsx")
```
**文件不存在时的处理:**
- 如文件不存在,自动按默认格式创建新的Excel文件
- 默认格式包含14列(A-N),带标题行格式
- 提示用户:"文案库不存在,已按默认格式创建"
#### 2. report_space_id(飞书空间ID)
**用途:** 指定执行报告保存的飞书知识库空间
**配置方式:**
```bash
kd config set report_space_id "你的空间ID"
```
**获取方式:**
1. 打开飞书知识库
2. 进入目标空间
3. 空间设置 → 复制空间ID
**为空时的处理:**
- 子任务10提示用户配置
- 可选择保存到本地目录:`reports/`
#### 3. rules_path(规则文档路径)
**用途:** 指定平台规则文档的保存位置
**配置方式:**
```bash
kd config set rules_path "F:\\我的规则\\"
```
**为空时的处理:**
- 使用默认值:`references/platform_rules/`
**规则文档不存在时的处理:**
- 子任务2提示用户先生成规则
- 提供生成命令:`kd rules --platform xiaohongshu`
#### 4. platform_keywords(平台关键词池)
**用途:** 子任务1搜索目标平台爆款时使用
**配置方式:**
```bash
# 方式1:命令行
kd config set-keywords --platform xiaohongshu \
--keywords "美食,探店,农家菜,采摘,生活,乡村,慢生活,周末"
# 方式2:编辑配置文件
# 编辑 config/platforms.json
{
"platforms": {
"xiaohongshu": {
"keywords": ["美食", "探店", "农家菜", "采摘"]
}
}
}
```
**为空时的处理:**
- 子任务1输出警告:"关键词池为空,无法进行搜索"
- 终止子任务1
- 提示用户配置关键词池
#### 5. external_keywords(外网关键词池)
**用途:** 子任务3搜索外网平台爆款时使用(可选)
**配置方式:**
```bash
kd config set-external-keywords \
--keywords "food,cooking,life,vlog,story,family,memory"
```
**为空时的处理:**
- 子任务3输出提示:"外网关键词池为空,跳过外网搜索"
- 跳过子任务3,直接执行子任务4
- 不影响主流程执行
#### 6. theme_direction(内容主题方向)
**用途:** 指导脚本生成的主题方向(可选)
**配置方式:**
```bash
# 编辑 config/platforms.json
{
"platforms": {
"xiaohongshu": {
"theme_direction": ["采摘攻略", "美食探店", "慢生活体验"]
}
}
}
```
**为空时的处理:**
- 脚本生成时不限制主题方向
- 完全基于爆款和外网参考生成
### 首次使用配置流程
```bash
# 步骤1:设置文案库路径(必需)
kd config set copy_library_path "你的文案库路径"
# 步骤2:设置飞书空间ID(必需)
kd config set report_space_id "你的空间ID"
# 步骤3:配置平台关键词池(建议)
kd config set-keywords --platform xiaohongshu \
--keywords "关键词1,关键词2,关键词3"
# 步骤4:配置外网关键词池(可选)
kd config set-external-keywords \
--keywords "food,cooking,life"
# 步骤5:验证配置
kd config validate
```
### 配置验证命令
```bash
# 验证所有配置
kd config validate
# 验证特定平台
kd config validate --platform xiaohongshu
# 查看当前配置
kd config show
# 查看特定平台配置
kd config show --platform xiaohongshu
```
## 首次使用配置
### 1. 设置文案库路径
```bash
kd config set copy_library_path "你的文案库目录"
# 示例:kd config set copy_library_path "D:\\我的文案\\"
```
### 2. 设置飞书报告空间
```bash
kd config set report_space_id "你的空间ID"
# 空间ID在飞书知识库设置中获取
```
### 3. 配置搜索关键词(可选)
编辑 `config/platforms.json` 中对应平台的 `keywords` 字段。
### 4. 设置规则文档路径
```bash
kd config set rules_path "你的规则文档目录"
# 示例:kd config set rules_path "D:\\我的规则\\"
# 默认:references/platform_rules/
```
### 5. 配置搜索关键词(可选)
编辑 `config/platforms.json` 中对应平台的 `keywords` 字段。
### 6. 设置行业场景(可选)
编辑 `config/platforms.json` 中 `industry_templates` 添加你的行业。
## 快速开始
### 执行完整任务链(10步)
```bash
# 执行小红书任务
kd run --platform xiaohongshu --duration "2-3min"
# 执行抖音任务
kd run --platform douyin --duration "1min"
```
### 执行单个子任务
```bash
# 仅执行子任务6:生成脚本
kd step --platform xiaohongshu --step 6
# 仅执行子任务7:合理性检查
kd step --platform xiaohongshu --step 7
```
### 批量生成脚本(仅子任务6)
```python
from kd import ScriptGenerator
gen = ScriptGenerator(
platform="xiaohongshu",
duration="2-3min",
keywords=["采摘", "农家菜", "慢生活"]
)
scripts = gen.generate(count=5)
```
## 完整10步流程
| 步骤 | 名称 | 说明 | 输出 |
|:---:|:---|:---|:---|
| 1 | 搜索目标平台爆款 | 随机抽取关键词,搜索爆款 | 选定的爆款列表 |
| 2 | 读取平台规则 | 读取平台运营规则文档 | 关键规则摘要 |
| 3 | 搜索外网平台 | 搜索TikTok/YouTube爆款 | 外网爆款参考 |
| 4 | 同质化检查 | 检查避免与已有脚本重复 | 需避开主题清单 |
| 5 | 格式检查 | 现场读取Excel实际格式 | 格式确认清单 |
| 6 | 生成脚本 | 生成差异化脚本 | 完整脚本 |
| 7 | 合理性检查 | 多维度检查并修改 | 检查清单+修改记录 |
| 8 | 更新文案库 | 脚本写入Excel | 写入确认清单 |
| 9 | 全面检查对比 | Python验证写入结果 | 验证报告 |
| 10 | 提交报告 | 生成并保存执行报告 | Markdown报告 |
**注意:**
- 步骤1的关键词池:用户配置(`config/platforms.json`)
- 步骤2的平台规则:用户通过"规则更新系列"生成
- 步骤4的已有脚本:从用户配置的文案库路径读取
## 灵动参数配置
### 核心参数
| 参数 | 默认值 | 说明 | 配置位置 |
|:---|:---:|:---|:---|
| `total_duration` | "2-3min" | 总时长 | 命令行参数 |
| `segment_duration` | "3-12s" | 单分镜时长范围 | `config/platforms.json` |
| `segments_count` | 自动计算 | 分镜数量 | 根据时长自动计算 |
### 分镜时长分配模式
根据总时长自动分配,遵循"开头快、中间稳、结尾慢"原则:
**示例(小红书,2-3分钟):**
- 开场:3-5秒(快速吸引)
- 主体:6-10秒(内容展开)
- 结尾:10-12秒(升华收尾)
### 内容详细度参数
| 参数 | 默认值 | 说明 |
|:---|:---:|:---|
| `shot_min_chars` | 20 | C列-镜头最少字数 |
| `movement_min_chars` | 15 | D列-运镜最少字数 |
| `technique_min_chars` | 15 | E列-技巧最少字数 |
| `scene_min_chars` | 20 | F列-画面最少字数 |
| `line_min_chars` | 30 | G列-台词最少字数 |
| `sound_min_chars` | 15 | H列-音效最少字数 |
**修改位置:** `config/platforms.json` → `content_requirements.columns`
## 文案库文件
**路径设置:** 首次使用需配置
```
你的文案库目录/
├── 小红书文案库.xlsx
├── 抖音文案库.xlsx
└── 视频号文案库.xlsx
```
**配置命令:**
```bash
kd config set copy_library_path "路径"
```
## 飞书报告保存
**空间ID设置:** 首次使用需配置
- **配置命令:** `kd config set report_space_id "你的空间ID"`
- **命名格式:** `YYYY-MM-DD-快导-平台名称`
- **示例:** `2026-04-22-快导-小红书`
**空间ID获取:** 飞书知识库 → 空间设置 → 复制空间ID
## 规则更新系列
**用途:** 生成/更新平台运营规则文档
```bash
# 更新小红书规则
kd rules --platform xiaohongshu
# 更新抖音规则
kd rules --platform douyin
# 更新视频号规则
kd rules --platform shipinhao
```
**规则文档位置:** `references/platform_rules/`
**初始状态:** 已提供3个平台的规则模板,用户可根据实际运营经验更新
### 规则文档说明
每个平台的规则文档包含:
- 平台基础信息(用户画像、内容风格)
- 核心规则(完播率、原创要求、违规红线)
- 文案规范(标题要求、字数要求、语言风格)
- 拍摄建议(画面要求、运镜技巧)
**查看规则:**
```bash
# 查看小红书规则
kd refs show --platform xiaohongshu
# 编辑规则(直接编辑文件)
nano references/platform_rules/xiaohongshu_rules.md
```
## 错误处理
- **单步超时:** 5分钟
- **出错处理:** 立即停止,通知用户错误详情
- **数据安全:** 写入前自动备份
## 跨平台使用说明
### 不同系统测试脚本使用
根据您的操作系统选择对应的测试脚本(文件名与scripts目录核心模块对应):
| 核心模块 | Windows | macOS | Linux |
|:---|:---|:---|:---|
| `config_manager.py` | `config_manager_win.py` | `config_manager_mac.py` | `config_manager_linux.py` |
| `excel_manager.py` | `excel_manager_win.py` | `excel_manager_mac.py` | `excel_manager_linux.py` |
| `format_checker.py` | `format_checker_win.py` | `format_checker_mac.py` | `format_checker_linux.py` |
| `script_generator.py` | `script_generator_win.py` | `script_generator_mac.py` | `script_generator_linux.py` |
#### 使用步骤
**Windows用户(默认):**
```bash
cd tests
python config_manager_win.py
python excel_manager_win.py
```
**macOS用户:**
```bash
cd tests
# 从mac目录复制到根目录
cp mac/config_manager_mac.py .
python config_manager_mac.py
```
**Linux用户:**
```bash
cd tests
# 从linux目录复制到根目录
cp linux/config_manager_linux.py .
python config_manager_linux.py
```
### 目录结构
```
tests/
├── config_manager_win.py # Windows版本(默认)
├── excel_manager_win.py
├── format_checker_win.py
├── script_generator_win.py
├── __init__.py
├── mac/ # macOS版本
│ ├── config_manager_mac.py
│ ├── excel_manager_mac.py
│ ├── format_checker_mac.py
│ └── script_generator_mac.py
└── linux/ # Linux版本
├── config_manager_linux.py
├── excel_manager_linux.py
├── format_checker_linux.py
└── script_generator_linux.py
```
**文件名对应规则:**
- Windows版本: `{模块名}_win.py`
- macOS版本: `{模块名}_mac.py`
- Linux版本: `{模块名}_linux.py`
**使用提示:** 根据系统复制对应版本的测试脚本到根目录即可直接使用。
## 版本历史
| 版本 | 日期 | 更新内容 |
|:---:|:---:|:---|
| 1.0.0 | 2026-04-22 | 初始版本,支持3平台,单次任务模式 |
## 配置文件说明
| 文件 | 用途 | 可修改内容 |
|:---|:---|:---|
| `config/platforms.json` | 平台参数 | 时长、字数、关键词、行业模板 |
| `config/workflow.json` | 流程配置 | 步骤定义、超时设置 |
| `config/templates.json` | 生成模板 | 提示词、时长分配模式 |
## 更多帮助
```bash
# 查看帮助
kd --help
# 查看平台配置
kd config show --platform xiaohongshu
# 验证配置
kd config validate
```
---
## 快速故障排查
| 问题 | 可能原因 | 解决方案 |
|:---|:---|:---|
| 导入错误 `ModuleNotFoundError: No module named 'openpyxl'` | 未安装依赖 | `pip install -r requirements.txt` 或 `pip install openpyxl` |
| 导入错误 `ModuleNotFoundError: No module named 'scripts'` | Python路径问题 | 确保在 `kd/` 根目录运行,或添加 `sys.path.insert(0, r'C:\\Users\\kkk49\\.agents\\skills\\kd')` |
| Excel写入失败 `Permission denied` | Excel文件被占用 | 关闭Excel后再重试 |
| Excel写入失败 `FileNotFoundError` | 目录不存在 | 确保路径存在,或让程序自动创建 |
| 配置未生效 `KeyError: 'copy_library_path'` | 配置文件未初始化 | 运行配置设置命令或检查 `config/user_config.json` 是否存在 |
| 飞书上传失败 `Permission denied` | 权限未开通 | 运行 `python scripts/feishu_permission_helper.py` 开通权限 |
| 飞书上传失败 `Space not found` | 空间ID错误 | 检查 `config/user_config.json` 中的 `report_space_id` 是否正确 |
| 搜索超时 `TimeoutError` | 网络不稳定 | 检查网络连接,或跳过搜索手动提供爆款标题 |
| 生成脚本为空 `scripts is empty` | 提示词参数缺失 | 检查 `config/templates.json` 和 `prompts/` 目录下模板文件是否完整 |
| 格式检查失败 `Format mismatch` | Excel格式变更 | 重新执行 Step 5 格式扫描 |
| 中文乱码 | 编码问题(Windows) | 使用 Python 脚本读取文件,而非直接查看 |
| Python版本不兼容 | Python < 3.7 | 升级到 Python 3.7+ |
### Windows 用户特别提醒
1. **路径分隔符**:使用双反斜杠 `\\` 或原始字符串 `r'C:\path'`
```python
# ❌ 错误
path = "C:\\Users\\name\\file.xlsx"
# ✅ 正确
path = r"C:\\Users\\name\\file.xlsx"
path = "C:\\\\Users\\\\name\\\\file.xlsx"
```
2. **中文文件名**:PowerShell 可能显示乱码,使用 Python 脚本读取
```python
import os
files = os.listdir(r"F:\\vlog\\JT\\idea")
for f in files:
print(f) # 正确显示中文
```
3. **Excel被占用**:确保生成脚本时 Excel 文件已关闭
### 调试模式
如需详细输出调试信息,在 Python 代码中添加:
```python
import logging
logging.basicConfig(level=logging.DEBUG)
```
---
## 活动配置(v1.1.0新增)
快导支持通过自然语言管理活动,用于自动匹配脚本关联活动。
### 配置方式
#### 方式1:自然语言命令(推荐)
```python
from scripts.activity_manager import ActivityManager
manager = ActivityManager()
# 添加活动
manager.execute_command(
manager.parse_natural_language("添加活动:春节特惠,关键词:春节、过年、团圆")
)
# 列出所有活动
manager.execute_command(
manager.parse_natural_language("列出所有活动")
)
# 查询活动
manager.execute_command(
manager.parse_natural_language("查询活动:春节")
)
# 删除活动
manager.execute_command(
manager.parse_natural_language("删除活动:春节特惠")
)
```
#### 方式2:直接编辑配置文件
编辑 `config/platforms.json`,在 `activities` 部分添加:
```json
"activities": {
"list": [
{
"id": "act_1",
"name": "春节特惠",
"keywords": ["春节", "过年", "团圆"],
"applicable_platforms": ["xiaohongshu", "douyin", "shipinhao", "pyq"],
"note": "春节期间推广活动"
},
{
"id": "act_2",
"name": "枇杷采摘季",
"keywords": ["枇杷", "采摘", "太平镇"],
"applicable_platforms": ["xiaohongshu", "douyin"],
"note": "5-6月枇杷采摘活动"
}
],
"user_configured": true
}
```
### 自动匹配规则
生成脚本时,系统会根据脚本主题自动匹配活动:
- 活动名称匹配:+10分
- 关键词匹配:每个+5分
- 平台不匹配:0分(排除)
得分最高的活动会被自动填入Excel的L列(关联活动)。
### 默认显示
如未配置任何活动,L列显示:
```
日常推广,当前未设置activities
```
---
## 联系我们
如有问题或建议,欢迎交流:
- **微信号:** huitouyoujianta
- **用途:** 快导(KD)技能使用咨询、功能建议、技术交流
欢迎加微信交流学习!
FILE:references/platform_rules/README.md
# 平台规则文档索引
本目录存放各平台的运营规则文档,用于快导系列任务执行时的规则参考。
---
## ⚠️ 初始状态
**本目录初始为空,没有预设规则文档。**
规则文档需要用户自行创建或生成。
---
## 规则文档位置
| 平台 | 文件名 |
|:---|:---|
| 小红书 | `xiaohongshu_rules.md` |
| 抖音 | `douyin_rules.md` |
| 视频号 | `shipinhao_rules.md` |
---
## 规则文档内容建议
规则文档建议包含以下内容:
1. **时长要求** - 总时长、分镜时长、分镜数量
2. **核心指标** - 完播率、点赞、评论、转发、收藏优先级
3. **引流限制** - 平台允许的引流方式
4. **原创要求** - 原创内容占比、真人出镜等
5. **互动率要求** - 冷启动期互动率标准
6. **违规红线** - 禁止内容、敏感话题
7. **文案要求** - 标题规则、内容风格、标签建议
---
## 使用方式
快导系列任务会自动读取本目录的规则文档(如存在):
```
子任务2:读取平台规则
↓
检查 references/platform_rules/<platform>_rules.md 是否存在
↓
如存在:读取并输出7项关键规则摘要
如不存在:提示用户创建规则文档
```
---
## 目录路径
`references/platform_rules/`
---
**注意:** 本目录不包含预设规则,由用户根据实际运营需求自行创建。
FILE:references/README.md
# References 目录说明
本目录用于存放快导(KD) Skill运行过程中生成的参考文档和数据文件。
## 目录结构
```
references/
├── platform_rules/ # 平台运营规则文档
│ ├── xiaohongshu_rules.md
│ ├── douyin_rules.md
│ └── shipinhao_rules.md
├── trending/ # 爆款内容缓存
│ ├── xiaohongshu_trending.json
│ ├── douyin_trending.json
│ └── shipinhao_trending.json
├── external/ # 外网爆款参考
│ └── tiktok_trending.json
└── README.md # 本文件
```
## 子目录说明
### platform_rules/ - 平台运营规则
**用途:** 存放各平台运营规则文档
**生成方式:** 通过 `kd rules` 命令生成/更新
**文件格式:** Markdown文档
**内容示例:**
```markdown
# 小红书平台运营规则(2026年4月)
## 核心规则
- 时长要求: 2-3分钟
- 完播率: > 40%
- 原创要求: > 60%
- 违规红线: 虚假宣传、侵权问题
## 内容建议
- 攻略型内容优先
- 图文结合形式
- 实用价值导向
```
**初始状态:** 空目录,首次运行规则更新系列时自动生成
### trending/ - 爆款内容缓存
**用途:** 缓存搜索到的平台爆款内容
**生成方式:** 快导系列任务执行时自动保存
**文件格式:** JSON
**缓存策略:**
- 保留最近30天的爆款数据
- 超过30天自动清理
- 用于避免重复搜索相同内容
### external/ - 外网爆款参考
**用途:** 存放TikTok/YouTube等平台爆款参考
**生成方式:** 快导系列子任务3执行时保存
**文件格式:** JSON
**使用场景:**
- 获取外网创意灵感
- 参考国际化内容趋势
## 文件命名规范
| 类型 | 命名格式 | 示例 |
|:---|:---|:---|
| 规则文档 | `{platform}_rules.md` | `xiaohongshu_rules.md` |
| 爆款缓存 | `{platform}_trending_{YYYYMMDD}.json` | `douyin_trending_20260422.json` |
| 外网参考 | `tiktok_trending_{YYYYMMDD}.json` | `tiktok_trending_20260422.json` |
## 自动管理
### 数据保留策略
| 数据类型 | 保留时间 | 清理方式 |
|:---|:---:|:---|
| 规则文档 | 永久 | 手动管理 |
| 爆款缓存 | 30天 | 自动清理 |
| 外网参考 | 30天 | 自动清理 |
### 手动清理命令
```bash
# 清理所有过期的缓存文件
kd refs clean --expired
# 清理指定平台的缓存
kd refs clean --platform xiaohongshu
# 查看缓存占用空间
kd refs size
```
## 配置说明
### 规则文档保存路径
默认路径:`references/platform_rules/`
自定义路径:
```bash
kd config set rules_path "你的自定义路径"
```
### 缓存路径
默认路径:`references/trending/` 和 `references/external/`
**注意:** 缓存路径不支持自定义,固定在本目录下
## 备份建议
**重要数据:**
- ✅ 规则文档(建议备份)
- ⚠️ 爆款缓存(可选备份)
- ⚠️ 外网参考(可选备份)
**备份命令:**
```bash
# 备份规则文档
kd refs backup --platform xiaohongshu
# 备份所有数据
kd refs backup --all
```
## 注意事项
1. **不要手动删除** `platform_rules/` 中的规则文档,可能导致任务失败
2. **定期清理** 过期缓存,避免占用过多磁盘空间
3. **规则文档** 建议定期备份,防止误删
## 版本历史
| 版本 | 日期 | 说明 |
|:---:|:---:|:---|
| 1.0.0 | 2026-04-22 | 初始版本,支持3平台规则存储 |
FILE:requirements.txt
# 快导(KD) - Python依赖
# 安装: pip install -r requirements.txt
# Excel文件操作
openpyxl>=3.0.0
# 可选依赖(用于增强功能)
# requests>=2.25.0 # HTTP请求(如需要网络搜索API)
# python-dotenv>=0.19.0 # 环境变量管理
FILE:scripts/activity_manager.py
"""
活动管理器 - 自然语言活动配置管理
支持用户通过自然语言添加、删除、查询活动
"""
import json
import re
from typing import Dict, List, Any, Optional
from pathlib import Path
class ActivityManager:
"""活动配置管理器"""
def __init__(self, config_path: str = None):
"""
初始化活动管理器
Args:
config_path: 配置文件路径,默认为平台配置文件
"""
if config_path:
self.config_path = Path(config_path)
else:
# 默认路径:技能目录下的 config/platforms.json
self.config_path = Path(__file__).parent.parent / 'config' / 'platforms.json'
self.config = self._load_config()
self.activities = self.config.get('activities', {})
self.activity_list = self.activities.get('list', [])
def _load_config(self) -> dict:
"""加载配置文件"""
if not self.config_path.exists():
return {'activities': {'list': []}}
try:
with open(self.config_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"加载配置失败: {e}")
return {'activities': {'list': []}}
def _save_config(self):
"""保存配置到文件"""
try:
# 更新配置
self.config['activities'] = self.activities
# 写入文件
with open(self.config_path, 'w', encoding='utf-8') as f:
json.dump(self.config, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
print(f"保存配置失败: {e}")
return False
def parse_natural_language(self, command: str) -> dict:
"""
解析自然语言命令
支持的命令格式:
- "添加活动:春节特惠,关键词:春节、过年、团圆"
- "删除活动:春节特惠"
- "列出所有活动"
- "查询活动:春节"
Args:
command: 用户输入的自然语言命令
Returns:
解析结果字典
"""
command = command.strip()
# 识别命令类型
patterns = {
'add': r'(?:添加|新建|创建|增加)(?:一个)?(?:活动|推广|促销)[::]?\s*(.+)',
'delete': r'(?:删除|移除|去掉)(?:活动|推广|促销)[::]?\s*(.+)',
'list': r'(?:列出|查看|显示|查询)(?:所有)?(?:的)?(?:活动|推广|促销)',
'query': r'(?:查询|查找|搜索)(?:活动|推广|促销)[::]?\s*(.+)'
}
for action, pattern in patterns.items():
match = re.search(pattern, command, re.IGNORECASE)
if match:
if action in ['add']:
return self._parse_add_command(match.group(1))
elif action == 'delete':
return {'action': 'delete', 'name': match.group(1).strip()}
elif action == 'list':
return {'action': 'list'}
elif action == 'query':
return {'action': 'query', 'name': match.group(1).strip()}
return {'action': 'unknown', 'error': '无法识别命令,请使用"添加活动"、"删除活动"、"列出活动"或"查询活动"'}
def _parse_add_command(self, content: str) -> dict:
"""解析添加命令"""
# 提取活动名称
name_match = re.search(r'^([^,,]+)', content)
if not name_match:
return {'action': 'error', 'error': '无法提取活动名称'}
name = name_match.group(1).strip()
# 提取关键词
keywords = []
keyword_patterns = [
r'(?:关键词|关键字|标签)[::]\s*([^,,]+(?:[,,][^,,]+)*)',
r'(?:关键词|关键字|标签)[:=]\s*([^,,]+(?:[,,][^,,]+)*)'
]
for pattern in keyword_patterns:
kw_match = re.search(pattern, content)
if kw_match:
kw_text = kw_match.group(1)
keywords = [k.strip() for k in re.split(r'[,,、]', kw_text)]
break
# 提取适用平台
platforms = ['xiaohongshu', 'douyin', 'shipinhao', 'pyq'] # 默认全平台
platform_patterns = [
r'(?:适用平台|平台)[::]\s*([^,,]+(?:[,,][^,,]+)*)',
r'(?:适用|用于)[::]\s*([^,,]+(?:[,,][^,,]+)*)'
]
for pattern in platform_patterns:
pf_match = re.search(pattern, content)
if pf_match:
pf_text = pf_match.group(1)
platforms = self._parse_platforms(pf_text)
break
return {
'action': 'add',
'name': name,
'keywords': keywords,
'platforms': platforms
}
def _parse_platforms(self, text: str) -> List[str]:
"""解析平台名称"""
platform_mapping = {
'小红书': 'xiaohongshu',
'抖音': 'douyin',
'视频号': 'shipinhao',
'朋友圈': 'pyq',
'小红': 'xiaohongshu',
'抖': 'douyin',
'视': 'shipinhao',
'pyq': 'pyq',
'小红book': 'xiaohongshu'
}
result = []
for name, code in platform_mapping.items():
if name in text:
result.append(code)
# 如果没匹配到任何平台,默认全平台
return result if result else ['xiaohongshu', 'douyin', 'shipinhao', 'pyq']
def execute_command(self, parsed: dict) -> dict:
"""
执行解析后的命令
Args:
parsed: parse_natural_language 返回的解析结果
Returns:
执行结果
"""
action = parsed.get('action')
if action == 'add':
return self.add_activity(
name=parsed.get('name'),
keywords=parsed.get('keywords', []),
platforms=parsed.get('platforms', ['xiaohongshu', 'douyin', 'shipinhao', 'pyq'])
)
elif action == 'delete':
return self.delete_activity(parsed.get('name'))
elif action == 'list':
return self.list_activities()
elif action == 'query':
return self.query_activity(parsed.get('name'))
else:
return {'success': False, 'error': parsed.get('error', '未知命令')}
def add_activity(self, name: str, keywords: List[str] = None,
platforms: List[str] = None) -> dict:
"""
添加活动
Args:
name: 活动名称
keywords: 关键词列表
platforms: 适用平台列表
Returns:
结果字典
"""
# 检查是否已存在
for act in self.activity_list:
if act.get('name') == name:
return {'success': False, 'error': f'活动"{name}"已存在'}
# 生成ID
activity_id = f"act_{len(self.activity_list) + 1}"
# 创建活动
new_activity = {
'id': activity_id,
'name': name,
'keywords': keywords or [],
'applicable_platforms': platforms or ['xiaohongshu', 'douyin', 'shipinhao', 'pyq'],
'note': ''
}
self.activity_list.append(new_activity)
# 标记为用户已配置
self.activities['user_configured'] = True
# 保存
if self._save_config():
return {
'success': True,
'message': f'活动"{name}"添加成功',
'activity': new_activity
}
else:
return {'success': False, 'error': '保存配置失败'}
def delete_activity(self, name: str) -> dict:
"""
删除活动
Args:
name: 活动名称
Returns:
结果字典
"""
# 查找活动
for i, act in enumerate(self.activity_list):
if act.get('name') == name:
# 删除
del self.activity_list[i]
# 保存
if self._save_config():
return {
'success': True,
'message': f'活动"{name}"已删除'
}
else:
return {'success': False, 'error': '保存配置失败'}
return {'success': False, 'error': f'未找到活动"{name}"'}
def list_activities(self) -> dict:
"""
列出所有活动
Returns:
结果字典
"""
# 过滤掉示例活动
real_activities = [a for a in self.activity_list if a.get('id') != 'example']
return {
'success': True,
'count': len(real_activities),
'activities': real_activities,
'message': f'共 {len(real_activities)} 个活动' if real_activities else '暂无配置的活动'
}
def query_activity(self, name: str) -> dict:
"""
查询活动
Args:
name: 活动名称(支持模糊匹配)
Returns:
结果字典
"""
matches = []
for act in self.activity_list:
if name.lower() in act.get('name', '').lower():
matches.append(act)
return {
'success': True,
'count': len(matches),
'activities': matches,
'message': f'找到 {len(matches)} 个匹配活动' if matches else '未找到匹配活动'
}
def match_activity(self, script_theme: str, platform: str = None) -> Optional[dict]:
"""
根据脚本主题自动匹配活动
Args:
script_theme: 脚本主题
platform: 当前平台(可选)
Returns:
匹配的活动,无匹配则返回 None
"""
if not script_theme:
return None
script_theme = script_theme.lower()
# 过滤掉示例活动
real_activities = [a for a in self.activity_list if a.get('id') != 'example']
if not real_activities:
return None
# 按关键词匹配度排序
scored_activities = []
for activity in real_activities:
score = 0
# 检查活动名称匹配
if activity.get('name', '').lower() in script_theme:
score += 10
# 检查关键词匹配
for keyword in activity.get('keywords', []):
if keyword.lower() in script_theme:
score += 5
# 平台过滤
if platform:
if platform not in activity.get('applicable_platforms', []):
score = 0 # 不匹配的平台得0分
if score > 0:
scored_activities.append((score, activity))
# 返回得分最高的活动
if scored_activities:
scored_activities.sort(key=lambda x: x[0], reverse=True)
return scored_activities[0][1]
return None
def get_default_activity(self) -> str:
"""获取默认活动显示文本"""
real_activities = [a for a in self.activity_list if a.get('id') != 'example']
if real_activities:
return real_activities[0].get('name', '日常推广')
return '日常推广,当前未设置activities'
# 便捷函数
def get_activity_manager(config_path: str = None) -> ActivityManager:
"""获取活动管理器实例"""
return ActivityManager(config_path)
# 测试代码
if __name__ == "__main__":
manager = ActivityManager()
# 测试自然语言解析
test_commands = [
"添加活动:春节特惠,关键词:春节、过年、团圆、年夜饭",
"添加活动:枇杷采摘季,关键词:枇杷、采摘、太平镇",
"列出所有活动",
"查询活动:枇杷",
"删除活动:春节特惠"
]
for cmd in test_commands:
print(f"\n命令: {cmd}")
parsed = manager.parse_natural_language(cmd)
print(f"解析: {parsed}")
if parsed.get('action') in ['add', 'delete', 'list', 'query']:
result = manager.execute_command(parsed)
print(f"执行: {result}")
FILE:scripts/config_manager.py
"""
配置管理器 - 管理用户配置和平台参数
"""
import json
import os
from pathlib import Path
from typing import Dict, Any, Optional
class ConfigManager:
"""管理快导Skill的所有配置"""
CONFIG_FILE = "config/platforms.json"
def __init__(self, skill_path: str = None):
"""
初始化配置管理器
Args:
skill_path: Skill根目录路径,默认从环境变量或推断
"""
if skill_path is None:
# 推断Skill路径
current_file = Path(__file__).resolve()
self.skill_path = current_file.parent.parent
else:
self.skill_path = Path(skill_path)
self.config_path = self.skill_path / self.CONFIG_FILE
self._config_cache = None
def load_config(self) -> Dict[str, Any]:
"""加载完整配置"""
if self._config_cache is None:
with open(self.config_path, 'r', encoding='utf-8') as f:
self._config_cache = json.load(f)
return self._config_cache
def get_platform_config(self, platform: str) -> Dict[str, Any]:
"""
获取指定平台的配置
Args:
platform: 平台标识(xiaohongshu/douyin/shipinhao)
Returns:
平台配置字典
"""
config = self.load_config()
platform_config = config.get("platforms", {}).get(platform, {})
if not platform_config:
raise ValueError(f"未找到平台配置: {platform}")
# 合并全局设置
global_settings = config.get("global_settings", {})
platform_config["_global"] = global_settings
return platform_config
def get_config(self) -> Dict[str, Any]:
"""
获取完整配置
Returns:
完整配置字典
"""
return self.load_config()
def get_user_config(self, key: str) -> Any:
"""
获取用户配置项
Args:
key: 配置项名称
Returns:
配置值
"""
config = self.load_config()
global_settings = config.get("global_settings", {})
value = global_settings.get(key)
# 检查必须配置项
required_keys = [
"copy_library_path",
"report_space_id",
"rules_path"
]
if key in required_keys and not value:
raise ConfigNotSetError(
f"配置项 '{key}' 未设置。\n"
f"请运行: kd config set {key} '你的路径'"
)
return value
def set_user_config(self, key: str, value: Any) -> bool:
"""
设置用户配置项
Args:
key: 配置项名称
value: 配置值
Returns:
是否设置成功
"""
config = self.load_config()
if key not in config.get("global_settings", {}):
raise ValueError(f"未知的配置项: {key}")
config["global_settings"][key] = value
# 保存配置
try:
with open(self.config_path, 'w', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=2)
self._config_cache = config
return True
except Exception as e:
raise ConfigSaveError(f"保存配置失败: {e}")
def validate_config(self) -> Dict[str, bool]:
"""
验证所有必需配置是否已设置
Returns:
配置项验证结果字典
"""
required_configs = [
"copy_library_path",
"report_space_id",
"rules_path"
]
results = {}
for key in required_configs:
try:
value = self.get_user_config(key)
results[key] = bool(value)
except ConfigNotSetError:
results[key] = False
return results
def get_excel_path(self, platform: str) -> str:
"""
获取指定平台的文案库Excel路径
Args:
platform: 平台标识
Returns:
Excel文件完整路径
"""
library_path = self.get_user_config("copy_library_path")
config = self.load_config()
file_naming = config.get("global_settings", {}).get(
"file_naming",
"{platform}文案库.xlsx"
)
platform_name = config.get("platforms", {}).get(platform, {}).get("name", platform)
filename = file_naming.format(platform=platform_name)
return os.path.join(library_path, filename)
def calculate_segments(self, platform: str, total_duration: str) -> int:
"""
根据总时长计算分镜数量
Args:
platform: 平台标识
total_duration: 总时长描述(如"2-3min")
Returns:
分镜数量
"""
platform_config = self.get_platform_config(platform)
duration_config = platform_config.get("duration", {})
segment_range = duration_config.get("segment_seconds", {})
min_seg = segment_range.get("min", 3)
max_seg = segment_range.get("max", 12)
avg_seg = (min_seg + max_seg) / 2
# 解析总时长
total_config = duration_config.get("total_seconds", {})
min_total = total_config.get("min", 120)
max_total = total_config.get("max", 180)
avg_total = (min_total + max_total) / 2
# 计算分镜数量
segments_count = int(avg_total / avg_seg)
return segments_count
class ConfigNotSetError(Exception):
"""配置项未设置错误"""
pass
class ConfigSaveError(Exception):
"""配置保存错误"""
pass
# 便捷函数
def get_config_manager(skill_path: str = None) -> ConfigManager:
"""获取配置管理器实例"""
return ConfigManager(skill_path)
def validate_platform_keywords(platform: str) -> bool:
"""
验证平台关键词是否已配置
Args:
platform: 平台标识
Returns:
是否已配置关键词
"""
config_mgr = get_config_manager()
platform_config = config_mgr.get_platform_config(platform)
keywords = platform_config.get("keywords", [])
return len(keywords) > 0
FILE:scripts/duration_calculator.py
"""
时长计算器 - 智能计算分镜建议时长
基于台词、运镜类型自动计算合理的分镜时长
"""
from typing import Dict, List, Any, Tuple
class DurationCalculator:
"""智能时长计算器"""
def __init__(self, config: dict):
"""
初始化时长计算器
Args:
config: 平台配置字典,包含 duration_policy 和 calculation_rules
"""
self.config = config
self.policy = config.get('duration_policy', config) # 兼容两种配置结构
self.rules = self.policy.get('calculation_rules', {
'speech_rate': 0.25,
'motion_base': {
'static': 2,
'push_pull': 3,
'pan_tilt': 4,
'complex': 6,
'drone': 8
},
'buffer_seconds': 1,
'min_motion_time': 2
})
def calculate_speech_time(self, line: str) -> float:
"""
计算台词所需时长
Args:
line: 台词内容
Returns:
建议时长(秒)
"""
if not line or not isinstance(line, str):
return 0
char_count = len(line.strip())
speech_rate = self.rules.get('speech_rate', 0.25)
return char_count * speech_rate
def calculate_motion_time(self, movement: str) -> float:
"""
根据运镜类型计算所需时长
Args:
movement: 运镜描述
Returns:
建议时长(秒)
"""
if not movement or not isinstance(movement, str):
return self.rules.get('motion_base', {}).get('static', 2)
movement = movement.lower()
motion_base = self.rules.get('motion_base', {})
# 运镜类型匹配规则
movement_patterns = {
'static': ['固定', '定', '静止', 'static'],
'push_pull': ['推', '拉', '推近', '推远', 'zoom'],
'pan_tilt': ['摇', '移', '跟随', '跟随', 'pan', 'tilt'],
'complex': ['环绕', '升降', '复杂', '复杂运镜', 'orbit', 'crane'],
'drone': ['航拍', '俯视', '无人机', 'drone', 'aerial']
}
# 匹配运镜类型
for motion_type, keywords in movement_patterns.items():
for keyword in keywords:
if keyword in movement:
return motion_base.get(motion_type, 3)
# 默认使用固定镜头时长
return motion_base.get('static', 2)
def calculate_scene_duration(self, scene: dict) -> dict:
"""
计算单个分镜的建议时长
Args:
scene: 分镜数据字典,包含 line, movement_desc, time 等字段
Returns:
计算结果字典
"""
# 提取字段(支持多种字段名)
line = scene.get('line', scene.get('台词', scene.get('narration', '')))
movement = scene.get('movement_desc', scene.get('运镜', scene.get('movement', '')))
time_str = scene.get('time', scene.get('时间段', ''))
# 计算各项时长
speech_time = self.calculate_speech_time(line)
motion_time = self.calculate_motion_time(movement)
# 基础时长 = max(台词时长, 运镜时长) + 缓冲
buffer_seconds = self.rules.get('buffer_seconds', 1)
base_duration = max(speech_time, motion_time) + buffer_seconds
# 检查是否在平台限制范围内
segment_limits = self.policy.get('segment_duration', {})
min_duration = segment_limits.get('min', 3)
max_duration = segment_limits.get('max', 12)
# 最终建议时长
recommended = max(min_duration, min(max_duration, base_duration))
# 解析当前标注的时长
labeled_duration = self._parse_labeled_duration(time_str)
# 检查是否匹配
match = self._check_duration_match(labeled_duration, recommended)
return {
'speech_time': round(speech_time, 1),
'motion_time': round(motion_time, 1),
'buffer': buffer_seconds,
'recommended': round(recommended, 1),
'labeled': labeled_duration,
'match': match,
'suggestion': self._generate_suggestion(labeled_duration, recommended, match)
}
def _parse_labeled_duration(self, time_str: str) -> float:
"""
解析当前标注的时长
Args:
time_str: 时间段字符串,如 "0-5s", "5-10s"
Returns:
时长(秒)
"""
if not time_str or not isinstance(time_str, str):
return 0
# 提取数字范围,如 "0-5s" -> [0, 5]
import re
numbers = re.findall(r'(\d+)', time_str)
if len(numbers) >= 2:
try:
start = int(numbers[0])
end = int(numbers[1])
return end - start
except (ValueError, IndexError):
return 0
return 0
def _check_duration_match(self, labeled: float, recommended: float) -> bool:
"""
检查标注时长是否与建议时长匹配
规则:
- 建议时长 ≥ 标注时长的60%
- 建议时长可以更长(允许慢节奏)
Args:
labeled: 标注时长
recommended: 建议时长
Returns:
是否匹配
"""
if labeled == 0:
return True # 无标注时视为匹配
return recommended >= labeled * 0.6
def _generate_suggestion(self, labeled: float, recommended: float, match: bool) -> str:
"""生成调整建议"""
if match:
return "✓ 时长匹配"
if labeled == 0:
return f"建议设置为 {recommended}s"
if recommended < labeled:
return f"建议缩短至 {recommended}s 或补充内容以支撑 {labeled}s"
return f"当前标注 {labeled}s,建议时长 {recommended}s"
def optimize_script(self, scenes: List[dict], target_min: int = None, target_max: int = None) -> dict:
"""
优化脚本时长 - 默认自动调整
Args:
scenes: 分镜列表
target_min: 目标总时长最小值(可选)
target_max: 目标总时长最大值(可选)
Returns:
优化结果字典
"""
# 获取目标时长范围
if target_min is None:
target_min = self.policy.get('target_duration', {}).get('min', 120)
if target_max is None:
target_max = self.policy.get('target_duration', {}).get('max', 180)
optimized_scenes = []
current_time = 0
total_labeled = 0
for scene in scenes:
calc = self.calculate_scene_duration(scene)
# 自动调整:使用建议时长作为最终时间段
if self.policy.get('auto_adjust', True):
start_time = current_time
end_time = current_time + int(calc['recommended'])
# 更新时间段
scene['time'] = f"{start_time}-{end_time}s"
current_time = end_time
# 保留计算详情到备注
scene['_duration_calc'] = calc
else:
# 手动模式:仅添加计算结果
scene['_duration_calc'] = calc
current_time += calc['labeled'] if calc['labeled'] > 0 else calc['recommended']
total_labeled += calc['labeled'] if calc['labeled'] > 0 else calc['recommended']
optimized_scenes.append(scene)
total_duration = current_time
# 验证总时长
total_match = target_min <= total_duration <= target_max
# 生成建议
suggestions = []
if not total_match:
if total_duration < target_min:
suggestions.append(f"总时长 {total_duration}s 不足,建议增加分镜或延长单镜")
elif total_duration > target_max:
suggestions.append(f"总时长 {total_duration}s 超出,建议精简内容")
return {
'scenes': optimized_scenes,
'total_duration': total_duration,
'total_labeled': total_labeled,
'target_range': f"{target_min}-{target_max}s",
'match': total_match,
'suggestions': suggestions
}
def get_duration_report(self, scenes: List[dict]) -> str:
"""
生成时长计算报告
Args:
scenes: 分镜列表
Returns:
Markdown 格式报告
"""
lines = [
"## 时长计算报告",
"",
"| 分镜 | 台词(秒) | 运镜(秒) | 缓冲(秒) | 建议时长 | 标注时长 | 匹配 |",
"|:---:|:---:|:---:|:---:|:---:|:---:|:---:|"
]
total_recommended = 0
total_labeled = 0
all_match = True
for i, scene in enumerate(scenes, 1):
calc = self.calculate_scene_duration(scene)
total_recommended += calc['recommended']
total_labeled += calc['labeled'] if calc['labeled'] > 0 else calc['recommended']
if not calc['match']:
all_match = False
lines.append(
f"| {i} | {calc['speech_time']:.1f} | {calc['motion_time']:.1f} | {calc['buffer']} | "
f"{calc['recommended']:.1f}s | {calc['labeled']:.1f}s | {'✓' if calc['match'] else '✗'} |"
)
lines.extend([
"",
f"**总计**: 建议 {total_recommended:.1f}s | 标注 {total_labeled:.1f}s | 整体 {'✓ 匹配' if all_match else '✗ 不匹配'}"
])
return "\n".join(lines)
# 测试代码
if __name__ == "__main__":
# 示例配置
config = {
'target_duration': {'min': 120, 'max': 180},
'segment_duration': {'min': 6, 'max': 15},
'auto_adjust': True,
'calculation_rules': {
'speech_rate': 0.25,
'motion_base': {
'static': 2,
'push_pull': 3,
'pan_tilt': 4,
'complex': 6,
'drone': 8
},
'buffer_seconds': 1
}
}
calculator = DurationCalculator(config)
# 示例分镜
scenes = [
{
'line': '欢迎来到嘉泰苑,这里是太平枇杷第一镇。',
'movement_desc': '固定镜头',
'time': '0-5s'
},
{
'line': '看这一树金黄的枇杷,又大又甜,每一颗都是阳光的味道。',
'movement_desc': '缓慢推近特写',
'time': '5-12s'
}
]
print(calculator.get_duration_report(scenes))
print()
print("优化结果:", calculator.optimize_script(scenes))
FILE:scripts/excel_manager.py
"""
Excel管理器 - 读取、写入、检查Excel文案库
使用openpyxl进行格式保持的操作
"""
import openpyxl
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
from openpyxl.utils import get_column_letter
from pathlib import Path
from typing import Dict, List, Any, Optional, Tuple
import os
class ExcelManager:
"""管理Excel文案库的读写和格式操作"""
def __init__(self, file_path: str):
"""
初始化Excel管理器
Args:
file_path: Excel文件路径
"""
self.file_path = Path(file_path)
self.wb = None
self.ws = None
self.format_cache = None
def load(self) -> 'ExcelManager':
"""加载Excel文件"""
if not self.file_path.exists():
raise FileNotFoundError(f"Excel文件不存在: {self.file_path}")
self.wb = openpyxl.load_workbook(self.file_path)
self.ws = self.wb.active
return self
def save(self, new_path: str = None):
"""保存Excel文件"""
save_path = new_path or self.file_path
self.wb.save(save_path)
def close(self):
"""关闭Excel文件"""
if self.wb:
self.wb.close()
self.wb = None
self.ws = None
def __enter__(self):
"""上下文管理器入口"""
return self.load()
def __exit__(self, exc_type, exc_val, exc_tb):
"""上下文管理器出口"""
self.close()
def scan_format(self) -> Dict[str, Any]:
"""
扫描并记录Excel格式(Step 5: 格式检查)
Returns:
格式参数字典
"""
if not self.ws:
raise RuntimeError("Excel未加载")
format_info = {
"total_rows": self.ws.max_row,
"total_columns": self.ws.max_column,
"title_row": {},
"data_row_A": {},
"data_row_B_H": {},
"data_row_I_N": {},
"merged_cells": []
}
# 检查标题行(第1行)
if self.ws.max_row >= 1:
for col in range(1, min(15, self.ws.max_column + 1)):
cell = self.ws.cell(1, col)
format_info["title_row"][f"col_{col}"] = {
"font_name": cell.font.name,
"font_size": cell.font.size,
"bold": cell.font.bold,
"font_color": cell.font.color.rgb if cell.font.color else None,
"fill_type": cell.fill.patternType,
"fill_color": cell.fill.fgColor.rgb if cell.fill.fgColor else None,
"h_align": cell.alignment.horizontal,
"v_align": cell.alignment.vertical,
"wrap_text": cell.alignment.wrap_text,
"border": self._get_border_style(cell.border)
}
# 行高
format_info["title_row"]["row_height"] = self.ws.row_dimensions[1].height
# 检查数据行(第2行作为样本)
if self.ws.max_row >= 2:
# A列
cell_a = self.ws.cell(2, 1)
format_info["data_row_A"] = self._extract_cell_format(cell_a)
format_info["data_row_A"]["row_height"] = self.ws.row_dimensions[2].height
# B-H列(取B列作为样本)
cell_b = self.ws.cell(2, 2)
format_info["data_row_B_H"] = self._extract_cell_format(cell_b)
# I-N列(取I列作为样本)
if self.ws.max_column >= 9:
cell_i = self.ws.cell(2, 9)
format_info["data_row_I_N"] = self._extract_cell_format(cell_i)
# 检查合并单元格
for merged_range in self.ws.merged_cells.ranges:
format_info["merged_cells"].append(str(merged_range))
self.format_cache = format_info
return format_info
def _extract_cell_format(self, cell) -> Dict[str, Any]:
"""提取单元格格式信息"""
return {
"font_name": cell.font.name,
"font_size": cell.font.size,
"bold": cell.font.bold,
"fill_type": cell.fill.patternType,
"fill_color": cell.fill.fgColor.rgb if cell.fill.fgColor else None,
"h_align": cell.alignment.horizontal,
"v_align": cell.alignment.vertical,
"wrap_text": cell.alignment.wrap_text,
"border": self._get_border_style(cell.border)
}
def _get_border_style(self, border) -> str:
"""获取边框样式"""
if border.left.style and border.right.style and border.top.style and border.bottom.style:
return border.left.style
return None
def get_existing_scripts(self) -> List[Dict[str, Any]]:
"""
获取已有脚本列表(Step 4: 同质化检查)
Returns:
已有脚本列表,包含标题和主题
"""
if not self.ws:
raise RuntimeError("Excel未加载")
scripts = []
# 从第2行开始读取(第1行是标题)
for row in range(2, self.ws.max_row + 1):
# 读取A列(视频文案,包含标题)
title_cell = self.ws.cell(row, 1).value
if title_cell:
# 提取标题(通常是第一行)
title = str(title_cell).split('\n')[0] if '\n' in str(title_cell) else str(title_cell)[:50]
scripts.append({
"row": row,
"title": title,
"raw_content": title_cell
})
return scripts
def append_script(self, script_data: Dict[str, Any], platform: str,
start_row: int = None, row_height: float = 100) -> int:
"""
追加脚本到Excel(Step 8: 更新文案库)
Args:
script_data: 脚本数据字典
platform: 平台标识
start_row: 开始写入行号,None则自动计算
row_height: 行高,默认100
Returns:
结束行号
"""
if not self.ws:
raise RuntimeError("Excel未加载")
if start_row is None:
start_row = self.ws.max_row + 1
segments = script_data.get("segments", [])
if not segments:
raise ValueError("脚本没有分镜数据")
# 计算结束行
end_row = start_row + len(segments) - 1
# 写入A列(视频文案)- 跨行合并
a_cell = self.ws.cell(start_row, 1)
a_cell.value = script_data.get("title", "") + "\n" + script_data.get("story", "")
if len(segments) > 1:
self.ws.merge_cells(start_row=start_row, start_column=1,
end_row=end_row, end_column=1)
# 写入B-H列(时间段、镜头、运镜、技巧、画面、台词、音效)
for i, segment in enumerate(segments):
row = start_row + i
self.ws.cell(row, 2).value = segment.get("time", "") # B-时间段
self.ws.cell(row, 3).value = segment.get("shot_desc", "") # C-镜头
self.ws.cell(row, 4).value = segment.get("movement_desc", "") # D-运镜
self.ws.cell(row, 5).value = segment.get("tech_desc", "") # E-技巧
self.ws.cell(row, 6).value = segment.get("scene_desc", "") # F-画面
self.ws.cell(row, 7).value = segment.get("line", "") # G-台词
self.ws.cell(row, 8).value = segment.get("sound_desc", "") # H-音效
# 写入I-N列(BGM、标签、状态、活动、日期、备注)
i_cell = self.ws.cell(start_row, 9)
i_cell.value = script_data.get("bgm", "") # I-BGM
# 设置其他列
self.ws.cell(start_row, 10).value = script_data.get("tags", "") # J-标签
self.ws.cell(start_row, 11).value = "待使用" # K-状态
self.ws.cell(start_row, 12).value = script_data.get("activity", "") # L-活动
self.ws.cell(start_row, 13).value = script_data.get("date", "") # M-日期
self.ws.cell(start_row, 14).value = script_data.get("notes", "") # N-备注
# 跨行合并I-N列
if len(segments) > 1:
self.ws.merge_cells(start_row=start_row, start_column=9,
end_row=end_row, end_column=14) # I-N列合并
# 设置行高
for row in range(start_row, end_row + 1):
self.ws.row_dimensions[row].height = row_height
return end_row
def apply_format(self, format_info: Dict[str, Any],
start_row: int, end_row: int):
"""
应用格式到指定行范围
Args:
format_info: 格式参数字典
start_row: 开始行
end_row: 结束行
"""
# 应用数据行格式(A列)
if "data_row_A" in format_info:
self._apply_row_format(format_info["data_row_A"], start_row, end_row, 1, 1)
# 应用数据行格式(B-H列)
if "data_row_B_H" in format_info:
for row in range(start_row, end_row + 1):
self._apply_row_format(format_info["data_row_B_H"], row, row, 2, 8)
# 应用数据行格式(I-N列)
if "data_row_I_N" in format_info:
self._apply_row_format(format_info["data_row_I_N"], start_row, end_row, 9, 14)
def _apply_row_format(self, format_dict: Dict[str, Any],
start_row: int, end_row: int,
start_col: int, end_col: int):
"""应用格式到单元格范围"""
# 创建字体
font = Font(
name=format_dict.get("font_name", "宋体"),
size=format_dict.get("font_size", 11),
bold=format_dict.get("bold", False)
)
# 创建填充
fill = None
if format_dict.get("fill_type"):
fill = PatternFill(
patternType=format_dict.get("fill_type"),
fgColor=format_dict.get("fill_color")
)
# 创建对齐
alignment = Alignment(
horizontal=format_dict.get("h_align", "left"),
vertical=format_dict.get("v_align", "center"),
wrap_text=format_dict.get("wrap_text", True)
)
# 创建边框
border_style = format_dict.get("border")
border = None
if border_style:
side = Side(style=border_style)
border = Border(left=side, right=side, top=side, bottom=side)
# 应用格式
for row in range(start_row, end_row + 1):
for col in range(start_col, end_col + 1):
cell = self.ws.cell(row, col)
cell.font = font
if fill:
cell.fill = fill
cell.alignment = alignment
if border:
cell.border = border
# 设置行高
if "row_height" in format_dict:
self.ws.row_dimensions[row].height = format_dict["row_height"]
def validate_write(self, start_row: int, end_row: int) -> Dict[str, Any]:
"""
验证写入结果(Step 9: 全面检查对比)
Args:
start_row: 开始行
end_row: 结束行
Returns:
验证结果字典
"""
result = {
"format_valid": True,
"content_complete": True,
"position_correct": True,
"errors": []
}
# 检查格式正确性
for row in range(start_row, end_row + 1):
for col in range(1, 15):
cell = self.ws.cell(row, col)
if not cell.value and col not in [11]: # K列(状态)可以有默认值
result["content_complete"] = False
result["errors"].append(f"第{row}行第{col}列内容为空")
# 检查合并单元格
for merged_range in self.ws.merged_cells.ranges:
# 验证合并范围是否正确
pass
return result
def delete_rows(self, start_row: int, end_row: int):
"""
删除指定行(用于Step 9失败回退)
Args:
start_row: 开始行
end_row: 结束行
"""
# 注意:openpyxl删除行是从start_row开始删除count行
count = end_row - start_row + 1
self.ws.delete_rows(start_row, count)
class ExcelFormatError(Exception):
"""Excel格式错误"""
pass
class ExcelWriteError(Exception):
"""Excel写入错误"""
pass
FILE:scripts/feishu_permission_helper.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
飞书权限批量开通助手
帮助用户一键生成权限配置并指导开通流程
"""
import json
import os
from pathlib import Path
class FeishuPermissionHelper:
"""飞书权限开通助手"""
def __init__(self):
self.skill_path = Path(__file__).parent.parent
self.config_path = self.skill_path / "config" / "feishu_permissions.json"
def load_permissions(self):
"""加载权限配置"""
with open(self.config_path, 'r', encoding='utf-8') as f:
return json.load(f)
def generate_import_json(self):
"""生成飞书批量导入用的 JSON"""
permissions = self.load_permissions()
# 飞书批量导入格式
import_data = {
"scopes": permissions["scopes"]
}
return json.dumps(import_data, ensure_ascii=False, indent=2)
def print_setup_guide(self):
"""打印开通指南"""
permissions = self.load_permissions()
guide = f"""
╔════════════════════════════════════════════════════════════════╗
║ 快导(KD) - 飞书机器人权限开通指南 ║
╚════════════════════════════════════════════════════════════════╝
📋 权限说明
本 Skill 需要以下飞书权限才能正常工作:
【应用身份权限 (tenant)】
{self._format_permissions(permissions['scopes']['tenant'])}
【用户身份权限 (user)】
{self._format_permissions(permissions['scopes']['user'])}
═══════════════════════════════════════════════════════════════════
🚀 开通步骤
步骤 1: 进入飞书开发者后台
1. 打开 https://open.feishu.cn/app
2. 选择你的应用
步骤 2: 批量导入权限
1. 左侧导航 → "权限管理" → "开通权限"
2. 点击 "批量导入/导出权限" 按钮
3. 粘贴下方的 JSON 配置
4. 点击 "下一步,确认新增权限"
5. 确认无误后点击 "申请开通"
步骤 3: 发布应用
1. 左侧导航 → "版本管理与发布" → "创建版本"
2. 填写版本信息
3. 点击 "保存" → "申请发布"
═══════════════════════════════════════════════════════════════════
📋 批量导入 JSON(复制以下内容)
{self.generate_import_json()}
═══════════════════════════════════════════════════════════════════
⚠️ 注意事项
1. 开通权限后需要等待审核(通常几分钟到几小时)
2. 如果权限不足,Step 10(保存报告到飞书)会失败
3. 失败时会自动保存到本地 reports/ 目录
🔧 验证权限
开通后运行以下命令验证:
lark-cli auth login --as bot
lark-cli wiki spaces list --as bot
如果命令成功执行,说明权限已正确开通。
═══════════════════════════════════════════════════════════════════
"""
print(guide)
def _format_permissions(self, permissions):
"""格式化权限列表"""
lines = []
for perm in permissions:
lines.append(f" • {perm}")
return "\n".join(lines)
def export_to_clipboard(self):
"""导出 JSON 到剪贴板(跨平台)"""
import_data = self.generate_import_json()
try:
# Windows
import subprocess
subprocess.run(['clip'], input=import_data.encode('utf-8'), check=True)
print("✅ 已复制到剪贴板")
except:
try:
# macOS
import subprocess
subprocess.run(['pbcopy'], input=import_data.encode('utf-8'), check=True)
print("✅ 已复制到剪贴板")
except:
# Linux 或其他
print("⚠️ 请手动复制上面的 JSON 内容")
def main():
"""主函数"""
helper = FeishuPermissionHelper()
print("快导(KD) - 飞书权限开通助手\n")
print("选择操作:")
print("1. 显示完整开通指南")
print("2. 仅显示批量导入 JSON")
print("3. 导出 JSON 到剪贴板")
print("4. 退出")
choice = input("\n请输入选项 (1-4): ").strip()
if choice == "1":
helper.print_setup_guide()
elif choice == "2":
print("\n批量导入 JSON:\n")
print(helper.generate_import_json())
elif choice == "3":
helper.export_to_clipboard()
else:
print("再见!")
if __name__ == "__main__":
main()
FILE:scripts/format_checker.py
"""
格式检查器 - 验证脚本内容和Excel格式
提供9维度合理性检查和格式修复功能
"""
import re
from typing import Dict, List, Any, Tuple
# 尝试相对导入
try:
from .config_manager import get_config_manager
except ImportError:
from config_manager import get_config_manager
class FormatChecker:
"""脚本格式检查和修复工具"""
def __init__(self, platform: str):
"""
初始化格式检查器
Args:
platform: 平台标识
"""
self.platform = platform
self.config_mgr = get_config_manager()
self.platform_config = self.config_mgr.get_platform_config(platform)
self.issues = []
self.fixes = []
def validate_script(self, script: Dict[str, Any]) -> Tuple[bool, List[str]]:
"""
验证单个脚本(Step 7: 合理性检查)
Args:
script: 脚本数据
Returns:
(是否通过, 问题列表)
"""
self.issues = []
# 9维度检查
checks = [
("时间合理性", self._check_time),
("内容合理性", self._check_content),
("场景合理性", self._check_scene),
("台词合理性", self._check_lines),
("分镜时长限制", self._check_segment_duration),
("技术描述合理性", self._check_technical),
("标题合理性", self._check_title),
("格式合理性", self._check_format),
("故事叙述", self._check_story)
]
for check_name, check_func in checks:
try:
check_func(script)
except Exception as e:
self.issues.append(f"{check_name}: {str(e)}")
passed = len(self.issues) == 0
return passed, self.issues
def validate_batch(self, scripts: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
批量验证脚本
Args:
scripts: 脚本列表
Returns:
验证结果字典
"""
results = {
"total": len(scripts),
"passed": 0,
"failed": 0,
"details": []
}
for i, script in enumerate(scripts, 1):
passed, issues = self.validate_script(script)
if passed:
results["passed"] += 1
else:
results["failed"] += 1
results["details"].append({
"script_id": i,
"passed": passed,
"issues": issues
})
return results
def auto_fix(self, script: Dict[str, Any]) -> Dict[str, Any]:
"""
自动修复脚本问题
Args:
script: 原始脚本
Returns:
修复后的脚本
"""
self.fixes = []
fixed_script = script.copy()
# 修复字数不足
fixed_script = self._fix_min_chars(fixed_script)
# 修复口语化问题
fixed_script = self._fix_colloquial(fixed_script)
# 修复格式问题
fixed_script = self._fix_format_issues(fixed_script)
return fixed_script
def _check_time(self, script: Dict[str, Any]):
"""检查时间合理性"""
segments = script.get("segments", [])
if not segments:
raise ValueError("分镜数据为空")
# 检查时间连续性
prev_end = 0
for seg in segments:
time_str = seg.get("time", "")
match = re.match(r'(\d+)-(\d+)秒', time_str)
if not match:
raise ValueError(f"时间段格式错误: {time_str}")
start, end = int(match.group(1)), int(match.group(2))
if start != prev_end:
raise ValueError(f"时间不连续: 期望{prev_end}秒开始,实际{start}秒")
prev_end = end
def _check_content(self, script: Dict[str, Any]):
"""检查内容合理性"""
segments = script.get("segments", [])
# 检查内容连贯性
for i in range(len(segments) - 1):
current = segments[i]
next_seg = segments[i + 1]
# 检查是否有内容
if not current.get("shot_desc"):
raise ValueError(f"第{i+1}分镜镜头描述为空")
def _check_scene(self, script: Dict[str, Any]):
"""检查场景合理性"""
segments = script.get("segments", [])
for i, seg in enumerate(segments):
scene_desc = seg.get("scene_desc", "")
# 检查场景描述是否具体
if len(scene_desc) < 10:
raise ValueError(f"第{i+1}分镜场景描述过短")
def _check_lines(self, script: Dict[str, Any]):
"""检查台词合理性"""
segments = script.get("segments", [])
for i, seg in enumerate(segments):
line = seg.get("line", "")
# 检查书面语
formal_words = ["首先", "其次", "综上所述", "由此可见"]
for word in formal_words:
if word in line:
raise ValueError(f"第{i+1}分镜台词包含书面语: {word}")
def _check_segment_duration(self, script: Dict[str, Any]):
"""检查分镜时长限制"""
segments = script.get("segments", [])
duration_config = self.platform_config.get("duration", {})
segment_range = duration_config.get("segment_seconds", {})
min_dur = segment_range.get("min", 3)
max_dur = segment_range.get("max", 12)
for i, seg in enumerate(segments):
duration = seg.get("duration", 0)
if duration < min_dur or duration > max_dur:
raise ValueError(
f"第{i+1}分镜时长{duration}秒超出范围[{min_dur}-{max_dur}]"
)
def _check_technical(self, script: Dict[str, Any]):
"""检查技术描述合理性"""
segments = script.get("segments", [])
content_req = self.platform_config.get("content_requirements", {}).get("columns", {})
# 检查各列字数
column_checks = [
("C", "shot_desc", "镜头描述"),
("D", "movement_desc", "运镜描述"),
("E", "tech_desc", "技巧描述"),
("F", "scene_desc", "画面描述"),
("H", "sound_desc", "音效描述")
]
for col_key, field, name in column_checks:
min_chars = content_req.get(col_key, {}).get("min_chars", 0)
for i, seg in enumerate(segments):
content = seg.get(field, "")
if len(content) < min_chars:
raise ValueError(
f"第{i+1}分镜{name}不足{min_chars}字,实际{len(content)}字"
)
def _check_title(self, script: Dict[str, Any]):
"""检查标题合理性"""
title_rules = self.platform_config.get("title_rules", {})
max_length = title_rules.get("max_length", 50)
no_punctuation = title_rules.get("no_punctuation", False)
title = script.get("title", "")
# 检查长度
if len(title) > max_length:
raise ValueError(f"标题过长: {len(title)}字 > 限制{max_length}字")
# 检查标点
if no_punctuation and re.search(r'[,。?!,\.\?!]', title):
raise ValueError("标题包含标点符号")
def _check_format(self, script: Dict[str, Any]):
"""检查格式合理性"""
required_fields = [
"title", "theme", "story", "total_duration",
"segments_count", "segments"
]
for field in required_fields:
if field not in script:
raise ValueError(f"缺少必需字段: {field}")
def _check_story(self, script: Dict[str, Any]):
"""检查故事叙述"""
story = script.get("story", "")
# 检查是否有起承转合结构
# 简化检查:至少包含200字以上的叙述
if len(story) < 200:
raise ValueError(f"故事叙述过短: {len(story)}字,建议200字以上")
# 检查是否包含完整叙述
if not any(word in story for word in ["后来", "然后", "最后", "结果"]):
raise ValueError("故事叙述可能不完整,缺少承接词")
def _fix_min_chars(self, script: Dict[str, Any]) -> Dict[str, Any]:
"""修复字数不足问题"""
content_req = self.platform_config.get("content_requirements", {}).get("columns", {})
segments = script.get("segments", [])
column_fixes = [
("C", "shot_desc", "镜头", "特写镜头,展现细节"),
("D", "movement_desc", "运镜", "缓慢推近,营造氛围"),
("E", "tech_desc", "技巧", "使用稳定器,确保画面平稳"),
("F", "scene_desc", "画面", "自然光线,柔和色调"),
("H", "sound_desc", "音效", "环境音为主,配合轻音乐")
]
for col_key, field, name, default_text in column_fixes:
min_chars = content_req.get(col_key, {}).get("min_chars", 0)
for seg in segments:
content = seg.get(field, "")
if len(content) < min_chars:
# 补充默认文本
seg[field] = content + " " + default_text
self.fixes.append(f"补充{name}描述至{min_chars}字")
return script
def _fix_colloquial(self, script: Dict[str, Any]) -> Dict[str, Any]:
"""修复口语化问题"""
formal_to_colloquial = {
"首先": "先",
"其次": "再说",
"综上所述": "总之",
"由此可见": "所以说"
}
for seg in script.get("segments", []):
line = seg.get("line", "")
for formal, colloquial in formal_to_colloquial.items():
if formal in line:
line = line.replace(formal, colloquial)
self.fixes.append(f"替换书面语'{formal}'为'{colloquial}'")
seg["line"] = line
return script
def _fix_format_issues(self, script: Dict[str, Any]) -> Dict[str, Any]:
"""修复格式问题"""
# 确保所有必需字段存在
if "story" not in script or not script["story"]:
script["story"] = "这是一个关于" + script.get("theme", "主题") + "的故事"
self.fixes.append("补充故事叙述")
return script
def get_fix_report(self) -> str:
"""获取修复报告"""
if not self.fixes:
return "无需修复"
return "\n".join([f"- {fix}" for fix in self.fixes])
def validate_activity(self, activity: str, config: dict = None) -> dict:
"""
验证关联活动字段
Args:
activity: 活动字段内容
config: 配置字典(可选)
Returns:
验证结果字典
"""
errors = []
# 获取当前配置的活动
if config is None:
config = self.platform_config
activities = config.get('activities', {}).get('list', [])
valid_names = [a.get('name') for a in activities if a.get('id') != 'example']
# 禁止值检查(任务标识)
forbidden_values = ["天选之子", "step", "流程", "task", "流程"]
for forbidden in forbidden_values:
if forbidden.lower() in activity.lower():
errors.append(f"关联活动不能包含'{forbidden}'")
# 检查是否在有效活动列表中(仅当用户配置了活动时)
if valid_names and activity not in valid_names:
# 允许"日常推广"或默认提示
if not activity.startswith("日常推广"):
errors.append(f"关联活动'{activity}'不在配置的活动列表中")
# 未配置活动时,检查是否显示默认提示
if not valid_names:
if "当前未设置activities" not in activity:
errors.append("未配置活动时,应显示'日常推广,当前未设置activities'")
return {"valid": len(errors) == 0, "errors": errors}
def validate_bgm(self, bgm_text: str) -> dict:
"""
验证BGM字段格式
Args:
bgm_text: BGM字段内容
Returns:
验证结果字典
"""
errors = []
# 检查是否为占位符
placeholder_values = ["推荐音乐", "BGM", "音乐", "", " "]
if bgm_text.strip() in placeholder_values:
errors.append("BGM不能只是占位符,需生成具体内容(音乐名+风格+使用时机)")
# 检查是否包含必需字段
required_parts = ["音乐名:", "风格:", "使用时机:"]
for part in required_parts:
if part not in bgm_text:
errors.append(f"BGM缺少'{part}'描述")
return {"valid": len(errors) == 0, "errors": errors}
def validate_duration_match(self, script: dict, platform_config: dict = None) -> dict:
"""
验证时长匹配(台词、画面与时间段匹配)
Args:
script: 脚本数据
platform_config: 平台配置(可选)
Returns:
验证结果字典
"""
if platform_config is None:
platform_config = self.platform_config
# 导入时长计算器
try:
from .duration_calculator import DurationCalculator
except ImportError:
from duration_calculator import DurationCalculator
calculator = DurationCalculator(platform_config)
scenes = script.get('segments', [])
results = []
all_match = True
for i, scene in enumerate(scenes, 1):
calc = calculator.calculate_scene_duration(scene)
results.append({
'scene_num': i,
**calc
})
if not calc['match']:
all_match = False
return {
'all_match': all_match,
'scenes': results,
'total_labeled': sum(r['labeled'] for r in results),
'total_recommended': sum(r['recommended'] for r in results)
}
class ValidationIssue:
"""验证问题详情"""
def __init__(self, dimension: str, description: str, severity: str = "error"):
self.dimension = dimension
self.description = description
self.severity = severity
def __str__(self):
return f"[{self.severity}] {self.dimension}: {self.description}"
FILE:scripts/script_generator.py
"""
脚本生成器 - 基于AI生成短视频脚本
整合提示词模板和平台配置
"""
import json
from typing import Dict, List, Any, Optional
# 尝试相对导入,失败则使用绝对导入
try:
from .config_manager import ConfigManager, get_config_manager
except ImportError:
from config_manager import ConfigManager, get_config_manager
class ScriptGenerator:
"""生成短视频脚本的AI驱动引擎"""
def __init__(self,
platform: str,
duration: str = None,
keywords: List[str] = None,
trending_titles: List[str] = None,
avoid_themes: List[str] = None):
"""
初始化脚本生成器
Args:
platform: 平台标识(xiaohongshu/douyin/shipinhao)
duration: 总时长描述(如"2-3min")
keywords: 搜索关键词列表
trending_titles: 爆款标题列表
avoid_themes: 需避开的主题列表
"""
self.platform = platform
self.duration = duration
self.keywords = keywords or []
self.trending_titles = trending_titles or []
self.avoid_themes = avoid_themes or []
self.config_mgr = get_config_manager()
self.platform_config = self.config_mgr.get_platform_config(platform)
# 计算分镜数量
self.segments_count = self._calculate_segments()
def _calculate_segments(self) -> int:
"""计算分镜数量"""
duration_config = self.platform_config.get("duration", {})
# 获取分镜时长范围
segment_range = duration_config.get("segment_seconds", {})
min_seg = segment_range.get("min", 3)
max_seg = segment_range.get("max", 12)
avg_seg = (min_seg + max_seg) / 2
# 获取总时长
if self.duration:
# 从duration参数解析
total_seconds = self._parse_duration(self.duration)
else:
total_config = duration_config.get("total_seconds", {})
min_total = total_config.get("min", 120)
max_total = total_config.get("max", 180)
total_seconds = (min_total + max_total) / 2
# 计算分镜数量
segments_count = int(total_seconds / avg_seg)
return max(segments_count, 1)
def _parse_duration(self, duration_str: str) -> int:
"""解析时长字符串为秒数"""
duration_str = duration_str.lower().replace(" ", "")
# 解析 "2-3min" 格式
if "min" in duration_str:
parts = duration_str.replace("min", "").split("-")
if len(parts) == 2:
avg_min = (float(parts[0]) + float(parts[1])) / 2
return int(avg_min * 60)
else:
return int(float(parts[0]) * 60)
# 解析 "120s" 格式
if "s" in duration_str:
return int(duration_str.replace("s", ""))
return 120 # 默认2分钟
def generate(self, count: int = 5) -> List[Dict[str, Any]]:
"""
生成指定数量的脚本
Args:
count: 脚本数量
Returns:
脚本列表
"""
scripts = []
for i in range(count):
script = self._generate_single_script(i + 1)
scripts.append(script)
return scripts
def _generate_single_script(self, script_num: int) -> Dict[str, Any]:
"""生成单个脚本"""
# 计算分镜时长分配
segment_durations = self._distribute_duration()
# 构建提示词
prompt = self._build_prompt(script_num)
# 构建脚本结构
script = {
"script_id": script_num,
"title": f"脚本{script_num}",
"theme": self.trending_titles[script_num - 1] if script_num <= len(self.trending_titles) else "",
"story": "", # 由AI生成
"total_duration": self.duration or self.platform_config["duration"]["total"],
"segments_count": self.segments_count,
"segments": self._build_segments_structure(segment_durations)
}
return script
def _distribute_duration(self) -> List[int]:
"""
分配分镜时长
遵循"开头快、中间稳、结尾慢"原则
"""
duration_config = self.platform_config.get("duration", {})
segment_range = duration_config.get("segment_seconds", {})
min_seg = segment_range.get("min", 3)
max_seg = segment_range.get("max", 12)
n = self.segments_count
# 分段:开场(20%)、主体(60%)、结尾(20%)
intro_count = max(1, int(n * 0.2))
ending_count = max(1, int(n * 0.2))
body_count = n - intro_count - ending_count
durations = []
# 开场:较短,快速吸引
for i in range(intro_count):
duration = min_seg + (max_seg - min_seg) * 0.2 * (i + 1) / intro_count
durations.append(int(duration))
# 主体:中等,内容展开
for i in range(body_count):
duration = (min_seg + max_seg) / 2
durations.append(int(duration))
# 结尾:较长,升华收尾
for i in range(ending_count):
duration = max_seg - (max_seg - min_seg) * 0.3 * (ending_count - i) / ending_count
durations.append(int(duration))
return durations
def _build_segments_structure(self, durations: List[int]) -> List[Dict[str, Any]]:
"""构建分镜结构"""
segments = []
current_time = 0
content_req = self.platform_config.get("content_requirements", {}).get("columns", {})
for i, duration in enumerate(durations, 1):
start_time = current_time
end_time = current_time + duration
segment = {
"seg_id": i,
"time": f"{start_time}-{end_time}秒",
"duration": duration,
"shot_desc": "", # C列-镜头,需满足min_chars
"movement_desc": "", # D列-运镜
"tech_desc": "", # E列-技巧
"scene_desc": "", # F列-画面
"line": "", # G列-台词
"sound_desc": "", # H列-音效
"bgm": "", # I列-BGM
"tags": "", # J列-标签
"status": "待使用", # K列-状态
"activity": "", # L列-活动
"date": "", # M列-日期
"notes": "" # N列-备注
}
segments.append(segment)
current_time = end_time
return segments
def _build_prompt(self, script_num: int) -> str:
"""构建AI提示词"""
# 读取模板
import json
config_path = self.config_mgr.skill_path / "config" / "templates.json"
with open(config_path, 'r', encoding='utf-8') as f:
templates = json.load(f)
template = templates.get("prompt_templates", {}).get("subtask6_generate", {}).get("template", "")
# 获取字数要求
content_req = self.platform_config.get("content_requirements", {}).get("columns", {})
# 替换变量
prompt = template
prompt = prompt.replace("[TRENDING_TITLES]", ", ".join(self.trending_titles[:5]))
prompt = prompt.replace("[PLATFORM_RULES]", "从规则文档读取")
prompt = prompt.replace("[PLATFORM_CONFIG]", json.dumps(self.platform_config, ensure_ascii=False))
prompt = prompt.replace("[SEGMENTS_COUNT]", str(self.segments_count))
prompt = prompt.replace("[TOTAL_DURATION]", self.duration or self.platform_config["duration"]["total"])
prompt = prompt.replace("[SEGMENT_DURATION_RANGE]", self.platform_config["duration"]["segment"])
prompt = prompt.replace("[AVOID_THEMES]", ", ".join(self.avoid_themes))
prompt = prompt.replace("[PLATFORM_NAME]", self.platform_config["name"])
prompt = prompt.replace("[TARGET_AUDIENCE]", self.platform_config["user_profile"])
prompt = prompt.replace("[CONTENT_STYLE]", self.platform_config["content_style"])
prompt = prompt.replace("[SCRIPT_COUNT]", "5")
# 替换字数要求
prompt = prompt.replace("[MIN_CHARS_SHOT]", str(content_req.get("C", {}).get("min_chars", 20)))
prompt = prompt.replace("[MIN_CHARS_MOVEMENT]", str(content_req.get("D", {}).get("min_chars", 15)))
prompt = prompt.replace("[MIN_CHARS_TECHNIQUE]", str(content_req.get("E", {}).get("min_chars", 15)))
prompt = prompt.replace("[MIN_CHARS_SCENE]", str(content_req.get("F", {}).get("min_chars", 20)))
prompt = prompt.replace("[MIN_CHARS_LINE]", str(content_req.get("G", {}).get("min_chars", 30)))
prompt = prompt.replace("[MIN_CHARS_SOUND]", str(content_req.get("H", {}).get("min_chars", 15)))
return prompt
def get_prompt_for_script(self, script_num: int) -> str:
"""获取指定脚本的生成提示词(供外部调用)"""
return self._build_prompt(script_num)
class ScriptValidationError(Exception):
"""脚本验证错误"""
pass
FILE:scripts/workflow_manager.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
WorkflowManager - 快导(KD) 10步流程编排器
负责管理和执行完整的快导系列工作流
"""
import json
import random
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Any, Optional, Tuple
from .config_manager import ConfigManager
from .script_generator import ScriptGenerator
from .excel_manager import ExcelManager
from .format_checker import FormatChecker
class WorkflowManager:
"""
快导系列工作流管理器
负责编排和执行10步完整流程:
1-5: 数据准备(可并行)
6: 脚本生成(整合前5步数据)
7-10: 后处理(顺序执行)
"""
def __init__(self, platform: str, config_path: Optional[str] = None, interactive: bool = False):
"""
初始化工作流管理器
Args:
platform: 目标平台(xiaohongshu/douyin/shipinhao/pyq)
config_path: 配置文件路径(可选)
interactive: 是否交互模式(默认False自动执行,True每步等待确认)
"""
self.platform = platform
self.platform_en = self._get_platform_en(platform)
self.interactive = interactive
# 初始化各管理器
self.config_mgr = ConfigManager(config_path)
self.excel_mgr = ExcelManager("") # 延迟加载,路径在调用时传入
# 工作流状态
self.step_outputs = {} # 存储各步骤输出
self.current_step = 0
self.errors = []
self.warnings = []
self.paused = False # 是否暂停等待用户输入
self.pending_step = None # 等待执行的步骤
# 加载平台配置
self.platform_config = self._load_platform_config()
def _get_platform_en(self, platform: str) -> str:
"""获取平台英文标识"""
mapping = {
'xiaohongshu': 'xiaohongshu',
'douyin': 'douyin',
'shipinhao': 'shipinhao',
'pyq': 'pyq',
'小红书': 'xiaohongshu',
'抖音': 'douyin',
'视频号': 'shipinhao',
'朋友圈': 'pyq'
}
return mapping.get(platform, platform)
def _load_platform_config(self) -> Dict:
"""加载平台配置"""
try:
return self.config_mgr.get_platform_config(self.platform_en)
except Exception as e:
self.warnings.append(f"无法加载平台配置: {e}")
return {}
def run_full(self, manual_inputs: Optional[Dict] = None, callback=None) -> Dict:
"""
执行完整的10步流程
Args:
manual_inputs: 手动输入数据
callback: 每步完成后的回调函数,签名为 callback(step_num, result, should_pause)
Returns:
执行结果字典
"""
print(f"\n{'='*60}")
print(f"启动快导系列 - 平台: {self.platform}")
if self.interactive:
print("模式: 交互式(每步等待确认)")
else:
print("模式: 自动执行")
print(f"{'='*60}\n")
try:
# 定义步骤列表
steps_to_run = [
(1, self._run_step1, manual_inputs.get('step1_trending') if manual_inputs else None),
(2, self._run_step2, None),
(3, self._run_step3, manual_inputs.get('step3_external') if manual_inputs else None),
(4, self._run_step4, None),
(5, self._run_step5, None),
(6, self._run_step6, None),
(7, self._run_step7, None),
(8, self._run_step8, None),
(9, self._run_step9, None),
(10, self._run_step10, None)
]
# 依次执行10步(处理Step 9特殊回退逻辑)
step_idx = 0
while step_idx < len(steps_to_run):
step_num, step_func, step_input = steps_to_run[step_idx]
# 检查是否暂停
if self.paused:
self.pending_step = step_num
return {
'success': False,
'paused': True,
'pending_step': step_num,
'message': f'工作流暂停在 Step {step_num-1},等待用户输入后继续'
}
# 执行步骤
self.current_step = step_num
if step_input is not None:
result = step_func(step_input)
else:
result = step_func()
# Step 9 特殊处理:失败时回退到 Step 8
if step_num == 9 and not result:
print("⚠️ Step 9 检查失败,回退到 Step 8 重新执行...")
step_idx = 7 # 回退到 Step 8 的索引
continue
# 调用回调(如果有)
should_pause = self.interactive and step_num < 10
if callback:
callback(step_num, result, should_pause)
# 交互模式:每步后暂停(除了最后一步)
if should_pause:
self.paused = True
self.pending_step = step_num + 1
return {
'success': False,
'paused': True,
'completed_step': step_num,
'next_step': step_num + 1,
'message': f'Step {step_num} 完成,是否继续执行 Step {step_num + 1}?'
}
step_idx += 1
# 全部完成
return {
'success': True,
'platform': self.platform,
'completed_steps': list(self.step_outputs.keys()),
'errors': self.errors,
'warnings': self.warnings
}
except Exception as e:
self.errors.append(str(e))
return {
'success': False,
'platform': self.platform,
'error': str(e),
'errors': self.errors,
'warnings': self.warnings,
'completed_steps': self.current_step
}
def resume(self, manual_inputs: Optional[Dict] = None, callback=None) -> Dict:
"""从暂停状态继续执行"""
if not self.paused:
return {'success': False, 'message': '工作流未暂停,无需恢复'}
self.paused = False
return self.run_full(manual_inputs, callback)
def get_status(self) -> Dict:
"""获取当前工作流状态"""
return {
'platform': self.platform,
'current_step': self.current_step,
'completed_steps': list(self.step_outputs.keys()),
'paused': self.paused,
'pending_step': self.pending_step,
'errors': self.errors,
'warnings': self.warnings
}
def run_step(self, step_number: int, **kwargs) -> Dict:
"""
执行单步
Args:
step_number: 步骤编号 (1-10)
**kwargs: 额外参数
Returns:
步骤执行结果
"""
self.current_step = step_number
step_methods = {
1: self._run_step1,
2: self._run_step2,
3: self._run_step3,
4: self._run_step4,
5: self._run_step5,
6: self._run_step6,
7: self._run_step7,
8: self._run_step8,
9: self._run_step9,
10: self._run_step10
}
if step_number not in step_methods:
return {'success': False, 'error': f'无效的步骤编号: {step_number}'}
try:
result = step_methods[step_number](**kwargs)
return {'success': True, 'step': step_number, 'result': result}
except Exception as e:
return {'success': False, 'step': step_number, 'error': str(e)}
# ========== Step 1: 搜索目标平台爆款 ==========
def _run_step1(self, manual_trending: Optional[List[str]] = None) -> Dict:
"""Step 1: 搜索目标平台爆款
流程:
1. 从配置读取关键词池
2. 随机抽取3个关键词
3. 搜索或接收手动输入的爆款
4. 选定4个最优爆款
"""
print("Step 1: 搜索目标平台爆款...")
# 获取关键词池
keywords_pool = self.platform_config.get('keywords', [])
if not keywords_pool:
raise Exception(f"平台 {self.platform} 的关键词池为空,请先配置 keywords")
# 随机抽取3个关键词
selected_keywords = random.sample(keywords_pool, min(3, len(keywords_pool)))
print(f" 抽取关键词: {selected_keywords}")
# 搜索或手动输入
if manual_trending and len(manual_trending) > 0:
print(f" 使用手动提供的 {len(manual_trending)} 个爆款")
trending_videos = manual_trending[:4]
else:
# 没有手动输入时,抛出异常提示用户
error_msg = """
⚠️ Step 1 需要爆款数据
由于未配置搜索功能,请提供4个爆款标题:
手动调用方式:
workflow.run_full(manual_inputs={
'step1_trending': ['爆款标题1', '爆款标题2', '爆款标题3', '爆款标题4']
})
或先执行 step1:
workflow.run_step(1, manual_trending=['标题1', '标题2', '标题3', '标题4'])
"""
print(error_msg)
raise Exception("Step 1 需要手动提供爆款数据")
# 提取主题方向
themes = self._extract_themes_from_titles(trending_videos)
self.step_outputs[1] = {
'selected_keywords': selected_keywords,
'trending_videos': trending_videos,
'final_selection': trending_videos[:4],
'themes': themes
}
print(f" ✅ Step 1 完成")
print(f" 关键词: {selected_keywords}")
print(f" 选定爆款: {len(trending_videos[:4])} 个")
print(f" 提取主题: {themes}\n")
return self.step_outputs[1]
def _extract_themes_from_titles(self, titles: List[str]) -> List[str]:
"""从爆款标题提取主题方向"""
themes = []
# 简单实现:提取标题中的关键词作为主题
for title in titles:
# 这里可以实现更复杂的主题提取逻辑
themes.append(title[:20] if len(title) > 20 else title)
return themes
# ========== Step 2: 读取平台规则 ==========
def _run_step2(self) -> Dict:
"""Step 2: 读取平台规则"""
print("Step 2: 读取平台规则...")
# 获取当前平台的规则文件路径
rules_path = self._get_platform_rules_path()
if not rules_path.exists():
self.warnings.append(f"规则文件不存在: {rules_path}")
print(f" ⚠️ 规则文件不存在,使用默认规则")
rules_summary = self._get_default_rules()
else:
# 读取规则文件并解析
try:
with open(rules_path, 'r', encoding='utf-8') as f:
content = f.read()
rules_summary = self._parse_rules(content)
print(f" 从文件读取规则: {rules_path}")
except Exception as e:
self.warnings.append(f"读取规则文件失败: {e}")
rules_summary = self._get_default_rules()
self.step_outputs[2] = {
'rules_file': str(rules_path),
'rules_exists': rules_path.exists(),
'rules_summary': rules_summary
}
print(f" ✅ Step 2 完成,提取 {len(rules_summary)} 项关键规则\n")
return self.step_outputs[2]
def _get_default_rules(self) -> List[Dict]:
"""获取默认规则"""
return [
{'name': '时长要求', 'content': '请查看平台配置'},
{'name': '核心指标', 'content': '请查看平台配置'},
{'name': '引流限制', 'content': '请查看平台配置'},
{'name': '原创要求', 'content': '请查看平台配置'},
{'name': '互动率要求', 'content': '请查看平台配置'},
{'name': '违规红线', 'content': '请查看平台配置'},
{'name': '文案要求', 'content': '请查看平台配置'}
]
def _parse_rules(self, content: str) -> List[Dict]:
"""解析规则文件内容"""
# 简化实现:提取关键规则表格
rules = []
lines = content.split('\n')
in_table = False
for line in lines:
if '|' in line and ('规则项' in line or '时长要求' in line or '核心指标' in line):
in_table = True
elif in_table and line.strip() and '|' in line:
parts = [p.strip() for p in line.split('|') if p.strip()]
if len(parts) >= 2 and parts[0] not in ['规则项', '---']:
rules.append({'name': parts[0], 'content': parts[1]})
elif in_table and not line.strip().startswith('|'):
in_table = False
return rules if rules else self._get_default_rules()
def _get_platform_library_path(self) -> str:
"""获取当前平台的文案库路径
优先从 user_config.json 的 copy_libraries 配置获取
如果没有配置,返回空字符串
Returns:
文案库文件路径
"""
config = self.config_mgr.get_config()
# 优先使用新的 copy_libraries 配置
copy_libraries = config.get('copy_libraries', {})
if self.platform_en in copy_libraries:
return copy_libraries[self.platform_en]
# 向后兼容:使用旧的 copy_library_path
return config.get('copy_library_path', '')
def _get_platform_rules_path(self) -> Path:
"""获取当前平台的规则文档路径
优先从 user_config.json 的 rules_files 配置获取
如果没有配置,使用默认路径
Returns:
规则文档路径
"""
config = self.config_mgr.get_config()
# 优先使用新的 rules_files 配置
rules_files = config.get('rules_files', {})
if self.platform_en in rules_files:
path = rules_files[self.platform_en]
# 支持相对路径和绝对路径
if Path(path).is_absolute():
return Path(path)
else:
return Path(self.config_mgr.skill_path) / path
# 向后兼容:使用默认路径
return Path(self.config_mgr.skill_path) / 'references' / 'platform_rules' / f'{self.platform_en}_rules.md'
# ========== Step 3: 搜索外网平台 ==========
def _run_step3(self, manual_external: Optional[List[str]] = None,
on_error_action: Optional[str] = None) -> Dict:
"""Step 3: 搜索外网平台(必须执行步骤,但出错时用户可选择)
Args:
manual_external: 手动提供的外网爆款列表
on_error_action: 错误处理方式('skip', 'cancel', 'retry' 或 None等待用户输入)
Returns:
步骤执行结果,如果用户选择跳过则返回空数据
"""
print("Step 3: 搜索外网平台...")
# 获取外网关键词池(优先从 user_config.json 读取)
external_keywords = []
user_config_path = self.config_mgr.skill_path / 'config' / 'user_config.json'
try:
if user_config_path.exists():
with open(user_config_path, 'r', encoding='utf-8') as f:
user_config = json.load(f)
external_keywords = user_config.get('external_keywords', [])
# 如果 external_keywords 为空,但 external_search.auto_collect 为 true
if not external_keywords:
external_search = user_config.get('external_search', {})
if external_search.get('auto_collect', False):
external_keywords = external_search.get('default_keywords', [])
# 如果 default_keywords 也为空,自动生成 trending 搜索词
if not external_keywords:
# 模拟自动获取热门:使用通用 trending 类别
trending_categories = [
'viral food', 'trending cooking', 'popular farm life',
'viral recipe', 'trending countryside', 'hot food video'
]
external_keywords = random.sample(trending_categories, 3)
print(f" 自动获取 trending 类别: {external_keywords}")
else:
print(f" 使用 external_search 默认关键词: {external_keywords}")
except Exception as e:
print(f" 读取 user_config.json 失败: {e}")
if not external_keywords:
error_msg = "外网关键词池为空,请在 user_config.json 中配置 external_keywords"
print(f" ⚠️ {error_msg}")
# 根据配置或用户选择处理错误
action = on_error_action or self._prompt_user_for_action(
"Step 3 遇到问题",
error_msg,
["重试(retry)", "跳过(skip)", "取消(cancel)"]
)
if action in ['skip', '跳过', 's']:
print(" [WARN] 用户选择跳过 Step 3")
self.step_outputs[3] = {
'external_trending': [],
'final_selection': [],
'skipped': True,
'note': '用户选择跳过:外网关键词池为空',
'warning': error_msg
}
return self.step_outputs[3]
elif action in ['cancel', '取消', 'c']:
raise Exception("用户取消执行")
else: # retry
raise Exception(f"{error_msg},请配置后重试")
# 随机抽取3个关键词
selected_keywords = random.sample(external_keywords, min(3, len(external_keywords)))
print(f" 抽取外网关键词: {selected_keywords}")
# 搜索或手动输入
if manual_external and len(manual_external) > 0:
print(f" 使用手动提供的外网爆款: {len(manual_external)} 个")
external_trending = manual_external[:1]
else:
# 检查是否处于 auto_collect 模式
try:
with open(user_config_path, 'r', encoding='utf-8') as f:
user_config = json.load(f)
external_search = user_config.get('external_search', {})
is_auto_collect = external_search.get('auto_collect', False)
except:
is_auto_collect = False
if is_auto_collect and not external_search.get('default_keywords', []):
# auto_collect=true 且没有预设关键词:调用外网搜索插件
print(f" auto_collect=true,调用外网搜索插件...")
try:
# 调用 web_search 工具获取热门
from openclaw.tools import web_search
result = web_search(
query="TikTok trending food videos 2025",
count=20
)
# 解析返回的热门视频标题
external_trending = []
for item in result.get('results', []):
title = item.get('title', '').strip()
if title and len(title) > 10:
external_trending.append(title)
if external_trending:
print(f" 搜索到外网热门: {len(external_trending)} 条")
else:
# 搜索结果为空,正常报错
raise Exception("外网搜索结果为空,未获取到热门视频")
except Exception as e:
# 搜索失败,正常报错
raise Exception(f"外网搜索失败: {e}")
else:
# 未提供数据,询问用户如何处理
error_msg = "未提供外网爆款数据"
print(f" ⚠️ {error_msg}")
action = on_error_action or self._prompt_user_for_action(
"Step 3 需要外网爆款数据",
"外网搜索需要人工提供爆款数据,或通过搜索工具获取",
["跳过(skip)", "取消(cancel)", "重试(retry)"]
)
if action in ['skip', '跳过', 's']:
print(" [WARN] 用户选择跳过 Step 3")
self.step_outputs[3] = {
'selected_keywords': selected_keywords,
'external_trending': [],
'final_selection': [],
'skipped': True,
'note': '用户选择跳过:未提供外网数据',
'warning': error_msg
}
return self.step_outputs[3]
elif action in ['cancel', '取消', 'c']:
raise Exception("用户取消执行")
else: # retry - 需要提供数据
raise Exception(f"{error_msg},请提供 manual_external 参数后重试")
self.step_outputs[3] = {
'selected_keywords': selected_keywords,
'external_trending': external_trending,
'final_selection': external_trending[:1] if external_trending else [],
'skipped': False
}
print(f" [OK] Step 3 完成\n")
return self.step_outputs[3]
def _prompt_user_for_action(self, title: str, message: str,
options: List[str]) -> str:
"""提示用户选择操作(交互模式)
Args:
title: 提示标题
message: 提示消息
options: 可选操作列表
Returns:
用户选择的操作
"""
if not self.interactive:
# 非交互模式,默认选择第一个非重试选项
print(f"\n {'='*50}")
print(f" {title}")
print(f" {'='*50}")
print(f" {message}")
print(f" 可选操作: {', '.join(options)}")
print(f" 注意: 当前为非交互模式,请使用 on_error_action 参数指定处理方式")
print(f" 示例: workflow.run_step(3, on_error_action='skip')")
print(f" {'='*50}\n")
raise Exception(f"{title}: {message}。请在交互模式下运行或指定 on_error_action 参数")
print(f"\n {'='*50}")
print(f" {title}")
print(f" {'='*50}")
print(f" {message}")
print(f"\n 请选择操作:")
for i, opt in enumerate(options, 1):
print(f" {i}. {opt}")
print(f" {'='*50}\n")
# 设置暂停状态,等待外部输入
self.paused = True
self.pending_step = 3
# 在真实交互环境中,这里会等待用户输入
# 由于当前为代码示例,返回一个占位值
return "skip"
# ========== Step 4: 同质化检查 ==========
def _run_step4(self) -> Dict:
"""Step 4: 同质化检查
检查新生成的脚本主题是否与文案库已有脚本重复
"""
print("Step 4: 同质化检查...")
# 获取当前平台的文案库路径
library_path = self._get_platform_library_path()
if not library_path or not Path(library_path).exists():
print(f" [INFO] 文案库不存在: {library_path}")
print(" [INFO] 跳过同质化检查")
self.step_outputs[4] = {
'themes_to_avoid': [],
'existing_scripts': [],
'skipped': True,
'note': f'文案库不存在: {library_path}'
}
return self.step_outputs[4]
try:
# 读取已有脚本(从Excel提取主题)
existing_themes = self._extract_existing_themes(library_path)
if existing_themes:
print(f" 发现 {len(existing_themes)} 个已有主题")
print(f" 示例: {existing_themes[:3]}")
else:
print(f" 文案库为空,无同质化问题")
self.step_outputs[4] = {
'themes_to_avoid': existing_themes,
'existing_scripts_count': len(existing_themes),
'skipped': False
}
except Exception as e:
print(f" ⚠️ 读取文案库失败: {e}")
self.step_outputs[4] = {
'themes_to_avoid': [],
'error': str(e),
'skipped': True
}
print(f" ✅ Step 4 完成\n")
return self.step_outputs[4]
def _extract_existing_themes(self, library_path: str) -> List[str]:
"""从文案库提取已有主题"""
themes = []
try:
# 使用 ExcelManager 读取
import openpyxl
wb = openpyxl.load_workbook(library_path, read_only=True)
ws = wb.active
# 读取A列(假设主题在A列)
for row in ws.iter_rows(min_row=2, max_col=1, values_only=True):
if row[0]:
# 提取主题(简单实现:取前20字作为主题)
theme = str(row[0])[:20]
if theme not in themes:
themes.append(theme)
wb.close()
except Exception as e:
print(f" 读取Excel失败: {e}")
return themes
# ========== Step 5: 格式检查 ==========
def _run_step5(self) -> Dict:
"""Step 5: 格式检查"""
print("Step 5: 格式检查...")
# 获取当前平台的文案库路径
library_path = self._get_platform_library_path()
if not library_path or not Path(library_path).exists():
print(f" [WARN] 文案库不存在: {library_path}")
print(" [INFO] 将创建新文件")
self.step_outputs[5] = {
'format_confirmed': True,
'is_new_file': True,
'format_details': self._get_default_format()
}
return self.step_outputs[5]
try:
# 创建 FormatChecker 实例
self.format_checker = FormatChecker(platform=self.platform_en)
# 使用 FormatChecker 检查格式
format_valid = self.format_checker.check_excel_format(library_path)
format_details = self.format_checker.get_format_details(library_path)
if format_valid:
print(f" 格式检查通过")
self.step_outputs[5] = {
'format_confirmed': True,
'format_details': format_details,
'is_new_file': False
}
else:
print(f" ⚠️ 格式存在问题,将尝试修复")
self.warnings.append("文案库格式存在问题")
self.step_outputs[5] = {
'format_confirmed': True,
'format_details': format_details,
'needs_fix': True
}
except Exception as e:
print(f" ⚠️ 格式检查失败: {e}")
self.warnings.append(f"格式检查失败: {e}")
self.step_outputs[5] = {
'format_confirmed': True,
'format_details': self._get_default_format(),
'error': str(e)
}
print(f" ✅ Step 5 完成\n")
return self.step_outputs[5]
def _get_default_format(self) -> Dict:
"""获取默认格式配置"""
return {
'title_row': {
'font': '微软雅黑',
'size': 14,
'bold': True,
'color': 'FFFFFF',
'fill': 'FF4472C4',
'alignment': 'center',
'row_height': 20.4
},
'data_row': {
'font': '宋体',
'size': 11,
'bold': False,
'alignment': 'left',
'row_height': 49.95
}
}
# ========== Step 6: 生成脚本 ==========
def _run_step6(self) -> Dict:
"""Step 6: 生成脚本"""
print("Step 6: 生成脚本...")
# 获取前5步的数据
trending = self.step_outputs.get(1, {}).get('final_selection', [])
rules = self.step_outputs.get(2, {}).get('rules_summary', [])
external = self.step_outputs.get(3, {}).get('final_selection', [])
themes_to_avoid = self.step_outputs.get(4, {}).get('themes_to_avoid', [])
if not trending:
raise Exception("Step 1 未提供爆款数据,无法生成脚本")
# 计算分镜数量
platform_config = self.platform_config
total_duration = platform_config.get('total_duration', 120) # 默认2分钟
segment_duration = platform_config.get('segment_duration', 8) # 默认8秒
num_segments = platform_config.get('segments_per_video',
int(total_duration / segment_duration))
print(f" 平台配置: 总时长{total_duration}秒, 分镜{num_segments}个")
print(f" 参考爆款: {len(trending)}个")
print(f" 需避开主题: {len(themes_to_avoid)}个")
# 使用 ScriptGenerator 生成脚本
scripts = []
script_count = min(5, len(trending)) # 最多生成5条
# 创建 ScriptGenerator 实例
self.script_gen = ScriptGenerator(
platform=self.platform_en,
duration=f"{total_duration//60}-{total_duration//60+1}min",
keywords=self.step_outputs.get(1, {}).get('selected_keywords', []),
trending_titles=trending,
avoid_themes=themes_to_avoid
)
for i, trend in enumerate(trending[:script_count]):
try:
# 生成单条脚本
script = self.script_gen.generate(
reference_title=trend,
platform_rules=rules
)
scripts.append(script)
print(f" 脚本{i+1}生成完成: {script.get('title', '无标题')[:20]}...")
except Exception as e:
print(f" 脚本{i+1}生成失败: {e}")
self.warnings.append(f"脚本{i+1}生成失败: {e}")
self.step_outputs[6] = {
'scripts': scripts,
'script_count': len(scripts),
'platform_config': {
'total_duration': total_duration,
'segment_duration': segment_duration,
'num_segments': num_segments
}
}
print(f" ✅ Step 6 完成,生成 {len(scripts)} 条脚本\n")
return self.step_outputs[6]
# ========== Step 7: 合理性检查 ==========
def _run_step7(self) -> Dict:
"""Step 7: 合理性检查"""
print("Step 7: 合理性检查...")
scripts = self.step_outputs.get(6, {}).get('scripts', [])
if not scripts:
print(" ⚠️ 无脚本需要检查")
self.step_outputs[7] = {'validated_scripts': [], 'fix_count': 0}
return self.step_outputs[7]
validated_scripts = []
fix_count = 0
# 创建 FormatChecker 实例
self.format_checker = FormatChecker(platform=self.platform_en)
for i, script in enumerate(scripts):
try:
# 检查脚本合理性
check_result = self.format_checker.validate_script(script)
if check_result['valid']:
validated_scripts.append(script)
print(f" 脚本{i+1}检查通过")
else:
# 尝试修复
print(f" 脚本{i+1}存在问题: {check_result['issues']}")
fixed_script = self._fix_script(script, check_result['issues'])
validated_scripts.append(fixed_script)
fix_count += 1
print(f" 脚本{i+1}已修复")
except Exception as e:
print(f" 脚本{i+1}检查失败: {e}")
validated_scripts.append(script) # 保留原脚本
self.step_outputs[7] = {
'validated_scripts': validated_scripts,
'script_count': len(validated_scripts),
'fix_count': fix_count
}
print(f" ✅ Step 7 完成,{len(validated_scripts)}条脚本通过检查(修复{fix_count}条)\n")
return self.step_outputs[7]
def _fix_script(self, script: Dict, issues: List[str]) -> Dict:
"""修复脚本问题"""
fixed = script.copy()
for issue in issues:
if '时长' in issue or '时间' in issue:
# 修复时长问题
segments = fixed.get('segments', [])
for seg in segments:
if 'duration' in seg:
seg['duration'] = min(seg['duration'], 12) # 最大12秒
elif '字数' in issue or '内容' in issue:
# 修复内容长度问题
segments = fixed.get('segments', [])
for seg in segments:
if 'narration' in seg and len(seg['narration']) < 50:
seg['narration'] += '(内容已补充)'
return fixed
# ========== Step 8: 更新文案库 ==========
def _run_step8(self) -> Dict:
"""Step 8: 更新文案库(写入Excel)"""
print("Step 8: 更新文案库...")
validated_scripts = self.step_outputs.get(7, {}).get('validated_scripts', [])
if not validated_scripts:
print(" [WARN] 无脚本需要保存")
self.step_outputs[8] = {'write_status': 'skipped', 'rows_written': 0}
return self.step_outputs[8]
# 获取当前平台的文案库路径
library_path = self._get_platform_library_path()
if not library_path:
# 使用默认路径
library_path = str(self.config_mgr.skill_path / 'output' / f'{self.platform_en}_scripts.xlsx')
print(f" 使用默认路径: {library_path}")
try:
# 确保目录存在
Path(library_path).parent.mkdir(parents=True, exist_ok=True)
# 写入Excel
rows_written = 0
for script in validated_scripts:
success = self.excel_mgr.append_script(library_path, script, self.platform_en)
if success:
rows_written += len(script.get('segments', []))
else:
self.warnings.append(f"脚本写入失败: {script.get('title', 'unknown')}")
self.step_outputs[8] = {
'write_status': 'success',
'library_path': library_path,
'scripts_written': len(validated_scripts),
'rows_written': rows_written
}
print(f" ✅ Step 8 完成,写入 {len(validated_scripts)} 条脚本({rows_written}行)\n")
except Exception as e:
self.errors.append(f"Step 8 失败: {e}")
self.step_outputs[8] = {
'write_status': 'failed',
'error': str(e)
}
raise
return self.step_outputs[8]
# ========== Step 9: 全面检查对比 ==========
def _run_step9(self) -> bool:
"""Step 9: 全面检查对比(返回是否验证通过)"""
print("Step 9: 全面检查对比...")
library_path = self.step_outputs.get(8, {}).get('library_path', '')
if not library_path or not Path(library_path).exists():
print(" ⚠️ 文案库不存在,跳过验证")
self.step_outputs[9] = {'verified': True, 'skipped': True}
return True
try:
# 验证写入的内容
scripts_written = self.step_outputs.get(8, {}).get('scripts_written', 0)
# 创建 FormatChecker 实例
self.format_checker = FormatChecker(platform=self.platform_en)
# 简单验证:检查文件是否存在且大小合理
file_size = Path(library_path).stat().st_size
if file_size < 100: # 文件太小可能有问题
print(f" ⚠️ 文件大小异常: {file_size} bytes")
self.step_outputs[9] = {
'verified': False,
'issues': ['文件大小异常']
}
return False
# 验证格式
format_valid = self.format_checker.check_excel_format(library_path)
if format_valid:
print(f" ✅ Step 9 完成,验证通过\n")
self.step_outputs[9] = {
'verified': True,
'file_size': file_size,
'scripts_count': scripts_written
}
return True
else:
print(f" ❌ Step 9 验证失败,格式存在问题")
self.step_outputs[9] = {
'verified': False,
'issues': ['格式验证失败']
}
return False
except Exception as e:
print(f" ❌ Step 9 验证失败: {e}")
self.step_outputs[9] = {
'verified': False,
'error': str(e)
}
return False
# ========== Step 10: 生成报告 ==========
def _run_step10(self) -> Dict:
"""Step 10: 生成并保存执行报告"""
print("Step 10: 生成报告...")
# 生成报告内容
report = self._generate_report_content()
# 保存到本地
local_path = self._save_report_local(report)
# 尝试上传到飞书(如果有配置)
wiki_url = None
try:
wiki_url = self._upload_to_wiki(report)
if wiki_url:
print(f" 报告已上传至飞书: {wiki_url}")
except Exception as e:
print(f" ⚠️ 飞书上传失败: {e}")
print(f" 报告已保存到本地: {local_path}")
self.step_outputs[10] = {
'report': report,
'local_path': local_path,
'wiki_url': wiki_url,
'timestamp': datetime.now().isoformat()
}
print(f" ✅ Step 10 完成\n")
print(f"{'='*60}")
print(f"快导系列执行完成!")
print(f"报告保存位置: {local_path}")
if wiki_url:
print(f"飞书链接: {wiki_url}")
print(f"{'='*60}\n")
return self.step_outputs[10]
def _generate_report_title(self) -> str:
"""生成飞书报告标题"""
date_str = datetime.now().strftime("%Y-%m-%d")
return f"{date_str}-快导-{self.platform}"
def _generate_report_content(self) -> str:
"""生成报告内容(Markdown格式)"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
report = f"""# 快导(KD) 执行报告
## 基本信息
| 项目 | 内容 |
|:---|:---|
| 平台 | {self.platform} |
| 执行时间 | {timestamp} |
| 完成步骤 | {list(self.step_outputs.keys())} |
| 脚本生成数 | {self.step_outputs.get(6, {}).get('script_count', 0)} |
| 通过检查数 | {self.step_outputs.get(7, {}).get('script_count', 0)} |
## 各步骤执行详情
"""
# 添加各步骤详情
for step_num in range(1, 11):
if step_num in self.step_outputs:
report += self._format_step_report(step_num)
# 添加错误和警告
if self.errors:
report += "\n## 错误记录\n\n"
for error in self.errors:
report += f"- ❌ {error}\n"
if self.warnings:
report += "\n## 警告记录\n\n"
for warning in self.warnings:
report += f"- ⚠️ {warning}\n"
return report
def _format_step_report(self, step_num: int) -> str:
"""格式化单步报告"""
output = self.step_outputs.get(step_num, {})
step_names = {
1: "搜索目标平台爆款",
2: "读取平台规则",
3: "搜索外网平台",
4: "同质化检查",
5: "格式检查",
6: "生成脚本",
7: "合理性检查",
8: "更新文案库",
9: "全面检查对比",
10: "生成报告"
}
content = f"### Step {step_num}: {step_names.get(step_num, 'Unknown')}\n\n"
# 根据步骤添加关键信息
if step_num == 1:
keywords = output.get('selected_keywords', [])
trending = output.get('final_selection', [])
content += f"- 抽取关键词: {', '.join(keywords)}\n"
content += f"- 选定爆款数: {len(trending)}\n"
for i, t in enumerate(trending[:4], 1):
content += f" {i}. {t[:50]}...\n"
elif step_num == 6:
scripts = output.get('scripts', [])
content += f"- 生成脚本数: {len(scripts)}\n"
for i, s in enumerate(scripts, 1):
title = s.get('title', '无标题')
content += f" {i}. {title[:40]}...\n"
elif step_num == 8:
content += f"- 写入状态: {output.get('write_status', 'unknown')}\n"
content += f"- 脚本数: {output.get('scripts_written', 0)}\n"
content += f"- 文案库路径: {output.get('library_path', 'N/A')}\n"
elif step_num == 9:
verified = output.get('verified', False)
content += f"- 验证结果: {'✅ 通过' if verified else '❌ 失败'}\n"
content += "\n"
return content
def _save_report_local(self, report: str) -> str:
"""保存报告到本地"""
reports_dir = self.config_mgr.skill_path / 'reports'
reports_dir.mkdir(exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{timestamp}_kd_{self.platform_en}.md"
filepath = reports_dir / filename
with open(filepath, 'w', encoding='utf-8') as f:
f.write(report)
return str(filepath)
def _upload_to_wiki(self, report: str) -> Optional[str]:
"""上传报告到飞书知识库(可选功能,需手动配置)
安全说明:
- 此功能需要用户手动配置飞书认证(lark-cli auth login)
- 不会自动上传任何内容,仅生成本地报告
- 如需上传,请手动运行 lark-cli 命令(见下方注释示例)
"""
# 获取飞书配置
wiki_space_id = self.config_mgr.get_config().get('report_space_id', '')
if not wiki_space_id:
print(" 未配置飞书知识库空间ID,跳过上传")
print(" 报告已保存到本地 reports/ 目录")
return None
# 生成报告标题
report_title = self._generate_report_title()
# 当前实现:仅保存到本地,不上传到飞书
# 如需上传到飞书,请手动运行以下命令:
# lark-cli docs +create --title "{report_title}" --wiki-space "{wiki_space_id}" --markdown "{report_content}"
# 并确保已运行:lark-cli auth login
print(f" 飞书上传功能说明:")
print(f" - 报告标题:{report_title}")
print(f" - 目标空间ID:{wiki_space_id}")
print(f" - 如需上传,请手动运行:lark-cli docs +create --title \"{report_title}\" --wiki-space \"{wiki_space_id}\" --markdown \"...\"")
print(f" - 报告已保存到本地,路径见上方输出")
return None
FILE:scripts/__init__.py
"""
快导(KD) Skill - Python工具脚本包
提供脚本生成、格式检查、Excel操作等功能
"""
import sys
import io
# Windows 编码修复 - 必须在所有导入之前
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
__version__ = "1.0.0"
__author__ = "快导(KD)"
from .script_generator import ScriptGenerator
from .format_checker import FormatChecker
from .excel_manager import ExcelManager
from .config_manager import ConfigManager
from .feishu_permission_helper import FeishuPermissionHelper
from .workflow_manager import WorkflowManager
__all__ = [
"ScriptGenerator",
"FormatChecker",
"ExcelManager",
"ConfigManager",
"FeishuPermissionHelper",
"WorkflowManager"
]
FILE:setup.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
快导(KD) - 短视频脚本批量生成与管理
"""
from setuptools import setup, find_packages
from pathlib import Path
# 读取README
readme_path = Path(__file__).parent / "SKILL.md"
long_description = readme_path.read_text(encoding="utf-8") if readme_path.exists() else ""
setup(
name="kd",
version="1.0.0",
description="快导(KD) - 短视频脚本批量生成与管理",
long_description=long_description,
long_description_content_type="text/markdown",
author="User",
python_requires=">=3.7",
packages=find_packages(),
install_requires=[
"openpyxl>=3.0.0",
],
extras_require={
"dev": [
"pytest>=6.0",
"black>=21.0",
],
},
entry_points={
"console_scripts": [
"kd=kd:main",
],
},
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
)
FILE:tests/config_manager_win.py
# -*- coding: utf-8 -*-
"""
配置管理器测试 - Windows版本
对应: scripts/config_manager.py
"""
import sys
import io
import os
# 设置标准输出编码为UTF-8
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
from config_manager import ConfigManager, ConfigNotSetError
def test_config():
"""测试配置管理功能"""
print("\n[测试1] 初始化配置管理器")
config_mgr = ConfigManager()
print(f"[OK] Skill路径: {config_mgr.skill_path}")
print("\n[测试2] 加载配置")
config = config_mgr.load_config()
print(f"[OK] 配置版本: {config['metadata']['version']}")
print(f"[OK] 配置描述: 快导(KD) Skill - 平台参数配置")
print("\n[测试3] 获取平台配置 - 小红书")
xhs_config = config_mgr.get_platform_config("xiaohongshu")
print(f"[OK] 平台名称: {xhs_config['name']}")
print(f"[OK] 用户画像: {xhs_config['user_profile']}")
print(f"[OK] 内容风格: {xhs_config['content_style']}")
print(f"[OK] 总时长: {xhs_config['duration']['total']}")
print(f"[OK] 分镜时长: {xhs_config['duration']['segment']}")
print(f"[OK] 分镜数量: {xhs_config['duration']['segments_count']}")
print("\n[测试4] 计算分镜数量")
segments = config_mgr.calculate_segments("xiaohongshu", "2-3min")
print(f"[OK] 2-3分钟计算分镜数: {segments}")
print("\n[测试5] 验证配置")
validation = config_mgr.validate_config()
print("[OK] 配置验证结果:")
for key, value in validation.items():
status = "[OK] 已设置" if value else "[X] 未设置"
print(f" - {key}: {status}")
print("\n[测试6] 获取Excel路径(预期报错)")
try:
path = config_mgr.get_excel_path("xiaohongshu")
print(f"[OK] Excel路径: {path}")
except ConfigNotSetError as e:
print(f"[OK] 正确捕获错误: 配置项未设置")
print("\n[完成] 配置管理测试通过")
if __name__ == "__main__":
test_config()
FILE:tests/excel_manager_win.py
# -*- coding: utf-8 -*-
"""
Excel管理器测试 - Windows版本
对应: scripts/excel_manager.py
"""
import sys
import io
import os
import tempfile
# 设置标准输出编码为UTF-8
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
from openpyxl import Workbook
from excel_manager import ExcelManager
def create_test_excel(path):
"""创建测试Excel文件"""
wb = Workbook()
ws = wb.active
ws.title = "文案库"
# 标题行
headers = [
"视频文案", "时间段", "镜头", "运镜", "拍摄技巧",
"画面", "台词", "音效", "推荐音乐/BGM", "文案标签",
"使用状态", "关联活动", "发布日期", "备注"
]
for col, header in enumerate(headers, 1):
ws.cell(1, col).value = header
# 示例数据
ws.cell(2, 1).value = "示例脚本\n这是一个测试脚本"
ws.cell(2, 2).value = "0-6秒"
ws.cell(2, 3).value = "全景镜头,展现场景"
ws.cell(2, 11).value = "已使用"
wb.save(path)
return path
def test_excel():
"""测试Excel管理功能"""
# 创建临时测试文件
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as tmp:
test_file = tmp.name
try:
# 准备测试文件
create_test_excel(test_file)
print(f"[OK] 创建测试文件: {test_file}")
# 测试1: 加载Excel
print("\n[测试1] 加载Excel")
with ExcelManager(test_file) as em:
print("[OK] Excel加载成功")
print(f"[OK] 总行数: {em.ws.max_row}")
print(f"[OK] 总列数: {em.ws.max_column}")
# 测试2: 扫描格式
print("\n[测试2] 扫描格式")
with ExcelManager(test_file) as em:
format_info = em.scan_format()
print(f"[OK] 总行数: {format_info['total_rows']}")
print(f"[OK] 总列数: {format_info['total_columns']}")
print(f"[OK] 合并单元格数: {len(format_info['merged_cells'])}")
# 测试3: 获取已有脚本
print("\n[测试3] 获取已有脚本")
with ExcelManager(test_file) as em:
scripts = em.get_existing_scripts()
print(f"[OK] 发现 {len(scripts)} 个已有脚本")
for script in scripts:
print(f" - 第{script['row']}行: {script['title'][:30]}...")
# 测试4: 追加脚本
print("\n[测试4] 追加脚本")
test_script = {
"title": "测试脚本",
"story": "这是一个测试故事",
"segments": [
{
"time": "0-5秒",
"duration": 5,
"shot_desc": "特写镜头",
"movement_desc": "缓慢推进",
"tech_desc": "手持稳定器",
"scene_desc": "自然光",
"line": "测试台词",
"sound_desc": "环境音"
},
{
"time": "5-10秒",
"duration": 5,
"shot_desc": "中景",
"movement_desc": "固定",
"tech_desc": "三脚架",
"scene_desc": "室内光",
"line": "继续测试",
"sound_desc": "背景音乐"
}
],
"bgm": "轻音乐",
"activity": "",
"date": "2026-04-22",
"notes": "测试备注"
}
with ExcelManager(test_file) as em:
start_row = em.ws.max_row + 1
end_row = em.append_script(test_script, start_row)
em.save()
print(f"[OK] 脚本写入成功: 第{start_row}-{end_row}行")
# 测试5: 验证写入
print("\n[测试5] 验证写入")
with ExcelManager(test_file) as em:
result = em.validate_write(start_row, end_row)
print(f"[OK] 格式正确性: {result['format_valid']}")
print(f"[OK] 内容完整性: {result['content_complete']}")
if result['errors']:
print(f"[X] 错误: {result['errors']}")
print("\n[完成] Excel管理测试通过")
finally:
# 清理测试文件
if os.path.exists(test_file):
os.remove(test_file)
print("[OK] 清理测试文件")
if __name__ == "__main__":
test_excel()
FILE:tests/format_checker_win.py
# -*- coding: utf-8 -*-
"""
格式检查器测试 - Windows版本
对应: scripts/format_checker.py
"""
import sys
import io
import os
# 设置标准输出编码为UTF-8
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
from format_checker import FormatChecker
def create_test_script(valid=True):
"""创建测试脚本"""
if valid:
return {
"title": "测试脚本标题",
"theme": "测试主题",
"story": "这是一个完整的故事。开始介绍背景,然后展开情节,接着遇到问题,最后解决问题。这是一个关于周末采摘的故事,主人公一家三口来到农家院,先是被美景吸引,然后品尝美食,最后满载而归。整个故事有起承转合,总共超过两百字的叙述,每个环节都有详细的描写和情感变化。",
"total_duration": "2-3min",
"segments_count": 3,
"segments": [
{
"seg_id": 1,
"time": "0-5秒",
"duration": 5,
"shot_desc": "全景镜头,展现场景全貌,主体位于画面中央,光线柔和自然,背景虚化突出主题",
"movement_desc": "缓慢推进,速度均匀,从远景到中景的过渡,营造期待感",
"tech_desc": "使用稳定器手持拍摄,ISO400,光圈f/2.8,快门1/50s",
"scene_desc": "自然光线从窗户射入,暖色调,简洁构图,三分法则",
"line": "先来说说今天的故事,真的很精彩",
"sound_desc": "环境音为主,轻微的风声,音量控制在-20dB左右",
"bgm": "轻快背景音乐",
"tags": "#测试",
"status": "待使用"
},
{
"seg_id": 2,
"time": "5-12秒",
"duration": 7,
"shot_desc": "中景特写,展现细节,主体表情丰富,背景适度虚化",
"movement_desc": "固定机位,保持稳定,偶尔轻微晃动增加真实感",
"tech_desc": "固定三脚架,ISO800,光圈f/4,快门1/60s",
"scene_desc": "室内灯光,色温5500K,对比度适中,饱和度自然",
"line": "然后发生了意想不到的事情",
"sound_desc": "背景音乐渐强,配合画面情绪变化,营造氛围感",
"bgm": "轻快背景音乐",
"tags": "#测试",
"status": "待使用"
},
{
"seg_id": 3,
"time": "12-18秒",
"duration": 6,
"shot_desc": "特写镜头,聚焦表情,展现情感高潮,眼神有光",
"movement_desc": "缓慢拉远,从特写到全景的过渡,留下回味空间",
"tech_desc": "使用滑轨,ISO200,光圈f/1.8,快门1/100s",
"scene_desc": "逆光拍摄,轮廓光勾勒主体,金色调,梦幻感",
"line": "最后大家都很满意",
"sound_desc": "音效淡出,留下环境音,营造余韵和情感延续",
"bgm": "轻快背景音乐",
"tags": "#测试",
"status": "待使用"
}
]
}
else:
# 创建有问题的脚本
return {
"title": "测试脚本标题",
"theme": "测试主题",
"story": "短故事",
"total_duration": "2-3min",
"segments_count": 3,
"segments": [
{
"seg_id": 1,
"time": "0-5秒",
"duration": 15,
"shot_desc": "短",
"movement_desc": "短",
"tech_desc": "短",
"scene_desc": "短",
"line": "首先其次",
"sound_desc": "短"
}
]
}
def test_format():
"""测试格式检查功能"""
# 测试1: 正常脚本验证
print("\n[测试1] 正常脚本验证(应通过)")
checker = FormatChecker("xiaohongshu")
valid_script = create_test_script(valid=True)
passed, issues = checker.validate_script(valid_script)
print(f"[OK] 验证结果: {'通过' if passed else '未通过'}")
if issues:
for issue in issues:
print(f" [X] {issue}")
else:
print(" [OK] 无问题")
# 测试2: 有问题脚本验证
print("\n[测试2] 有问题脚本验证(应失败)")
invalid_script = create_test_script(valid=False)
passed, issues = checker.validate_script(invalid_script)
print(f"[OK] 验证结果: {'通过' if passed else '未通过(预期)'}")
print(f"[OK] 发现 {len(issues)} 个问题:")
for issue in issues:
print(f" - {issue}")
# 测试3: 批量验证
print("\n[测试3] 批量验证")
scripts = [create_test_script(valid=True), create_test_script(valid=False)]
results = checker.validate_batch(scripts)
print(f"[OK] 总脚本数: {results['total']}")
print(f"[OK] 通过: {results['passed']}")
print(f"[OK] 失败: {results['failed']}")
# 测试4: 自动修复
print("\n[测试4] 自动修复")
checker_fix = FormatChecker("xiaohongshu")
broken_script = create_test_script(valid=False)
print("修复前:")
print(f" - 镜头描述: {broken_script['segments'][0]['shot_desc']}")
print(f" - 台词: {broken_script['segments'][0]['line']}")
fixed_script = checker_fix.auto_fix(broken_script)
print("修复后:")
print(f" - 镜头描述: {fixed_script['segments'][0]['shot_desc']}")
print(f" - 台词: {fixed_script['segments'][0]['line']}")
print(f"\n[OK] 修复记录:")
print(checker_fix.get_fix_report())
# 测试5: 抖音平台检查
print("\n[测试5] 抖音平台验证")
dy_checker = FormatChecker("douyin")
dy_script = create_test_script(valid=True)
passed, issues = dy_checker.validate_script(dy_script)
print(f"[OK] 抖音验证结果: {'通过' if passed else '未通过'}")
# 测试6: 视频号平台检查
print("\n[测试6] 视频号平台验证(标题限制)")
sph_checker = FormatChecker("shipinhao")
# 测试过长标题
long_title_script = create_test_script(valid=True)
long_title_script["title"] = "这是一个超过16个字符的很长很长的标题"
passed, issues = sph_checker.validate_script(long_title_script)
print(f"[OK] 长标题验证: {'通过' if passed else '未通过(预期)'}")
# 测试标点标题
punct_title_script = create_test_script(valid=True)
punct_title_script["title"] = "标题,有标点。"
passed, issues = sph_checker.validate_script(punct_title_script)
print(f"[OK] 标点标题验证: {'通过' if passed else '未通过(预期)'}")
print("\n[完成] 格式检查测试通过")
if __name__ == "__main__":
test_format()
FILE:tests/linux/config_manager_linux.py
# -*- coding: utf-8 -*-
"""
配置管理器测试 - Linux版本
对应: scripts/config_manager.py
"""
import sys
import os
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
from config_manager import ConfigManager, ConfigNotSetError
def test_config():
"""测试配置管理功能"""
print("\n[测试1] 初始化配置管理器")
config_mgr = ConfigManager()
print(f"[OK] Skill路径: {config_mgr.skill_path}")
print("\n[测试2] 加载配置")
config = config_mgr.load_config()
print(f"[OK] 配置版本: {config['metadata']['version']}")
print(f"[OK] 配置描述: 快导(KD) Skill - 平台参数配置")
print("\n[测试3] 获取平台配置 - 小红书")
xhs_config = config_mgr.get_platform_config("xiaohongshu")
print(f"[OK] 平台名称: {xhs_config['name']}")
print(f"[OK] 用户画像: {xhs_config['user_profile']}")
print(f"[OK] 内容风格: {xhs_config['content_style']}")
print(f"[OK] 总时长: {xhs_config['duration']['total']}")
print(f"[OK] 分镜时长: {xhs_config['duration']['segment']}")
print(f"[OK] 分镜数量: {xhs_config['duration']['segments_count']}")
print("\n[测试4] 计算分镜数量")
segments = config_mgr.calculate_segments("xiaohongshu", "2-3min")
print(f"[OK] 2-3分钟计算分镜数: {segments}")
print("\n[测试5] 验证配置")
validation = config_mgr.validate_config()
print("[OK] 配置验证结果:")
for key, value in validation.items():
status = "[OK] 已设置" if value else "[X] 未设置"
print(f" - {key}: {status}")
print("\n[测试6] 获取Excel路径(预期报错)")
try:
path = config_mgr.get_excel_path("xiaohongshu")
print(f"[OK] Excel路径: {path}")
except ConfigNotSetError as e:
print(f"[OK] 正确捕获错误: 配置项未设置")
print("\n[完成] 配置管理测试通过")
if __name__ == "__main__":
test_config()
FILE:tests/linux/excel_manager_linux.py
# -*- coding: utf-8 -*-
"""
Excel管理器测试 - Linux版本
对应: scripts/excel_manager.py
"""
import sys
import os
import tempfile
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
from openpyxl import Workbook
from excel_manager import ExcelManager
def create_test_excel(path):
"""创建测试Excel文件"""
wb = Workbook()
ws = wb.active
ws.title = "文案库"
# 标题行
headers = [
"视频文案", "时间段", "镜头", "运镜", "拍摄技巧",
"画面", "台词", "音效", "推荐音乐/BGM", "文案标签",
"使用状态", "关联活动", "发布日期", "备注"
]
for col, header in enumerate(headers, 1):
ws.cell(1, col).value = header
# 示例数据
ws.cell(2, 1).value = "示例脚本\n这是一个测试脚本"
ws.cell(2, 2).value = "0-6秒"
ws.cell(2, 3).value = "全景镜头,展现场景"
ws.cell(2, 11).value = "已使用"
wb.save(path)
return path
def test_excel():
"""测试Excel管理功能"""
# 创建临时测试文件
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as tmp:
test_file = tmp.name
try:
# 准备测试文件
create_test_excel(test_file)
print(f"[OK] 创建测试文件: {test_file}")
# 测试1: 加载Excel
print("\n[测试1] 加载Excel")
with ExcelManager(test_file) as em:
print("[OK] Excel加载成功")
print(f"[OK] 总行数: {em.ws.max_row}")
print(f"[OK] 总列数: {em.ws.max_column}")
# 测试2: 扫描格式
print("\n[测试2] 扫描格式")
with ExcelManager(test_file) as em:
format_info = em.scan_format()
print(f"[OK] 总行数: {format_info['total_rows']}")
print(f"[OK] 总列数: {format_info['total_columns']}")
print(f"[OK] 合并单元格数: {len(format_info['merged_cells'])}")
# 测试3: 获取已有脚本
print("\n[测试3] 获取已有脚本")
with ExcelManager(test_file) as em:
scripts = em.get_existing_scripts()
print(f"[OK] 发现 {len(scripts)} 个已有脚本")
for script in scripts:
print(f" - 第{script['row']}行: {script['title'][:30]}...")
# 测试4: 追加脚本
print("\n[测试4] 追加脚本")
test_script = {
"title": "测试脚本",
"story": "这是一个测试故事",
"segments": [
{
"time": "0-5秒",
"duration": 5,
"shot_desc": "特写镜头",
"movement_desc": "缓慢推进",
"tech_desc": "手持稳定器",
"scene_desc": "自然光",
"line": "测试台词",
"sound_desc": "环境音"
},
{
"time": "5-10秒",
"duration": 5,
"shot_desc": "中景",
"movement_desc": "固定",
"tech_desc": "三脚架",
"scene_desc": "室内光",
"line": "继续测试",
"sound_desc": "背景音乐"
}
],
"bgm": "轻音乐",
"activity": "",
"date": "2026-04-22",
"notes": "测试备注"
}
with ExcelManager(test_file) as em:
start_row = em.ws.max_row + 1
end_row = em.append_script(test_script, start_row)
em.save()
print(f"[OK] 脚本写入成功: 第{start_row}-{end_row}行")
# 测试5: 验证写入
print("\n[测试5] 验证写入")
with ExcelManager(test_file) as em:
result = em.validate_write(start_row, end_row)
print(f"[OK] 格式正确性: {result['format_valid']}")
print(f"[OK] 内容完整性: {result['content_complete']}")
if result['errors']:
print(f"[X] 错误: {result['errors']}")
print("\n[完成] Excel管理测试通过")
finally:
# 清理测试文件
if os.path.exists(test_file):
os.remove(test_file)
print("[OK] 清理测试文件")
if __name__ == "__main__":
test_excel()
FILE:tests/linux/format_checker_linux.py
# -*- coding: utf-8 -*-
"""
格式检查器测试 - Linux版本
对应: scripts/format_checker.py
"""
import sys
import os
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
from format_checker import FormatChecker
def create_test_script(valid=True):
"""创建测试脚本"""
if valid:
return {
"title": "测试脚本标题",
"theme": "测试主题",
"story": "这是一个完整的故事。开始介绍背景,然后展开情节,接着遇到问题,最后解决问题。这是一个关于周末采摘的故事,主人公一家三口来到农家院,先是被美景吸引,然后品尝美食,最后满载而归。整个故事有起承转合,总共超过两百字的叙述,每个环节都有详细的描写和情感变化。",
"total_duration": "2-3min",
"segments_count": 3,
"segments": [
{
"seg_id": 1,
"time": "0-5秒",
"duration": 5,
"shot_desc": "全景镜头,展现场景全貌,主体位于画面中央,光线柔和自然,背景虚化突出主题",
"movement_desc": "缓慢推进,速度均匀,从远景到中景的过渡,营造期待感",
"tech_desc": "使用稳定器手持拍摄,ISO400,光圈f/2.8,快门1/50s",
"scene_desc": "自然光线从窗户射入,暖色调,简洁构图,三分法则",
"line": "先来说说今天的故事,真的很精彩",
"sound_desc": "环境音为主,轻微的风声,音量控制在-20dB左右",
"bgm": "轻快背景音乐",
"tags": "#测试",
"status": "待使用"
},
{
"seg_id": 2,
"time": "5-12秒",
"duration": 7,
"shot_desc": "中景特写,展现细节,主体表情丰富,背景适度虚化",
"movement_desc": "固定机位,保持稳定,偶尔轻微晃动增加真实感",
"tech_desc": "固定三脚架,ISO800,光圈f/4,快门1/60s",
"scene_desc": "室内灯光,色温5500K,对比度适中,饱和度自然",
"line": "然后发生了意想不到的事情",
"sound_desc": "背景音乐渐强,配合画面情绪变化,营造氛围感",
"bgm": "轻快背景音乐",
"tags": "#测试",
"status": "待使用"
},
{
"seg_id": 3,
"time": "12-18秒",
"duration": 6,
"shot_desc": "特写镜头,聚焦表情,展现情感高潮,眼神有光",
"movement_desc": "缓慢拉远,从特写到全景的过渡,留下回味空间",
"tech_desc": "使用滑轨,ISO200,光圈f/1.8,快门1/100s",
"scene_desc": "逆光拍摄,轮廓光勾勒主体,金色调,梦幻感",
"line": "最后大家都很满意",
"sound_desc": "音效淡出,留下环境音,营造余韵和情感延续",
"bgm": "轻快背景音乐",
"tags": "#测试",
"status": "待使用"
}
]
}
else:
# 创建有问题的脚本
return {
"title": "测试脚本标题",
"theme": "测试主题",
"story": "短故事",
"total_duration": "2-3min",
"segments_count": 3,
"segments": [
{
"seg_id": 1,
"time": "0-5秒",
"duration": 15,
"shot_desc": "短",
"movement_desc": "短",
"tech_desc": "短",
"scene_desc": "短",
"line": "首先其次",
"sound_desc": "短"
}
]
}
def test_format():
"""测试格式检查功能"""
# 测试1: 正常脚本验证
print("\n[测试1] 正常脚本验证(应通过)")
checker = FormatChecker("xiaohongshu")
valid_script = create_test_script(valid=True)
passed, issues = checker.validate_script(valid_script)
print(f"[OK] 验证结果: {'通过' if passed else '未通过'}")
if issues:
for issue in issues:
print(f" [X] {issue}")
else:
print(" [OK] 无问题")
# 测试2: 有问题脚本验证
print("\n[测试2] 有问题脚本验证(应失败)")
invalid_script = create_test_script(valid=False)
passed, issues = checker.validate_script(invalid_script)
print(f"[OK] 验证结果: {'通过' if passed else '未通过(预期)'}")
print(f"[OK] 发现 {len(issues)} 个问题:")
for issue in issues:
print(f" - {issue}")
# 测试3: 批量验证
print("\n[测试3] 批量验证")
scripts = [create_test_script(valid=True), create_test_script(valid=False)]
results = checker.validate_batch(scripts)
print(f"[OK] 总脚本数: {results['total']}")
print(f"[OK] 通过: {results['passed']}")
print(f"[OK] 失败: {results['failed']}")
# 测试4: 自动修复
print("\n[测试4] 自动修复")
checker_fix = FormatChecker("xiaohongshu")
broken_script = create_test_script(valid=False)
print("修复前:")
print(f" - 镜头描述: {broken_script['segments'][0]['shot_desc']}")
print(f" - 台词: {broken_script['segments'][0]['line']}")
fixed_script = checker_fix.auto_fix(broken_script)
print("修复后:")
print(f" - 镜头描述: {fixed_script['segments'][0]['shot_desc']}")
print(f" - 台词: {fixed_script['segments'][0]['line']}")
print(f"\n[OK] 修复记录:")
print(checker_fix.get_fix_report())
# 测试5: 抖音平台检查
print("\n[测试5] 抖音平台验证")
dy_checker = FormatChecker("douyin")
dy_script = create_test_script(valid=True)
passed, issues = dy_checker.validate_script(dy_script)
print(f"[OK] 抖音验证结果: {'通过' if passed else '未通过'}")
# 测试6: 视频号平台检查
print("\n[测试6] 视频号平台验证(标题限制)")
sph_checker = FormatChecker("shipinhao")
# 测试过长标题
long_title_script = create_test_script(valid=True)
long_title_script["title"] = "这是一个超过16个字符的很长很长的标题"
passed, issues = sph_checker.validate_script(long_title_script)
print(f"[OK] 长标题验证: {'通过' if passed else '未通过(预期)'}")
# 测试标点标题
punct_title_script = create_test_script(valid=True)
punct_title_script["title"] = "标题,有标点。"
passed, issues = sph_checker.validate_script(punct_title_script)
print(f"[OK] 标点标题验证: {'通过' if passed else '未通过(预期)'}")
print("\n[完成] 格式检查测试通过")
if __name__ == "__main__":
test_format()
FILE:tests/linux/script_generator_linux.py
# -*- coding: utf-8 -*-
"""
脚本生成器测试 - Linux版本
对应: scripts/script_generator.py
"""
import sys
import os
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
from script_generator import ScriptGenerator
def test_generator():
"""测试脚本生成功能"""
# 测试1: 初始化生成器(小红书)
print("\n[测试1] 初始化生成器 - 小红书")
generator = ScriptGenerator(
platform="xiaohongshu",
duration="2-3min",
keywords=["采摘", "农家菜", "周末"],
trending_titles=["周末去哪儿", "春日采摘攻略", "农家美食推荐"],
avoid_themes=["已发布主题"]
)
print(f"[OK] 平台: {generator.platform}")
print(f"[OK] 分镜数量: {generator.segments_count}")
print(f"[OK] 关键词: {generator.keywords}")
# 测试2: 生成分镜时长分配
print("\n[测试2] 分镜时长分配")
durations = generator._distribute_duration()
print("[OK] 分配模式(开头快、中间稳、结尾慢):")
print(f" 前3个: {durations[:3]}秒(开场)")
print(f" 中间: ...{len(durations)-5}个...(主体)")
print(f" 后2个: {durations[-2:]}秒(结尾)")
# 测试3: 构建分镜结构
print("\n[测试3] 构建分镜结构")
segments = generator._build_segments_structure(durations[:3])
print(f"[OK] 生成了 {len(segments)} 个分镜")
for seg in segments:
print(f" - 分镜{seg['seg_id']}: {seg['time']}, {seg['duration']}秒")
# 测试4: 构建提示词
print("\n[测试4] 构建AI提示词")
prompt = generator.get_prompt_for_script(1)
print(f"[OK] 提示词长度: {len(prompt)} 字符")
print(f"[OK] 提示词预览(前200字):")
print(f" {prompt[:200]}...")
# 测试5: 生成单个脚本
print("\n[测试5] 生成单个脚本")
script = generator._generate_single_script(1)
print(f"[OK] 脚本ID: {script['script_id']}")
print(f"[OK] 标题: {script['title']}")
print(f"[OK] 主题: {script['theme']}")
print(f"[OK] 总时长: {script['total_duration']}")
print(f"[OK] 分镜数: {script['segments_count']}")
# 测试6: 生成多个脚本
print("\n[测试6] 生成多个脚本")
scripts = generator.generate(count=3)
print(f"[OK] 生成了 {len(scripts)} 个脚本")
for i, s in enumerate(scripts, 1):
print(f" - 脚本{i}: {s['segments_count']}个分镜")
# 测试7: 其他平台测试
print("\n[测试7] 抖音平台测试")
dy_generator = ScriptGenerator(
platform="douyin",
duration="1min"
)
print(f"[OK] 抖音分镜数: {dy_generator.segments_count}")
print("\n[测试8] 视频号平台测试")
sph_generator = ScriptGenerator(
platform="shipinhao",
duration="1-3min"
)
print(f"[OK] 视频号分镜数: {sph_generator.segments_count}")
print("\n[完成] 脚本生成测试通过")
if __name__ == "__main__":
test_generator()
FILE:tests/mac/config_manager_mac.py
# -*- coding: utf-8 -*-
"""
配置管理器测试 - macOS版本
对应: scripts/config_manager.py
"""
import sys
import os
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
from config_manager import ConfigManager, ConfigNotSetError
def test_config():
"""测试配置管理功能"""
print("\n[测试1] 初始化配置管理器")
config_mgr = ConfigManager()
print(f"[OK] Skill路径: {config_mgr.skill_path}")
print("\n[测试2] 加载配置")
config = config_mgr.load_config()
print(f"[OK] 配置版本: {config['metadata']['version']}")
print(f"[OK] 配置描述: 快导(KD) Skill - 平台参数配置")
print("\n[测试3] 获取平台配置 - 小红书")
xhs_config = config_mgr.get_platform_config("xiaohongshu")
print(f"[OK] 平台名称: {xhs_config['name']}")
print(f"[OK] 用户画像: {xhs_config['user_profile']}")
print(f"[OK] 内容风格: {xhs_config['content_style']}")
print(f"[OK] 总时长: {xhs_config['duration']['total']}")
print(f"[OK] 分镜时长: {xhs_config['duration']['segment']}")
print(f"[OK] 分镜数量: {xhs_config['duration']['segments_count']}")
print("\n[测试4] 计算分镜数量")
segments = config_mgr.calculate_segments("xiaohongshu", "2-3min")
print(f"[OK] 2-3分钟计算分镜数: {segments}")
print("\n[测试5] 验证配置")
validation = config_mgr.validate_config()
print("[OK] 配置验证结果:")
for key, value in validation.items():
status = "[OK] 已设置" if value else "[X] 未设置"
print(f" - {key}: {status}")
print("\n[测试6] 获取Excel路径(预期报错)")
try:
path = config_mgr.get_excel_path("xiaohongshu")
print(f"[OK] Excel路径: {path}")
except ConfigNotSetError as e:
print(f"[OK] 正确捕获错误: 配置项未设置")
print("\n[完成] 配置管理测试通过")
if __name__ == "__main__":
test_config()
FILE:tests/mac/excel_manager_mac.py
# -*- coding: utf-8 -*-
"""
Excel管理器测试 - macOS版本
对应: scripts/excel_manager.py
"""
import sys
import os
import tempfile
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
from openpyxl import Workbook
from excel_manager import ExcelManager
def create_test_excel(path):
"""创建测试Excel文件"""
wb = Workbook()
ws = wb.active
ws.title = "文案库"
# 标题行
headers = [
"视频文案", "时间段", "镜头", "运镜", "拍摄技巧",
"画面", "台词", "音效", "推荐音乐/BGM", "文案标签",
"使用状态", "关联活动", "发布日期", "备注"
]
for col, header in enumerate(headers, 1):
ws.cell(1, col).value = header
# 示例数据
ws.cell(2, 1).value = "示例脚本\n这是一个测试脚本"
ws.cell(2, 2).value = "0-6秒"
ws.cell(2, 3).value = "全景镜头,展现场景"
ws.cell(2, 11).value = "已使用"
wb.save(path)
return path
def test_excel():
"""测试Excel管理功能"""
# 创建临时测试文件
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as tmp:
test_file = tmp.name
try:
# 准备测试文件
create_test_excel(test_file)
print(f"[OK] 创建测试文件: {test_file}")
# 测试1: 加载Excel
print("\n[测试1] 加载Excel")
with ExcelManager(test_file) as em:
print("[OK] Excel加载成功")
print(f"[OK] 总行数: {em.ws.max_row}")
print(f"[OK] 总列数: {em.ws.max_column}")
# 测试2: 扫描格式
print("\n[测试2] 扫描格式")
with ExcelManager(test_file) as em:
format_info = em.scan_format()
print(f"[OK] 总行数: {format_info['total_rows']}")
print(f"[OK] 总列数: {format_info['total_columns']}")
print(f"[OK] 合并单元格数: {len(format_info['merged_cells'])}")
# 测试3: 获取已有脚本
print("\n[测试3] 获取已有脚本")
with ExcelManager(test_file) as em:
scripts = em.get_existing_scripts()
print(f"[OK] 发现 {len(scripts)} 个已有脚本")
for script in scripts:
print(f" - 第{script['row']}行: {script['title'][:30]}...")
# 测试4: 追加脚本
print("\n[测试4] 追加脚本")
test_script = {
"title": "测试脚本",
"story": "这是一个测试故事",
"segments": [
{
"time": "0-5秒",
"duration": 5,
"shot_desc": "特写镜头",
"movement_desc": "缓慢推进",
"tech_desc": "手持稳定器",
"scene_desc": "自然光",
"line": "测试台词",
"sound_desc": "环境音"
},
{
"time": "5-10秒",
"duration": 5,
"shot_desc": "中景",
"movement_desc": "固定",
"tech_desc": "三脚架",
"scene_desc": "室内光",
"line": "继续测试",
"sound_desc": "背景音乐"
}
],
"bgm": "轻音乐",
"activity": "",
"date": "2026-04-22",
"notes": "测试备注"
}
with ExcelManager(test_file) as em:
start_row = em.ws.max_row + 1
end_row = em.append_script(test_script, start_row)
em.save()
print(f"[OK] 脚本写入成功: 第{start_row}-{end_row}行")
# 测试5: 验证写入
print("\n[测试5] 验证写入")
with ExcelManager(test_file) as em:
result = em.validate_write(start_row, end_row)
print(f"[OK] 格式正确性: {result['format_valid']}")
print(f"[OK] 内容完整性: {result['content_complete']}")
if result['errors']:
print(f"[X] 错误: {result['errors']}")
print("\n[完成] Excel管理测试通过")
finally:
# 清理测试文件
if os.path.exists(test_file):
os.remove(test_file)
print("[OK] 清理测试文件")
if __name__ == "__main__":
test_excel()
FILE:tests/mac/format_checker_mac.py
# -*- coding: utf-8 -*-
"""
格式检查器测试 - macOS版本
对应: scripts/format_checker.py
"""
import sys
import os
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
from format_checker import FormatChecker
def create_test_script(valid=True):
"""创建测试脚本"""
if valid:
return {
"title": "测试脚本标题",
"theme": "测试主题",
"story": "这是一个完整的故事。开始介绍背景,然后展开情节,接着遇到问题,最后解决问题。这是一个关于周末采摘的故事,主人公一家三口来到农家院,先是被美景吸引,然后品尝美食,最后满载而归。整个故事有起承转合,总共超过两百字的叙述,每个环节都有详细的描写和情感变化。",
"total_duration": "2-3min",
"segments_count": 3,
"segments": [
{
"seg_id": 1,
"time": "0-5秒",
"duration": 5,
"shot_desc": "全景镜头,展现场景全貌,主体位于画面中央,光线柔和自然,背景虚化突出主题",
"movement_desc": "缓慢推进,速度均匀,从远景到中景的过渡,营造期待感",
"tech_desc": "使用稳定器手持拍摄,ISO400,光圈f/2.8,快门1/50s",
"scene_desc": "自然光线从窗户射入,暖色调,简洁构图,三分法则",
"line": "先来说说今天的故事,真的很精彩",
"sound_desc": "环境音为主,轻微的风声,音量控制在-20dB左右",
"bgm": "轻快背景音乐",
"tags": "#测试",
"status": "待使用"
},
{
"seg_id": 2,
"time": "5-12秒",
"duration": 7,
"shot_desc": "中景特写,展现细节,主体表情丰富,背景适度虚化",
"movement_desc": "固定机位,保持稳定,偶尔轻微晃动增加真实感",
"tech_desc": "固定三脚架,ISO800,光圈f/4,快门1/60s",
"scene_desc": "室内灯光,色温5500K,对比度适中,饱和度自然",
"line": "然后发生了意想不到的事情",
"sound_desc": "背景音乐渐强,配合画面情绪变化,营造氛围感",
"bgm": "轻快背景音乐",
"tags": "#测试",
"status": "待使用"
},
{
"seg_id": 3,
"time": "12-18秒",
"duration": 6,
"shot_desc": "特写镜头,聚焦表情,展现情感高潮,眼神有光",
"movement_desc": "缓慢拉远,从特写到全景的过渡,留下回味空间",
"tech_desc": "使用滑轨,ISO200,光圈f/1.8,快门1/100s",
"scene_desc": "逆光拍摄,轮廓光勾勒主体,金色调,梦幻感",
"line": "最后大家都很满意",
"sound_desc": "音效淡出,留下环境音,营造余韵和情感延续",
"bgm": "轻快背景音乐",
"tags": "#测试",
"status": "待使用"
}
]
}
else:
# 创建有问题的脚本
return {
"title": "测试脚本标题",
"theme": "测试主题",
"story": "短故事",
"total_duration": "2-3min",
"segments_count": 3,
"segments": [
{
"seg_id": 1,
"time": "0-5秒",
"duration": 15,
"shot_desc": "短",
"movement_desc": "短",
"tech_desc": "短",
"scene_desc": "短",
"line": "首先其次",
"sound_desc": "短"
}
]
}
def test_format():
"""测试格式检查功能"""
# 测试1: 正常脚本验证
print("\n[测试1] 正常脚本验证(应通过)")
checker = FormatChecker("xiaohongshu")
valid_script = create_test_script(valid=True)
passed, issues = checker.validate_script(valid_script)
print(f"[OK] 验证结果: {'通过' if passed else '未通过'}")
if issues:
for issue in issues:
print(f" [X] {issue}")
else:
print(" [OK] 无问题")
# 测试2: 有问题脚本验证
print("\n[测试2] 有问题脚本验证(应失败)")
invalid_script = create_test_script(valid=False)
passed, issues = checker.validate_script(invalid_script)
print(f"[OK] 验证结果: {'通过' if passed else '未通过(预期)'}")
print(f"[OK] 发现 {len(issues)} 个问题:")
for issue in issues:
print(f" - {issue}")
# 测试3: 批量验证
print("\n[测试3] 批量验证")
scripts = [create_test_script(valid=True), create_test_script(valid=False)]
results = checker.validate_batch(scripts)
print(f"[OK] 总脚本数: {results['total']}")
print(f"[OK] 通过: {results['passed']}")
print(f"[OK] 失败: {results['failed']}")
# 测试4: 自动修复
print("\n[测试4] 自动修复")
checker_fix = FormatChecker("xiaohongshu")
broken_script = create_test_script(valid=False)
print("修复前:")
print(f" - 镜头描述: {broken_script['segments'][0]['shot_desc']}")
print(f" - 台词: {broken_script['segments'][0]['line']}")
fixed_script = checker_fix.auto_fix(broken_script)
print("修复后:")
print(f" - 镜头描述: {fixed_script['segments'][0]['shot_desc']}")
print(f" - 台词: {fixed_script['segments'][0]['line']}")
print(f"\n[OK] 修复记录:")
print(checker_fix.get_fix_report())
# 测试5: 抖音平台检查
print("\n[测试5] 抖音平台验证")
dy_checker = FormatChecker("douyin")
dy_script = create_test_script(valid=True)
passed, issues = dy_checker.validate_script(dy_script)
print(f"[OK] 抖音验证结果: {'通过' if passed else '未通过'}")
# 测试6: 视频号平台检查
print("\n[测试6] 视频号平台验证(标题限制)")
sph_checker = FormatChecker("shipinhao")
# 测试过长标题
long_title_script = create_test_script(valid=True)
long_title_script["title"] = "这是一个超过16个字符的很长很长的标题"
passed, issues = sph_checker.validate_script(long_title_script)
print(f"[OK] 长标题验证: {'通过' if passed else '未通过(预期)'}")
# 测试标点标题
punct_title_script = create_test_script(valid=True)
punct_title_script["title"] = "标题,有标点。"
passed, issues = sph_checker.validate_script(punct_title_script)
print(f"[OK] 标点标题验证: {'通过' if passed else '未通过(预期)'}")
print("\n[完成] 格式检查测试通过")
if __name__ == "__main__":
test_format()
FILE:tests/mac/script_generator_mac.py
# -*- coding: utf-8 -*-
"""
脚本生成器测试 - macOS版本
对应: scripts/script_generator.py
"""
import sys
import os
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'scripts'))
from script_generator import ScriptGenerator
def test_generator():
"""测试脚本生成功能"""
# 测试1: 初始化生成器(小红书)
print("\n[测试1] 初始化生成器 - 小红书")
generator = ScriptGenerator(
platform="xiaohongshu",
duration="2-3min",
keywords=["采摘", "农家菜", "周末"],
trending_titles=["周末去哪儿", "春日采摘攻略", "农家美食推荐"],
avoid_themes=["已发布主题"]
)
print(f"[OK] 平台: {generator.platform}")
print(f"[OK] 分镜数量: {generator.segments_count}")
print(f"[OK] 关键词: {generator.keywords}")
# 测试2: 生成分镜时长分配
print("\n[测试2] 分镜时长分配")
durations = generator._distribute_duration()
print("[OK] 分配模式(开头快、中间稳、结尾慢):")
print(f" 前3个: {durations[:3]}秒(开场)")
print(f" 中间: ...{len(durations)-5}个...(主体)")
print(f" 后2个: {durations[-2:]}秒(结尾)")
# 测试3: 构建分镜结构
print("\n[测试3] 构建分镜结构")
segments = generator._build_segments_structure(durations[:3])
print(f"[OK] 生成了 {len(segments)} 个分镜")
for seg in segments:
print(f" - 分镜{seg['seg_id']}: {seg['time']}, {seg['duration']}秒")
# 测试4: 构建提示词
print("\n[测试4] 构建AI提示词")
prompt = generator.get_prompt_for_script(1)
print(f"[OK] 提示词长度: {len(prompt)} 字符")
print(f"[OK] 提示词预览(前200字):")
print(f" {prompt[:200]}...")
# 测试5: 生成单个脚本
print("\n[测试5] 生成单个脚本")
script = generator._generate_single_script(1)
print(f"[OK] 脚本ID: {script['script_id']}")
print(f"[OK] 标题: {script['title']}")
print(f"[OK] 主题: {script['theme']}")
print(f"[OK] 总时长: {script['total_duration']}")
print(f"[OK] 分镜数: {script['segments_count']}")
# 测试6: 生成多个脚本
print("\n[测试6] 生成多个脚本")
scripts = generator.generate(count=3)
print(f"[OK] 生成了 {len(scripts)} 个脚本")
for i, s in enumerate(scripts, 1):
print(f" - 脚本{i}: {s['segments_count']}个分镜")
# 测试7: 其他平台测试
print("\n[测试7] 抖音平台测试")
dy_generator = ScriptGenerator(
platform="douyin",
duration="1min"
)
print(f"[OK] 抖音分镜数: {dy_generator.segments_count}")
print("\n[测试8] 视频号平台测试")
sph_generator = ScriptGenerator(
platform="shipinhao",
duration="1-3min"
)
print(f"[OK] 视频号分镜数: {sph_generator.segments_count}")
print("\n[完成] 脚本生成测试通过")
if __name__ == "__main__":
test_generator()
FILE:tests/script_generator_win.py
# -*- coding: utf-8 -*-
"""
脚本生成器测试 - Windows版本
对应: scripts/script_generator.py
"""
import sys
import io
import os
# 设置标准输出编码为UTF-8
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
from script_generator import ScriptGenerator
def test_generator():
"""测试脚本生成功能"""
# 测试1: 初始化生成器(小红书)
print("\n[测试1] 初始化生成器 - 小红书")
generator = ScriptGenerator(
platform="xiaohongshu",
duration="2-3min",
keywords=["采摘", "农家菜", "周末"],
trending_titles=["周末去哪儿", "春日采摘攻略", "农家美食推荐"],
avoid_themes=["已发布主题"]
)
print(f"[OK] 平台: {generator.platform}")
print(f"[OK] 分镜数量: {generator.segments_count}")
print(f"[OK] 关键词: {generator.keywords}")
# 测试2: 生成分镜时长分配
print("\n[测试2] 分镜时长分配")
durations = generator._distribute_duration()
print("[OK] 分配模式(开头快、中间稳、结尾慢):")
print(f" 前3个: {durations[:3]}秒(开场)")
print(f" 中间: ...{len(durations)-5}个...(主体)")
print(f" 后2个: {durations[-2:]}秒(结尾)")
# 测试3: 构建分镜结构
print("\n[测试3] 构建分镜结构")
segments = generator._build_segments_structure(durations[:3])
print(f"[OK] 生成了 {len(segments)} 个分镜")
for seg in segments:
print(f" - 分镜{seg['seg_id']}: {seg['time']}, {seg['duration']}秒")
# 测试4: 构建提示词
print("\n[测试4] 构建AI提示词")
prompt = generator.get_prompt_for_script(1)
print(f"[OK] 提示词长度: {len(prompt)} 字符")
print(f"[OK] 提示词预览(前200字):")
print(f" {prompt[:200]}...")
# 测试5: 生成单个脚本
print("\n[测试5] 生成单个脚本")
script = generator._generate_single_script(1)
print(f"[OK] 脚本ID: {script['script_id']}")
print(f"[OK] 标题: {script['title']}")
print(f"[OK] 主题: {script['theme']}")
print(f"[OK] 总时长: {script['total_duration']}")
print(f"[OK] 分镜数: {script['segments_count']}")
# 测试6: 生成多个脚本
print("\n[测试6] 生成多个脚本")
scripts = generator.generate(count=3)
print(f"[OK] 生成了 {len(scripts)} 个脚本")
for i, s in enumerate(scripts, 1):
print(f" - 脚本{i}: {s['segments_count']}个分镜")
# 测试7: 其他平台测试
print("\n[测试7] 抖音平台测试")
dy_generator = ScriptGenerator(
platform="douyin",
duration="1min"
)
print(f"[OK] 抖音分镜数: {dy_generator.segments_count}")
print("\n[测试8] 视频号平台测试")
sph_generator = ScriptGenerator(
platform="shipinhao",
duration="1-3min"
)
print(f"[OK] 视频号分镜数: {sph_generator.segments_count}")
print("\n[完成] 脚本生成测试通过")
if __name__ == "__main__":
test_generator()
FILE:tests/__init__.py
"""
快导(KD) Skill - 测试套件
"""
import sys
import os
# 添加scripts目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
from test_config import test_config
from test_excel import test_excel
from test_generator import test_generator
from test_format import test_format
def run_all_tests():
"""运行所有测试"""
print("=" * 60)
print("快导(KD) Skill 测试套件")
print("=" * 60)
tests = [
("配置管理测试", test_config),
("Excel管理测试", test_excel),
("脚本生成测试", test_generator),
("格式检查测试", test_format)
]
results = []
for name, test_func in tests:
print(f"\n{'='*60}")
print(f"开始: {name}")
print('='*60)
try:
test_func()
results.append((name, "✅ 通过"))
print(f"✅ {name} 通过")
except Exception as e:
results.append((name, f"❌ 失败: {e}"))
print(f"❌ {name} 失败: {e}")
# 打印测试报告
print("\n" + "=" * 60)
print("测试报告")
print("=" * 60)
for name, result in results:
print(f"{name}: {result}")
passed = sum(1 for _, r in results if "通过" in r)
print(f"\n总计: {passed}/{len(results)} 通过")
if __name__ == "__main__":
run_all_tests()
FILE:test_workflow.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
KD Skill Test Script
测试 WorkflowManager 的基本功能
"""
import sys
sys.path.insert(0, r'C:\Users\kkk49\.agents\skills\kd')
from scripts import WorkflowManager
def test_workflow():
print("="*60)
print("KD Skill Test - WorkflowManager")
print("="*60)
# 测试1: 初始化
print("\n[Test 1] 初始化 WorkflowManager")
try:
workflow = WorkflowManager('xiaohongshu', interactive=False)
print(f" Platform: {workflow.platform}")
print(f" Interactive: {workflow.interactive}")
keywords = workflow.platform_config.get('keywords', [])
print(f" Keywords count: {len(keywords)}")
print(" [PASS] 初始化成功")
except Exception as e:
print(f" [FAIL] {e}")
return False
# 测试2: Step 1
print("\n[Test 2] Step 1: 搜索爆款")
try:
manual_trending = [
'成都周边采摘攻略',
'周末亲子游玩推荐',
'枇杷采摘节来了',
'带爸妈去体验采摘'
]
result = workflow._run_step1(manual_trending=manual_trending)
print(f" Selected keywords: {result.get('selected_keywords', [])}")
print(f" Final selection: {len(result.get('final_selection', []))} items")
print(" [PASS] Step 1 完成")
except Exception as e:
print(f" [FAIL] {e}")
return False
# 测试3: Step 2
print("\n[Test 3] Step 2: 读取规则")
try:
result = workflow._run_step2()
rules_count = len(result.get('rules_summary', []))
print(f" Rules count: {rules_count}")
print(f" Rules file exists: {result.get('rules_exists', False)}")
print(" [PASS] Step 2 完成")
except Exception as e:
print(f" [FAIL] {e}")
return False
# 测试4: Step 3
print("\n[Test 4] Step 3: 外网搜索")
try:
# 手动提供外网爆款数据
manual_external = ['Village Farm Life Cooking']
result = workflow._run_step3(manual_external=manual_external)
print(f" Final selection: {len(result.get('final_selection', []))} items")
print(" [PASS] Step 3 完成")
except Exception as e:
print(f" [FAIL] {e}")
return False
# 测试5: Step 4
print("\n[Test 5] Step 4: 同质化检查")
try:
result = workflow._run_step4()
print(f" Skipped: {result.get('skipped', False)}")
print(f" Themes to avoid: {len(result.get('themes_to_avoid', []))}")
print(" [PASS] Step 4 完成")
except Exception as e:
print(f" [FAIL] {e}")
return False
# 测试6: Step 5
print("\n[Test 6] Step 5: 格式检查")
try:
result = workflow._run_step5()
print(f" Format confirmed: {result.get('format_confirmed', False)}")
print(f" Is new file: {result.get('is_new_file', False)}")
print(" [PASS] Step 5 完成")
except Exception as e:
print(f" [FAIL] {e}")
return False
print("\n" + "="*60)
print("All tests passed!")
print("="*60)
return True
if __name__ == '__main__':
success = test_workflow()
sys.exit(0 if success else 1)