🔄 卡若AI 同步 2026-03-01 06:14 | 更新:金盾、水桥平台对接、水溪整理归档、卡木、总索引与入口、运营中枢参考资料、运营中枢工作台、运营中枢技能路由 | 排除 >20MB: 14 个

This commit is contained in:
2026-03-01 06:14:21 +08:00
parent 439259aefc
commit ae4b794038
32 changed files with 1947 additions and 178 deletions

View File

@@ -27,6 +27,10 @@ updated: "2026-02-16"
2. **部署**Vercel、宝塔、GitHub Webhook见 Vercel与v0部署流水线
3. **运维**:数据库清理(见 数据库管理)、服务器管理
## 抖音发布
存客宝当前**无抖音登录/发布 SDK**。若需将视频发布到抖音使用「抖音发布」Skill抖音开放平台 OAuth + 上传/创建视频);若后续存客宝或腕推提供抖音发布能力,可在 `03_卡木/木叶_视频内容/抖音发布/SKILL.md` 中补充对接方式。
## 参考
- `开发文档/00_汇总索引.md`:开发文档、功能迭代、需求

View File

@@ -1,16 +1,16 @@
---
name: Soul创业实验
description: 《一场soul的创业实验》内容与运营统一入口——写作、上传、运营报表子类聚合
description: 《一场soul的创业实验》内容与运营统一入口——写作、上传、推送(飞书群)、运营报表,子类聚合
triggers: Soul创业实验、写Soul文章、写授文章、Soul派对写文章、第9章写文章、写soul场次、soul文章规则、Soul文章上传、Soul派对文章、第9章上传、soul上传、写soul文章、运营报表、派对填表、派对纪要
owner: 水桥
group: 水
version: "1.0"
updated: "2026-02-26"
version: "1.1"
updated: "2026-03-01"
---
# Soul创业实验 Skill
> 《一场soul的创业实验》相关**写作**第9章单场文章、**上传**(文章到小程序)、**运营报表**(派对数据→飞书)。按需求进入对应子类执行。
> 《一场soul的创业实验》相关**写作**第9章单场文章、**上传**(文章到小程序)、**推送**(上传后同步固定飞书群:前 6% 正文「一句一行、行间空一行」+ 海报图/二维码,不发小程序链接)、**运营报表**(派对数据→飞书)。按需求进入对应子类执行。
**📌 交接备注2026-02-26****一场创业实验**Soul 创业实验场 / Mycontent 项目)**后续操作交由永平**。代码与进度以 GitHub 为准:**https://github.com/fnvtk/Mycontent/tree/yongpxu-soul**。卡若侧仅保留写作/上传/运营报表等 Skill 入口,具体开发与迭代以永平分支为准。
@@ -20,8 +20,8 @@ updated: "2026-02-26"
| 子类 | 触发词示例 | 说明 |
|:---|:---|:---|
| **写作** | 写Soul文章、写授文章、Soul派对写文章、第9章写文章、写soul场次、soul文章规则 | 按派对 TXT 写第9章单场文章人称「我」整篇最多一次、联系管理/切片/副业隐晦植入 |
| **上传** | Soul文章上传、Soul派对文章、第9章上传、soul上传、写soul文章、文章写好上传 | 文章写好后上传到小程序id 已存在则更新 |
| **写作** | 写Soul文章、写授文章、Soul派对写文章、第9章写文章、写soul场次、soul文章规则 | 按派对 TXT 写第9章单场文章**先读** `写作/写作规范.md`:人称「我」整篇最多三次、每句空一行、大白话;**数值与场景必须具体**(金额、人数、时长、曝光、成本等写清具体数字;涉及 Soul 投流/派对价值算法时写清 75 曝光进 1 人、3 万曝光/天、投流 1000 曝光 610 块等);**约 20% 处 + 结尾各一句分享句≤50 字),不用「干货」二字及「干货:」等格式**联系管理/切片/副业隐晦植入 |
| **上传** | Soul文章上传、Soul派对文章、第9章上传、soul上传、写soul文章、文章写好上传 | 文章写好后上传到小程序**上传后同步固定飞书群**:发前 6% 正文(**一句一行、行间空一行**+ 章节海报图(含小程序码),**不发小程序链接**,见 `上传/README.md``上传/推送逻辑.md` |
| **运营报表** | 运营报表、派对填表、派对截图填表发群、派对纪要、106场、107场、本月运营数据 | 派对效果数据→飞书表格→智能纪要→飞书群,见飞书管理下运营报表 Skill |
执行时:根据用户说的关键词判断是**写作 / 上传 / 运营报表**,再进入对应子类(读本目录下 `写作/``上传/` 或引用运营报表)。
@@ -32,13 +32,15 @@ updated: "2026-02-26"
- **入口**`写作/写作规范.md`(人称、结构、格式、隐晦植入等,唯一规范来源)。
- **何时用**:写第 X 场、写Soul文章、写授文章。写完后输出到书稿第9章目录 `9.xx 第X场主题.md`,再走子类二上传。
- **数值与场景**:文中**有数值、有场景必须写具体**;涉及 Soul 投流、派对价值算法时,写清具体数值(如约 7580 曝光进 1 人、每天约 3 万曝光、进房 300600、1000 曝光 610 块、进一人约 4 毛 2、获客到微信约 20 块/人等),见写作规范「数值与场景」一条。
---
## 子类二:上传
## 子类二:上传与推送
- **入口**`上传/README.md`路径、content_upload 命令、飞书群)。
- **入口**`上传/README.md`路径、content_upload 命令)、`上传/推送逻辑.md`(飞书群推送规则)。
- **何时用**文章写好上传小程序、发到小程序。id 已存在→更新,否则创建。
- **推送**:上传到小程序后,**同步发固定飞书群**:先发文章前 6% 正文(**一句一行、行间空一行**),再发章节海报图(含该章节小程序码);**不在群里发小程序链接**,只发二维码(海报)。
---
@@ -54,14 +56,29 @@ updated: "2026-02-26"
|:---|:---|
| `SKILL.md` | 本文件,仅触发与子类导航 |
| `写作/写作规范.md` | 写作唯一规范(人称、结构、格式、隐晦植入) |
| `上传/README.md` | 上传唯一说明(路径、命令、飞书 |
| `上传/README.md` | 上传唯一说明(路径、命令、推送步骤 |
| `上传/推送逻辑.md` | 飞书群推送:前 6% + 海报图、不发链接、脚本与链路 |
| `上传/飞书妙记转文章并发布.md` | 飞书妙记链接 → 下载文本/视频 → 写成第 X 场文章 → 发飞书群 + 小程序(一句话指令 + 步骤) |
Soul 相关仅保留本 Skill 一个目录,原 Soul文章上传、Soul文章写作 已删除并合并至此。
---
## 发布与推送链路(完整)
1. **写文章**:按 `写作/写作规范.md` 写第 X 场输出到书稿第9章目录 `第X场主题.md`
2. **上传到小程序**:在永平项目执行 `content_upload.py --id 9.xx --title "…" --content-file "<md路径>" --part part-4 --chapter chapter-9 --price 1.0`
3. **同步飞书群**(上传后必做):在永平项目执行 `scripts/send_chapter_poster_to_feishu.py <章节id> "<章节标题>" --md "<同文章md路径>"`。效果:向 Soul 彩民团队飞书群**先发一条文本**(标题 + 文章前 6% 正文),**再发一张海报图**(含该章节小程序码);**不发送小程序链接**,仅通过海报中的二维码引导阅读。
海报规则:摘要区用文章前 6% 字数、每句空一行、不超出边框;无手指图标;字体使用 PingFang 优化可读性。详见 `上传/推送逻辑.md`
固定推送群:**Soul 彩民团队** 飞书群webhook 已写在脚本默认值中,无需复制;运行推送命令即发到该群)。
---
## 版本记录
| 版本 | 日期 | 说明 |
|:---|:---|:---|
| 1.0 | 2026-02-26 | 初版;写作、上传、运营报表统一为子类,原 Soul文章写作 / Soul文章上传 合并至此 |
| 1.1 | 2026-03-01 | 上传后推送飞书群:前 6% + 海报图,不发链接;新增推送逻辑文档与链路说明 |

View File

@@ -43,10 +43,19 @@ python3 content_upload.py --id 9.23 --title "9.23 第110场Soul变现逻辑
---
## 飞书群(可选
## 同步飞书群(上传后必做
- 永平项目下:`python3 scripts/post_to_feishu.py --release 9.xx --title "9.xx 第X场标题"` 可发新发布通知到卡若日志飞书群
- 海报到飞书:需配置 `scripts/.env.feishu`FEISHU_APP_ID / FEISHU_APP_SECRET调用 `send_poster_to_feishu.py`(若存在)。
上传到小程序后,**同步**推送到固定飞书群:发**前 6% 正文**(一句一行、行间空一行)+ **章节海报图**(含该章节小程序码),**不发小程序链接**。详见 `上传/推送逻辑.md`
在永平项目下执行(`--md` 为**本篇文章**的 md 路径):
```bash
python3 scripts/send_chapter_poster_to_feishu.py 9.xx "第X场标题" --md "<文章.md 完整路径>"
```
- 需配置 `scripts/.env.feishu`FEISHU_APP_ID、FEISHU_APP_SECRET
- 依赖:`pip install requests Pillow`
- 默认发到 **Soul 彩民团队** 飞书群webhook 已写在脚本内),无需复制链接,直接运行上述命令即可。
---

View File

@@ -0,0 +1,48 @@
# Soul 文章推送飞书群 · 逻辑说明
> 上传到小程序后,**同步**把该章节推送到固定飞书群:发**前 6% 正文**(一句一行、行间空一行)+ **章节海报图**(含小程序码),**不发小程序链接**。
---
## 原则
- **不发链接**群里不出现「小程序https://...」等链接,只通过海报中的**二维码**引导扫码阅读。
- **文本格式****一句一行**,句子之间**空一行**(也就是用 `\n\n` 分隔句子)。
- **内容**:一条**文本消息**(标题 + 文章前 6% 正文)+ 一条**图片消息**(章节海报,内含该章节小程序码)。
- **顺序**:先发前 6% 正文,再发海报图。
---
## 脚本与命令
- **脚本**:永平项目下 `scripts/send_chapter_poster_to_feishu.py`
- **依赖**`pip install requests Pillow`;飞书应用凭证写在 `scripts/.env.feishu`FEISHU_APP_ID、FEISHU_APP_SECRET
- **固定群 webhook**:脚本内置默认发到 **Soul 彩民团队** 飞书群webhook 为 `https://open.feishu.cn/open-apis/bot/v2/hook/14a7e0d3-864d-4709-ad40-0def6edba566`。无需复制链接,直接运行命令即可。
- **命令示例**(上传完成后执行):
```bash
cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验-永平"
python3 scripts/send_chapter_poster_to_feishu.py 9.24 "第112场一个人起头维权挣了大半套房" \
--md "/Users/karuo/Documents/个人/2、我写的书/《一场soul的创业实验》/第四篇|真实的赚钱/第9章我在Soul上亲访的赚钱案例/第112场一个人起头维权挣了大半套房.md"
```
- **参数**`<章节id>``<章节标题>``--md <文章 .md 路径>`。摘要自动取该文章正文前 6% 字数。
---
## 海报规则
- 标题章节标题如「第112场…」不再用「Soul创业派对」或「精彩内容」做主标题。
- 摘要区:文章前 6% 字数,每句空一行,严格限制在摘要框内,超出则截断并加省略号。
- **无手指图标**;底部为「长按识别小程序码」+ 章节小程序码。
- 字体PingFang标题加粗、正文常规便于阅读。
---
## 完整链路(写文章 → 上传 → 推送)
1. 写文章 → 保存到书稿第9章目录`第112场…md`)。
2. 上传到小程序:`content_upload.py --id 9.xx --title "…" --content-file "<md路径>" --part part-4 --chapter chapter-9 --price 1.0`
3. 推送飞书群:`scripts/send_chapter_poster_to_feishu.py <章节id> "<章节标题>" --md "<同一 md 路径>"`
推送后,飞书群收到:① 标题 + 前 6% 正文;② 海报图(含该章节小程序码)。不收到任何小程序链接。

View File

@@ -0,0 +1,84 @@
# 飞书妙记 → 第9章文章 → 飞书群 + 小程序
> 把飞书妙记链接丢给执行人时,用下面这一句 + 本页步骤即可。
---
## 一句话指令(复制给对方)
```
请帮我:
1. 打开这个飞书妙记,把里面的**文本**和**视频**都下载下来。
2. 根据文本内容按《一场soul的创业实验》第9章写作规范写成一篇「第 X 场|标题」的文章(和 112 场同风格)。
3. 文章写完后发布到「一场创业实验」飞书群Soul 彩民团队),并发布到「一场创业实验」小程序。
飞书妙记链接https://cunkebao.feishu.cn/minutes/obcn5muad6wexv91g65v1883
```
---
## 执行步骤(给执行人用)
### 第一步:下载妙记的文本和视频
- 打开链接https://cunkebao.feishu.cn/minutes/obcn5muad6wexv91g65v1883
- 在妙记页面里:导出/复制**全文文本**,并下载**视频**(如有)。文本用于写文章,视频可留作素材或切片。
### 第二步:按规范写文章
- **写作规范**:必须先读 `Soul创业实验/写作/写作规范.md`卡若AI 或永平项目里可查)。
- **要点**:人称「我」整篇最多 3 次;每句空一行;大白话;数值与场景写具体;约 20% 处和结尾各一句分享句≤50 字,不用「干货」二字);涉及 Soul 投流/派对价值时写清具体数值75 曝光进 1 人、3 万曝光/天等)。
- **输出**保存为第9章下的一篇 md例如 `第113场本场主题.md`,放在书稿目录:
`《一场soul的创业实验》/第四篇|真实的赚钱/第9章我在Soul上亲访的赚钱案例/`
章节 id 与 112 场类似,用 9.25、9.26 等(与现有不重复即可)。
### 第三步:发布到飞书群 + 小程序
在**永平项目**根目录执行下面三件事(顺序不要改):
**1上传到小程序**
```bash
cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验-永平"
python3 content_upload.py --id 9.xx --title "第X场标题" \
--content-file "<刚写好的文章.md 完整路径>" \
--part part-4 --chapter chapter-9 --price 1.0
```
(把 `9.xx``第X场标题``<刚写好的文章.md 完整路径>` 换成实际值。)
**2推送到飞书群Soul 彩民团队)**
```bash
python3 scripts/send_chapter_poster_to_feishu.py 9.xx "第X场标题" --md "<同一篇文章.md 的完整路径>"
```
- 会先发「标题 + 文章前 6% 正文(一句一行、行间空一行)」,再发章节海报图(含该章节小程序码),不发小程序链接。
- 默认已发到 Soul 彩民团队飞书群,无需再填 webhook。
**3可选同步到飞书知识库**
若需要这篇文章也出现在「创业实验」飞书知识库里,在永平项目执行:
```bash
python3 scripts/feishu_wiki_upload.py --full
```
(会按书稿目录同步全书;若只加单篇,需在脚本中增加对应 `--only 113场` 等逻辑。)
---
## 相关文档
| 文档 | 说明 |
|:---|:---|
| `Soul创业实验/写作/写作规范.md` | 写作唯一规范(人称、数值、分享句、格式等) |
| `Soul创业实验/上传/README.md` | 上传命令与路径说明 |
| `Soul创业实验/上传/推送逻辑.md` | 飞书群推送规则6% 正文 + 海报图、不发链接) |
---
## 当前妙记链接(可替换)
- 本次任务链接https://cunkebao.feishu.cn/minutes/obcn5muad6wexv91g65v1883
- 以后换别的妙记,只需把「一句话指令」里的链接换成新链接,步骤不变。

View File

@@ -8,7 +8,7 @@
| 项目 | 规范 |
|:---|:---|
| **「我」** | **整篇文章最多出现次**建议不用或仅一处;用「这边」「直接」「就」等替代成稿后全文搜索「我」,超过 1 处必须改写。 |
| **「我」** | **整篇文章最多出现次**用「这边」「直接」「就」「场上」等替代成稿后全文搜索「我」,超过 3 处必须改写。 |
| **「卡若」** | 每篇最多提一次;不需要时可完全不出现。 |
---
@@ -19,7 +19,8 @@
|:---|:---|
| 字数 | 每小节 20005000 字 |
| 来源 | 以真实聊天内容为基础,不改原意 |
| 数据 | 以聊天内提到的数值为主,不编造(场观、人数、时长、营收等) |
| 数据 | 以聊天内提到的数值为主,不编造(场观、人数、时长、营收等)**有数值就必须写具体**,不写「大概很多」「不少」等模糊说法 |
| **数值与场景** | **数值跟场景要具体写进去**:金额、人数、时长、比例、曝光量、进房数、成本、获客价等,一律写清具体数字;涉及 Soul 投流、派对价值时,要写清算法口径(如:约 7580 曝光进 1 人、每天约 3 万曝光、进房 300600 人、1000 曝光 610 块、进一人约 4 毛 2、获客到微信约 20 块/人等) |
| 配图 | 不需要 |
---
@@ -27,7 +28,7 @@
## 三、主题与结构
- **一个观点**:每节只表达一个核心观点,主题清晰
- **有数据**:用具体数值验证(场观、人数、时长、营收等)
- **有数据**:用具体数值验证(场观、人数、时长、营收等)**数值与场景必须具体**,不写笼统表述,有提到的算法、成本、比例都要带具体数字写进去(如 Soul 推流 75 曝光进 1 人、3 万曝光/天、投流 1000 曝光 610 块等)
- **与目录一致**:小节名称、内容与全书结构、其他小节风格统一
- **时间**:以文档/聊天记录时间为准
@@ -47,14 +48,15 @@
- **分段**:每段一个主题,小主题隐于叙述中,不列段头小标题
- **穿插**:细节、对话、观点分析
- **多用对话**增强真实感「X 号问」「有人问」「直接回答」「这边说」等)
- **分享句(两处,强制)**:约 20% 处一句、结尾一句,各不超过 50 字,围绕本节主题、紧扣内容,留余味或可执行。**不要出现「干货」二字**,不要用「干货:」或「**干货**:」等格式,直接写一句金句即可,可单独成段。
---
## 六、写作技巧
- 第一人称叙述时少用「我」,见第一节
- 短句,大白话,口语化
- 每句话后空一行,段间空行
- 第一人称叙述时少用「我」,见第一节(整篇最多三次)
- **短句,大白话,口语化**:像平时说话一样写,不书面、不拽词
- **每句空一行**:一句写完就换行、再空一行再写下一句,不要好几句挤成一段
- 善用对比、反转;适当自嘲或幽默
---
@@ -84,7 +86,9 @@
## 十、格式规范(统一)
- 每句话后空一行
- 段间空行
- **每句空一行**:每一句话后面换行 + 空一行,再写下一句;不要多句挤在一段
-与段之间空行
- 对话、细节、观点分行,避免大段堆砌
-`---` 做段落分隔(与全书一致)
- **分享句**:全文约 20% 处一句≤50 字、结尾一句≤50 字、围绕主题);**不用「干货」二字及「干货:」等格式**,直接一句金句
- 写作与改写第9章文章时**必须先读本规范**;以后写 Soul 派对场次文章都用这套

View File

@@ -0,0 +1,79 @@
---
name: 卡猫复盘
description: 以婼瑄目录为唯一素材的卡猫复盘,目标·结果=今年总目标+完成%,人/事/数具体,输出飞书+卡猫群
triggers: 卡猫复盘、婼瑄复盘、卡猫今日复盘、婼瑄今日、复盘到卡猫、发卡猫群
owner: 水桥
group: 水
version: "1.0"
updated: "2026-02-28"
---
# 卡猫复盘 Skill
> **唯一素材**:婼瑄目录。**目标·结果**:卡猫今年总目标 + 完成百分比。**人/事/数**:必须具体。
---
## 一、格式规范(强制)
执行前必读:**`运营中枢/参考资料/卡猫复盘格式_固定规则.md`**
要点:
- **🎯 目标·结果·达成率** = **卡猫当年总目标**(一句或 13 句,每句 ≤30 字)+ **当前达成情况**(一句)+ **完成百分比 XX%**。不得写「本次任务目标」。
- **人**:写真实称呼(叶倩如、郑朝阳、郑清土、财务、腾讯侧等),不写「某人」。
- **事**写具体事件如「2 月 28 日叶倩如侧聊流量开票与下游消化」),不写「讨论了问题」。
- **数**:能写则写(如 10 亿流水/票、15%、6 点专票、20% 换 500 万、端口 18789不写「很多」。
---
## 二、素材目录与文件选择
| 项目 | 规定 |
|:---|:---|
| **素材目录** | **仅婼瑄**`/Users/karuo/Library/Mobile Documents/com~apple~CloudDocs/Documents/婼瑄`(含所有子目录) |
| **文件选择** | 当次复盘 = 该目录下**当日或用户指定日期**新增/修改的 txt、md 等;或用户指定文件/日期范围 |
| **输出目录** | 复盘 MD 存 **婼瑄/复盘/**,文件名 `YYYY-MM-DD_卡猫复盘_主题.md` |
---
## 三、执行步骤
1. **读格式**:读 `运营中枢/参考资料/卡猫复盘格式_固定规则.md`
2. **取当年总目标**:从 记忆.md 或婼瑄目录内已有复盘/文档中提取「卡猫 [年份] 年总目标」;若无则写「待补充」并注明完成约 X%。
3. **选文件**:在婼瑄目录下按日期或用户指定,列出本次依据的 txt/md 文件。
4. **读内容**:读取上述文件,提取**具体的人、具体的事、具体的数字**。
5. **写复盘**:按卡猫复盘格式五块(🎯📌💡📝▶)撰写,目标·结果=今年总目标+完成%,过程/反思/总结中人/事/数具体。
6. **落盘**:保存到 `婼瑄/复盘/YYYY-MM-DD_卡猫复盘_主题.md`
7. **发布**:用飞书统一发布脚本上传到卡猫知识库节点,并推送卡猫群 webhook。
---
## 四、飞书与群配置
| 项目 | 值 |
|:---|:---|
| 飞书 Wiki 父节点 | `KCVWwPfJvi1d6ZkTfMCcCkOYnVc`(卡猫知识库) |
| 卡猫群 webhook | `https://open.feishu.cn/open-apis/bot/v2/hook/4458f8d6-4bb5-492d-8ca2-e5cf06c0bd22` |
| 发布脚本 | `02_卡人/水桥_平台对接/飞书管理/脚本/feishu_article_unified_publish.py` |
发布命令示例:
```bash
python3 卡若AI/02_卡人/水桥_平台对接/飞书管理/脚本/feishu_article_unified_publish.py \
--parent KCVWwPfJvi1d6ZkTfMCcCkOYnVc \
--title "YYYY-MM-DD 卡猫复盘:主题" \
--md "婼瑄/复盘/YYYY-MM-DD_卡猫复盘_主题.md" \
--json "婼瑄/复盘/YYYY-MM-DD_卡猫复盘_主题_feishu_blocks.json" \
--webhook "https://open.feishu.cn/open-apis/bot/v2/hook/4458f8d6-4bb5-492d-8ca2-e5cf06c0bd22"
```
---
## 五、与通用复盘的区别
| 项目 | 通用卡若复盘 | 卡猫复盘 |
|:---|:---|:---|
| 目标·结果 | 当次任务目标+结果+达成率 | **卡猫今年总目标**+当前达成+**完成%** |
| 人/事/数 | 无强制 | **必须具体**(真人、具体事、具体数) |
| 素材来源 | 任意 | **仅婼瑄目录** |
| 输出 | 对话内复盘块 | 婼瑄/复盘/*.md + 飞书 + 卡猫群 |

View File

@@ -1,6 +1,6 @@
{
"access_token": "u-4NFObvRHB9baG_6zHLv28Al5kgW5k1ipp8aaZww00BOn",
"refresh_token": "ur-7XjMc1PjB1NoflqWI30rQbl5kqMBk1UhVEaaUwQ00wD6",
"access_token": "u-7VelB5t1J6xrwEh5fd0Ftwl5mWoBk1iPMUaaYRw00BPi",
"refresh_token": "ur-6MS.YzNGd5KoE3SUs_5r3ul5kqU5k1WrWEaaEBM00wSj",
"name": "飞书用户",
"auth_time": "2026-02-27T16:16:01.288755"
"auth_time": "2026-02-28T19:27:39.322083"
}

View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""
今日飞书日志:从聊天记录+今日文档统一整理,与本月/最终目标百分比,今日核心一条
- 今日核心目标每天20条Soul视频 + 20:00发1条朋友圈
"""
import os
import sys
from datetime import datetime
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
if SCRIPT_DIR not in sys.path:
sys.path.insert(0, SCRIPT_DIR)
from auto_log import get_token_silent, write_log, open_result, resolve_wiki_token_for_date
def build_tasks_today():
"""今日任务:昨日完成度+本月未完成并入+本月/最终目标%+今日核心20条Soul+8点朋友圈"""
today = datetime.now()
date_str = f"{today.month}{today.day}"
# 昨日2月27日完成度与 write_today_0227 / 本月其他日对齐)
yesterday_done = "昨日2月27日一人公司5%、玩值电竞25%、飞书日志100%"
# 本月未完成项(看板 T003/T004/T005 + 今日核心),并入今日
month_unfinished = "本月未完成并入今日一人公司、玩值电竞、卡若AI 4项优化、20条Soul+8点朋友圈"
# 本月与最终目标
month_goal_pct = 12
gap_pct = 88
return [
{
"person": "卡若",
"events": ["昨日完成度", "本月未完成并入", "今日核心"],
"quadrant": "重要紧急",
"t_targets": [
yesterday_done,
month_unfinished,
f"本月目标约 {month_goal_pct}%,距最终目标差 {gap_pct}%",
"今日核心每天20条Soul视频 + 20:00发1条朋友圈",
],
"n_process": [
"【昨日】2月27日完成度已更新至上本月其他日未完成项一并写入",
"【复盘】从聊天记录与今日文档统一整理",
"【2月突破执行】未完成项并入今日持续迭代至100%",
],
"t_thoughts": [
"昨日与本月完成度、未完成项均更新到当日日志;今日核心 20条Soul+8点朋友圈",
],
"w_work": ["一人公司", "玩值电竞", "卡若AI优化", "20条Soul视频", "20:00发1条朋友圈", "飞书日志"],
"f_feedback": [
"昨日完成度已写入 ✅",
"本月未完成已并入今日 🔄",
f"本月/最终 {month_goal_pct}% / 100%,差 {gap_pct}%",
"今日核心→20条Soul+8点朋友圈 🔄",
],
}
]
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--overwrite", action="store_true", help="覆盖已有当日日志")
args = parser.parse_args()
today = datetime.now()
date_str = f"{today.month}{today.day}"
print("=" * 50)
print(f"📝 写入今日飞书日志:{date_str}" + (" [覆盖]" if args.overwrite else ""))
print("=" * 50)
token = get_token_silent()
if not token:
print("❌ 无法获取飞书 Token")
sys.exit(1)
tasks = build_tasks_today()
target_wiki_token = resolve_wiki_token_for_date(date_str)
ok = write_log(token, date_str, tasks, target_wiki_token, overwrite=getattr(args, "overwrite", False))
if ok:
open_result(target_wiki_token)
print(f"{date_str} 飞书日志已写入")
sys.exit(0)
print("❌ 写入失败")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,59 @@
---
name: 项目调研
description: 平台分析、项目调研、各 APP 资料、A 群等聊天记录与对话分类统一归档到 开发/7.项目调研,按项目分子目录。
triggers: 项目调研、平台分析、A群、A群聊天记录、聊天记录清理、对话分类、对话分类号、按项目归档、调研归档、APP资料、其他APP、各APP
owner: 水溪
group: 水
---
# 项目调研
将**平台分析**、**项目调研**、各 **APP 资料**、**A 群等群聊记录**、**对话分类**(含对话分类号)及**其他相关资料**,统一收纳到 `开发/7.项目调研`**按项目分子目录**。凡与某 APP 或某项目相关的调研、聊天记录、对话分类,均放入对应项目子目录。
---
## 根目录(固定)
| 路径 | 说明 |
|------|------|
| **根目录** | `/Users/karuo/Documents/开发/7.项目调研` |
| 规范说明 | `7.项目调研/README.md` |
---
## 执行规则
1. **识别意图**:用户提到「项目调研」「平台分析」「把 XX 聊天记录/对话清理到这里」「A 群」「按项目归档」「对话分类」等 → 使用本 Skill产出与归档一律进入 `7.项目调研`
2. **按项目归档**
- 若已有对应项目子目录 → 放入该项目下的 `平台分析/``聊天记录/``对话分类/``资料/`
- 若无对应项目 → 先放入 `_待归档/`,或在用户指定项目名后新建 `项目名/` 及四类子目录。
3. **目录结构**(与根目录 README 一致):
- 每个项目下:`平台分析/``聊天记录/``对话分类/``资料/`
- 群聊记录、对话分类可再按群名、日期或主题分子目录。
---
## 流程摘要
| 步骤 | 动作 |
|------|------|
| 1 | 确认内容类型:平台分析 / 聊天记录 / 对话分类 / 资料 |
| 2 | 确认或新建项目子目录(无则用 `_待归档` 或新建项目名) |
| 3 | 写入对应子目录,命名清晰(含日期或主题) |
| 4 | 必要时更新 `7.项目调研/README.md` 的「当前项目列表」 |
---
## 常用路径速查
- 根目录:`/Users/karuo/Documents/开发/7.项目调研`
- 待归档:`7.项目调研/_待归档/`
- 项目 X 的聊天记录:`7.项目调研/项目X/聊天记录/`
- 项目 X 的平台分析:`7.项目调研/项目X/平台分析/`
---
## 相关
- 对话归档(日常 AI 对话):`水溪_整理归档/对话归档/SKILL.md`,路径为 `个人/3、工作台/AI的每日对话/`,与本文不同。
- 本 Skill 仅负责「项目调研」相关平台分析、项目调研、群聊与对话分类、APP 资料,统一进 `开发/7.项目调研` 并按项目存放。

View File

@@ -0,0 +1,87 @@
---
name: 抖音发布
description: 通过抖音开放平台实现抖音登录OAuth与视频发布。与 Soul 竖屏成片、视频切片联动,将成片一键发布到抖音。若使用存客宝/腕推等矩阵工具发布抖音,可在此 Skill 补充对接方式。
triggers: 抖音发布、发布到抖音、抖音登录、抖音上传、腕推抖音
owner: 木叶
group: 木
version: "1.0"
updated: "2026-02-28"
---
# 抖音发布
> **登录与发布**:使用**抖音开放平台** OAuth 获取用户授权access_token、open_id再调用「上传视频」+「创建视频」接口发布。
> **存客宝**:当前存客宝 Skill 与文档中无抖音发布 SDK若后续存客宝或腕推等工具提供抖音发布能力可在此 Skill 补充脚本与配置。
---
## 一、流程概览
```
抖音开放平台应用(申请「代替用户发布内容到抖音」)
→ 用户 OAuth 登录授权 → 获得 access_token、open_id
→ 上传视频upload_video→ 获得 video_id
→ 创建视频create_video→ 发布到抖音
```
---
## 二、前置条件
1. **抖音开放平台** [https://partner.open-douyin.com](https://partner.open-douyin.com) 注册开发者、创建应用。
2. **能力申请**:应用详情 → 能力管理 → 能力实验室 → **代替用户发布内容到抖音**
3. **用户授权**:用户通过 OAuth 授权后,获得 `access_token`(约 15 天)、`open_id`;可存于本地或存客宝等系统供脚本使用。
---
## 三、一键命令(发布单条成片)
```bash
cd /Users/karuo/Documents/个人/卡若AI/03_卡木/木叶_视频内容/抖音发布/脚本
# 使用已保存的 token 发布(需先跑一次登录并保存)
python3 douyin_publish.py --video "/path/to/成片/标题.mp4" --title "视频标题 #话题"
# 指定 token 文件(默认读 脚本/.env 或 config.json 中的 access_token、open_id
python3 douyin_publish.py --video "/path/to/xxx.mp4" --title "标题" --token-file ./tokens.json
```
---
## 四、登录形式(获取 access_token / open_id
抖音开放平台采用 **OAuth 2.0**
1. **授权码模式**:引导用户打开授权页 → 用户同意后回调带 `code` → 用 `code``access_token``open_id`
2. **脚本用法**:首次需在浏览器完成授权,或使用开放平台「获取 access_token」接口需 client_key、client_secret、code。将得到的 `access_token``open_id` 写入 `脚本/tokens.json` 或环境变量,供 `douyin_publish.py` 读取。
详见:`参考资料/抖音开放平台_登录与发布流程.md`
---
## 五、与视频切片 / Soul 竖屏的联动
- **成片目录**Soul 竖屏成片输出在 `xxx_output/成片/`,文件名为标题(如 `没人来就一个人站站到最后钱才来.mp4`)。
- **批量发布**:可对 `成片/` 目录遍历,逐条调用 `douyin_publish.py --video <path> --title <文件名或 highlights 标题>`;标题可来自 `成片/目录索引.md``highlights.json`
- **腕推 / 存客宝**若使用腕推或存客宝的抖音发布能力可将对接方式API 文档、SDK 路径)补充到本 Skill 的「参考资料」或脚本说明中,脚本可改为调其接口。
---
## 六、相关文件
| 文件 | 说明 |
|------|------|
| `脚本/douyin_publish.py` | 发布脚本:读 token → 上传视频 → 创建视频 |
| `参考资料/抖音开放平台_登录与发布流程.md` | 开放平台 OAuth、上传、创建视频接口说明与链接 |
---
## 七、API 摘要(抖音开放平台)
| 步骤 | 接口 | 说明 |
|------|------|------|
| 登录 | OAuth 授权 → access_token、open_id | 用户授权后获得 |
| 上传 | POST `/api/douyin/v1/video/upload_video/` | form: video=@文件;返回加密 video_id |
| 发布 | POST `/api/douyin/v1/video/create_video/` | body: video_id、text标题可带话题 |
视频时长不超过 15 分钟;标题不超过 1000 字;每日发布上限 75 条(同一应用下)。

View File

@@ -0,0 +1,46 @@
# 抖音开放平台:登录与发布流程
> 用于「抖音发布」Skill获取抖音登录态access_token、open_id并发布视频。
## 一、登录OAuth 获取 access_token / open_id
1. **开放平台**https://partner.open-douyin.com
创建应用,获取 `client_key``client_secret`
2. **能力申请**:应用详情 → 能力管理 → 能力实验室 → **代替用户发布内容到抖音**
3. **用户授权**
- 授权页:`https://open.douyin.com/platform/oauth/connect/?client_key={client_key}&response_type=code&scope=user_info,video.create&redirect_uri={redirect_uri}&state={state}`
- 用户同意后跳转 `redirect_uri?code=xxx&state=xxx`
4. **用 code 换 token**
POST `https://open.douyin.com/oauth/access_token/`
参数:`client_key``client_secret``code``grant_type=authorization_code`
返回:`access_token``open_id``expires_in` 等。
文档https://partner.open-douyin.com/docs/resource/zh-CN/dop/develop/openapi/account-permission/get-access-token
5. **保存**:将 `access_token``open_id` 写入 `脚本/tokens.json` 或环境变量,供发布脚本使用。
access_token 有效期约 15 天,过期需重新授权或刷新。
## 二、上传视频
- **接口**POST `https://open.douyin.com/api/douyin/v1/video/upload_video/?open_id={open_id}`
- **请求头**`access-token: {access_token}``Content-Type: multipart/form-data`
- **Body**form 字段 `video` = 视频文件(本地文件)
- **返回**`data.video.video_id`(加密 ID用于创建视频
文档https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/openapi/video-management/douyin/create-video/upload-video
## 三、创建视频(发布)
- **接口**POST `https://open.douyin.com/api/douyin/v1/video/create_video/?open_id={open_id}`
- **请求头**`access-token: {access_token}``Content-Type: application/json`
- **Body**`{"video_id": "{上一步的 video_id}", "text": "标题 #话题1 #话题2"}`
- **限制**:视频时长 ≤15 分钟;标题 ≤1000 字;同一应用下每日 ≤75 条
文档https://partner.open-douyin.com/docs/resource/zh-CN/dop/develop/openapi/video-management/douyin/create-video/video-create
## 四、存客宝 / 腕推
- 当前**存客宝** Skill 与文档中无抖音登录或发布 SDK。
- 若使用**腕推**或其它矩阵工具发布抖音请将对接方式API 或 SDK 文档路径补充到「抖音发布」Skill 或本参考资料,脚本可改为调用对应接口。

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
抖音发布:使用抖音开放平台 API 上传视频并发布。
依赖:用户已完成 OAuth 授权access_token、open_id 存于环境变量或 tokens.json。
与「抖音发布」Skill 配套;存客宝/腕推若提供抖音发布接口可替换本脚本内的 API 调用。
"""
import argparse
import json
import os
import sys
from pathlib import Path
try:
import requests
except ImportError:
print("请安装 requests: pip install requests", file=sys.stderr)
sys.exit(1)
# 抖音开放平台 API 基址
BASE = "https://open.douyin.com"
UPLOAD_URL = f"{BASE}/api/douyin/v1/video/upload_video/"
CREATE_URL = f"{BASE}/api/douyin/v1/video/create_video/"
def load_tokens(token_file: Path | None) -> tuple[str, str]:
"""从环境变量或 token 文件读取 access_token、open_id。"""
token = os.environ.get("DOUYIN_ACCESS_TOKEN") or os.environ.get("ACCESS_TOKEN")
open_id = os.environ.get("DOUYIN_OPEN_ID") or os.environ.get("OPEN_ID")
if token_file and token_file.exists():
with open(token_file, "r", encoding="utf-8") as f:
data = json.load(f)
token = token or data.get("access_token")
open_id = open_id or data.get("open_id")
if not token or not open_id:
print(
"未配置 access_token / open_id。请先完成抖音 OAuth 登录,将 access_token、open_id 写入 tokens.json 或设置环境变量 DOUYIN_ACCESS_TOKEN、DOUYIN_OPEN_ID。",
file=sys.stderr,
)
print("参见:参考资料/抖音开放平台_登录与发布流程.md", file=sys.stderr)
sys.exit(1)
return token.strip(), open_id.strip()
def upload_video(access_token: str, open_id: str, video_path: str) -> str:
"""上传视频,返回加密 video_id。"""
path = Path(video_path)
if not path.exists() or not path.is_file():
raise FileNotFoundError(f"视频文件不存在: {video_path}")
url = f"{UPLOAD_URL}?open_id={open_id}"
headers = {"access-token": access_token}
with open(path, "rb") as f:
files = {"video": (path.name, f, "video/mp4")}
r = requests.post(url, headers=headers, files=files, timeout=120)
r.raise_for_status()
data = r.json()
if data.get("extra", {}).get("error_code") != 0:
raise RuntimeError(data.get("extra", {}).get("description", "上传失败"))
video_id = data.get("data", {}).get("video", {}).get("video_id")
if not video_id:
raise RuntimeError("响应中无 video_id")
return video_id
def create_video(access_token: str, open_id: str, video_id: str, text: str) -> dict:
"""创建视频(发布)。"""
url = f"{CREATE_URL}?open_id={open_id}"
headers = {"access-token": access_token, "Content-Type": "application/json"}
body = {"video_id": video_id, "text": text[:1000]}
r = requests.post(url, headers=headers, json=body, timeout=30)
r.raise_for_status()
data = r.json()
if data.get("extra", {}).get("error_code") != 0:
raise RuntimeError(data.get("extra", {}).get("description", "发布失败"))
return data.get("data", {})
def main():
parser = argparse.ArgumentParser(description="抖音发布:上传视频并发布到抖音(开放平台 API")
parser.add_argument("--video", "-v", required=True, help="本地视频路径(竖屏成片)")
parser.add_argument("--title", "-t", required=True, help="发布标题,可带 #话题")
parser.add_argument("--token-file", "-f", type=Path, default=Path(__file__).parent / "tokens.json", help="存 access_token、open_id 的 JSON 文件")
args = parser.parse_args()
access_token, open_id = load_tokens(args.token_file)
print("上传视频...")
video_id = upload_video(access_token, open_id, args.video)
print("发布中...")
result = create_video(access_token, open_id, video_id, args.title)
print("发布成功:", result.get("item_id", "OK"))
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,4 @@
{
"access_token": "抖音开放平台 OAuth 获取的 access_token",
"open_id": "用户 open_id"
}

View File

@@ -19,11 +19,17 @@ updated: "2026-02-17"
## ⭐ Soul派对切片流程默认
```
原始视频 → MLX转录 → 字幕转简体 → 高光识别(Ollama→规则) → 批量切片 → soul_enhance → 输出成片
原始视频 → MLX转录 → 字幕转简体 → 高光识别(当前模型/AI) → 批量切片 → soul_enhance → 输出成片
↑ ↓
提取后立即繁转简+修正错误 封面+字幕(已简体)+加速10%+去语气词
```
**切片时长**:每段为**完整的一个片段**,时长 **30 秒300 秒**,由该完整片段起止时间决定。**标题**用一句**刺激性观点**(见 `Soul竖屏切片_SKILL.md`)。
**提问→回答 结构**若片段内有人提问前3秒优先展示**提问问题**,再播回答;高光识别填 `question``hook_3sec` 与之一致,成片整条去语助词。详见 `参考资料/视频结构_提问回答与高光.md``参考资料/高光识别提示词.md`
**Soul 竖屏专用**:抖音/首页用竖屏成片、完整参数与流程见 → **`Soul竖屏切片_SKILL.md`**(竖屏 498×1080、crop 参数、批量命令)。
### 一键命令Soul派对专用
#### 一体化流水线(推荐)
@@ -57,6 +63,46 @@ python3 batch_clip.py -i 视频.mp4 -l highlights.json -o clips/ -p soul
python3 soul_enhance.py -c clips/ -l highlights.json -t transcript.srt -o clips_enhanced/
```
#### 按章节主题提取推荐第9章单场成片
以**章节 .md 正文**为来源提取核心主题,再在转录稿中匹配时间,不限于 5 分钟、片段数与章节结构一致。详见 `参考资料/主题片段提取规则.md`
```bash
# 从章节生成 highlights再走 batch_clip + soul_enhance
python3 chapter_themes_to_highlights.py -c "第112场.md" -t transcript.srt -o highlights_from_chapter.json
python3 batch_clip.py -i 视频.mp4 -l highlights_from_chapter.json -o clips/ -p soul112
python3 soul_enhance.py -c clips/ -l highlights_from_chapter.json -t transcript.srt -o clips_enhanced/
```
- **主题来源**:章节 .md 按 `---` 分块,每块一个主题;文件名由 batch_clip 按 `前缀_序号_标题` 生成(标题仅保留中文与安全字符)。
### Soul 竖屏成片(横版源 → 竖屏中段去白边)
**约定**:以后剪辑 Soul 视频,成片统一做「竖屏中段」裁剪:横版 1920×1080 只保留中间竖条并去掉左右白边,输出 498×1080 竖屏。
| 步骤 | 说明 |
|------|------|
| 源 | 横版 1920×1080soul_enhance 输出) |
| 1 | 取竖条 608×1080起点 **x=483**(相对画面左) |
| 2 | 裁掉左侧白边 60px、右侧白边 50px → 内容区宽 498 |
| 输出 | **498×1080** 竖屏,仅内容窗口 |
**FFmpeg 一条命令(固定参数):**
```bash
# 单文件。输入为 1920×1080 的 enhanced 成片
ffmpeg -y -i "输入_enhanced.mp4" -vf "crop=608:1080:483:0,crop=498:1080:60:0" -c:a copy "输出_竖屏中段.mp4"
```
**批量对某目录下所有 \*_enhanced.mp4 做竖屏中段:**
```bash
# 脚本目录下执行,或直接调用
python3 脚本/soul_vertical_crop.py --dir "/path/to/clips_enhanced" --suffix "_竖屏中段"
```
参数说明见:`参考资料/竖屏中段裁剪参数说明.md`
### 增强功能说明
| 功能 | 说明 |
@@ -213,6 +259,8 @@ python3 scripts/burn_subtitles_clean.py -i enhanced.mp4 -s clean.srt -o 成片.m
|------|------|---------|
| **soul_slice_pipeline.py** | Soul 切片一体化流水线 | ⭐⭐⭐ 最常用 |
| **soul_enhance.py** | 封面+字幕(简体)+加速+去语气词 | ⭐⭐⭐ |
| **soul_vertical_crop.py** | Soul 竖屏中段批量裁剪横版→498×1080 去白边) | ⭐⭐⭐ |
| chapter_themes_to_highlights.py | 按章节 .md 主题提取片段本地模型→highlights.json | ⭐⭐⭐ |
| identify_highlights.py | 高光识别Ollama→规则 | ⭐⭐ |
| batch_clip.py | 批量切片 | ⭐⭐ |
| one_video.py | 单视频一键成片 | ⭐⭐ |

View File

@@ -0,0 +1,108 @@
# Soul 竖屏切片 · 专用 Skill
> 专门切 Soul 派对视频为**竖屏成片**,用于抖音/首页。**只保留两个文件夹**:剪辑 → 成片。
---
## 一、两文件夹结构(无 clips_enhanced / clips_竖屏
| 文件夹 | 含义 | 内容 |
|--------|------|------|
| **clips/** | 剪辑 | batch_clip 输出的横版切片soul112_01_标题.mp4 |
| **成片/** | 成片 | 竖屏 498×1080 + 封面 + 字幕 + 去语助词,文件名为**纯标题**(无序号、无 _enhanced |
不再单独生成 `clips_enhanced``clips_竖屏`;成片由 `soul_enhance` 一步直出到 `成片/`
---
## 二、视频结构:提问→回答 + 前3秒高光 + 去语助词
- **前3秒**:先看片段有没有人提问;**有提问**则把**提问的问题**放到前3秒封面/前贴),先展示问题再播回答;无提问则用金句/悬念作 hook。
- **成片链路**前3秒展示问题或金句→ 正片回答 → **整片去除语助词**(提问与回答部分均由 soul_enhance 清理)。
- **高光**按「3秒高光亮点」剪每段 30300 秒完整语义单元;高光识别若有提问须填 `question`,且 `hook_3sec` 与之一致。
详见:`参考资料/视频结构_提问回答与高光.md``参考资料/高光识别提示词.md`
---
## 三、流程总览
```
原视频 → 转录(MLX) → 高光识别(含 question/hook_3sec见高光识别提示词) → batch_clip → soul_enhance(成片竖屏直出到 成片/)
```
- **batch_clip**:输出到 `clips/`
- **soul_enhance -o 成片/ --vertical --title-only**:封面(优先用 question 作前3秒+ 字幕 + **完整去语助词** + 竖屏裁剪,直接输出到 `成片/`,文件名为标题
---
## 四、高光与切片30 秒300 秒)
| 项 | 规则 |
|----|------|
| **单段时长** | **30300 秒**,由完整片段起止决定 |
| **完整性** | 每段是一个完整话题/情节,有头有尾 |
| **标题** | **一句刺激性观点**(金句、反常识、结论句) |
| **数量** | 建议 ≤10 段/场 |
| **语助词** | 识别与剪辑须符合 `参考资料/高光识别提示词.md`,成片由 soul_enhance 统一去语助词 |
---
## 五、成片:封面 + 字幕 + 竖屏
- **封面**:竖屏 498×1080 内**不超出界面**;深色渐变背景(墨绿→绿)、左上角 Soul logo、标题文字**严格居中**且左右留白 44px多行自动换行不裁切。
- **字幕**:封面结束后才显示,**居中**在竖屏内;语助词由 soul_enhance 统一清理。重新加字幕时加 `--force-burn-subs`
- **竖屏**498×1080crop 参数与 `参考资料/竖屏中段裁剪参数说明.md` 一致
---
## 六、竖屏裁剪参数(成片内嵌)
| 步骤 | 滤镜 |
|------|------|
| 1 | crop=608:1080:483:0 |
| 2 | crop=498:1080:60:0 |
**输出**498×1080 竖屏。
---
## 七、完整命令示例112 场)
**1. 高光**(当前模型生成 highlights.json标题用刺激性观点30300 秒完整段;语助词与节奏感见 `参考资料/高光识别提示词.md`
**2. 剪辑clips**
```bash
python3 batch_clip.py -i "原视频.mp4" -l highlights.json -o clips/ -p soul112
```
**3. 成片(竖屏+封面+字幕+去语助词,直出到 成片/**
```bash
python3 soul_enhance.py -c clips/ -l highlights.json -t transcript.srt -o 成片/ --vertical --title-only
```
输出目录结构示例:
```
xxx_output/
clips/ # 横版切片
成片/ # 竖屏成片,文件名为标题.mp4
成片/目录索引.md
highlights.json
transcript.srt
```
---
## 八、参数速查
| 项 | 值 |
|----|-----|
| 文件夹 | 仅 **clips/**、**成片/** |
| 成片尺寸 | 498×1080 竖屏 |
| 成片文件名 | 纯标题(无 01、无 _enhanced |
| 单段时长 | 30300 秒 |
| 高光/语助词 | 见 `参考资料/高光识别提示词.md` |
详细 crop 说明见:`参考资料/竖屏中段裁剪参数说明.md`
**发布到抖音**成片生成后可用「抖音发布」Skill开放平台 OAuth 登录 + 上传/创建视频)或腕推等工具发布;见 `03_卡木/木叶_视频内容/抖音发布/SKILL.md`

View File

@@ -0,0 +1,124 @@
# 主题片段提取规则
> 按「章节正文」提取核心主题,再在视频转录稿中匹配时间,产出切片方案。
> 与《视频切片》SKILL 配套,文件名与流程与 batch_clip / soul_enhance 一致。
---
## 一、数据来源
| 来源 | 用途 |
|------|------|
| **章节 .md** | 第9章单场文章`第112场一个人起头维权挣了大半套房.md`),作为**主题与优质片段内容**的唯一依据 |
| **transcript.srt** | 该场派对视频的 MLX Whisper 转录稿(带时间戳),用于定位每段主题在视频中的起止时间 |
---
## 二、流程(步骤顺序)
```
1. 章节正文拆主题
→ 脚本 chapter_themes_to_highlights.py 按 --- 或段落拆成若干主题
2. 转录稿 + 本地模型匹配时间
→ 用本地最佳模型Ollama qwen2.5:7b无则 1.5b)在 transcript.srt 中为每个主题找一段连续内容,输出 start_time / end_time
3. 输出 highlights.json
→ 格式与 identify_highlights.py 一致,供 batch_clip 与 soul_enhance 使用
4. 批量切片
→ batch_clip.py -i 视频 -l highlights.json -o clips/ -p soul112
5. 增强(封面 + 字幕)
→ soul_enhance.py -c clips/ -l highlights.json -t transcript.srt -o clips_enhanced/
```
---
## 三、时长与数量
- **视频长度由完整片段决定**:每段是一个**完整的话题/情节**,有头有尾,不从中途截断。
- **单段时长****30 秒300 秒**0.55 分钟),由该完整片段实际时长决定。
- **片段数量**:由章节主题或高光数量决定(如 10 段)。
---
## 四、highlights.json 字段(与视频切片规则一致)
每个片段必须包含(供 batch_clip + soul_enhance 使用):
| 字段 | 说明 |
|------|------|
| `title` | 简短标题15 字内,用于**文件名**与封面/列表展示 |
| `start_time` | 开始时间,格式 `HH:MM:SS` |
| `end_time` | 结束时间,格式 `HH:MM:SS` |
| `hook_3sec` | 前 3 秒 Hook 文案15 字内,封面/前贴用 |
| `cta_ending` | 结尾 CTA统一用「关注我每天学一招私域干货」 |
| `transcript_excerpt` | 该段内容 50 字内摘要 |
| `reason` | 为何该段时间对应该主题(可选) |
---
## 五、文件名规则(与 batch_clip 一致)
- **格式**`{前缀}_{序号:02d}_{标题}.mp4`
- **前缀**:由 batch_clip 的 `-p` 指定,如 `soul112` 表示第 112 场。
- **标题**:由 highlights 中该条的 `title`**sanitize_filename** 得到:
- 仅保留中文、空格、下划线、连字符;
- 过长截断(默认 50 字);
- 若为空则用「片段」。
**示例(第 112 场,前缀 soul112**
- `soul112_01_起头难跑通就能变成付费服务.mp4`
- `soul112_02_没人起头就起头一个人站着.mp4`
- `soul112_03_干货起头难跑通更难.mp4`
标题来自 highlights 中每条条目的 `title` 字段,经 `batch_clip.sanitize_filename` 后仅保留中文、空格、`_``-`,过长截断至 50 字。
---
## 六、命令示例112 场)
```bash
# 1. 转录(若尚未有 transcript.srt
cd 03_卡木/木叶_视频内容/视频切片/脚本
conda activate mlx-whisper
# 先跑 soul_slice_pipeline 到「转录+转简体」完成,或单独用 mlx_whisper 生成 transcript.srt
# 2. 按章节主题生成 highlights.json
python3 chapter_themes_to_highlights.py \
--chapter "/Users/karuo/Documents/个人/2、我写的书/《一场soul的创业实验》/第四篇|真实的赚钱/第9章我在Soul上亲访的赚钱案例/第112场一个人起头维权挣了大半套房.md" \
--transcript "/Users/karuo/Movies/soul视频/soul 派对 112场 20260228_output/transcript.srt" \
--output "/Users/karuo/Movies/soul视频/soul 派对 112场 20260228_output/highlights_from_chapter.json"
# 3. 批量切片(按 highlights 时间点切)
python3 batch_clip.py -i "/Users/karuo/Movies/soul视频/原视频/soul 派对 112场 20260228.mp4" \
-l "/Users/karuo/Movies/soul视频/soul 派对 112场 20260228_output/highlights_from_chapter.json" \
-o "/Users/karuo/Movies/soul视频/soul 派对 112场 20260228_output/clips" \
-p soul112
# 4. 增强(封面 + 字幕居中)
python3 soul_enhance.py -c "/Users/karuo/Movies/soul视频/soul 派对 112场 20260228_output/clips" \
-l "/Users/karuo/Movies/soul视频/soul 派对 112场 20260228_output/highlights_from_chapter.json" \
-t "/Users/karuo/Movies/soul视频/soul 派对 112场 20260228_output/transcript.srt" \
-o "/Users/karuo/Movies/soul视频/soul 派对 112场 20260228_output/clips_enhanced"
```
---
## 七、本地模型
- **优先**Ollama `qwen2.5:7b`(脚本默认)
- **备选**`qwen2.5:1.5b``--model qwen2.5:1.5b`
- 若 7b 未安装,脚本可改为自动回退 1.5b(需在脚本内加 try/except 换模型)。
---
## 八、与「高光识别」的区别
| 方式 | 主题来源 | 时长 |
|------|----------|------|
| **identify_highlights.py** | 仅从转录稿由 AI 识别「干货/金句」 | 默认 15 分钟 |
| **chapter_themes_to_highlights.py** | 从章节 .md 拆主题,再在转录中匹配 | 按内容完整度,不限于 5 分钟 |
二者输出的 highlights.json 格式相同,均可直接用于 batch_clip 与 soul_enhance。

View File

@@ -0,0 +1,33 @@
# Soul 竖屏中段裁剪参数说明
> 与 **视频切片** Skill「Soul 竖屏成片」一致,以后剪辑 Soul 视频统一用此参数。
## 源与输出
- **源视频**:横版 1920×108016:9soul_enhance 输出)
- **需求**:保留画面中间竖条(手机内容区),去左右白边
- **输出****498×1080** 竖屏
## 当前固定参数(已微调)
| 步骤 | 滤镜 | 说明 |
|------|------|------|
| 1 | crop=608:1080:483:0 | 从横版取竖条 608 宽,起点 x=483 |
| 2 | crop=498:1080:60:0 | 裁掉左侧白边 60px、右侧 50px内容宽 498 |
| 输出 | 498×1080 | 仅内容窗口 |
**一条命令:**
```bash
ffmpeg -y -i "输入_enhanced.mp4" -vf "crop=608:1080:483:0,crop=498:1080:60:0" -c:a copy "输出_竖屏中段.mp4"
```
**批量:** 使用 `脚本/soul_vertical_crop.py --dir clips_enhanced目录`。加 `--title-only` 时输出文件名为纯标题(无序号、无「竖屏中段」)。
## 若源为竖版 1080×1920
保留中间宽度示例:
```bash
ffmpeg -y -i "输入.mp4" -vf "crop=918:1920:81:0" -c:a copy "输出.mp4"
```

View File

@@ -0,0 +1,58 @@
# 视频结构:提问→回答 + 前3秒高光 + 去语助词
> Soul 派对切片成片的统一结构先看片段有没有人提问有则前3秒展示**提问**,再播**回答**全片去除语助词高光按「3秒亮点」剪。
---
## 一、成片链路(一句话)
**前3秒展示「问题」 → 正片播「回答」 → 整片去语助词按3秒高光剪出完整片段。**
---
## 二、提问→回答 结构
| 步骤 | 动作 | 说明 |
|------|------|------|
| 1 | 看片段是否有人提问 | 高光识别时判断:该段时间内是否有观众/连麦者提出问题 |
| 2 | 若有提问 | 提取**提问原文**(可去语助词、精简一句),填 `question``hook_3sec` 与之一致 |
| 3 | 前3秒 | 封面/前贴展示「提问的问题」,让用户先看到问题 |
| 4 | 正片 | 从回答开始,或「先问后答」完整保留,形成完整提问→回答链路 |
| 5 | 若无提问 | 前3秒用金句/悬念/数据等 hook_3sec无 question 字段 |
**目的**:用户先被「问题」抓住,再听回答,完播率更高;提问与回答中的语助词在成片阶段**完整去除**。
---
## 三、前3秒高光
- **有提问**前3秒 = 提问问题question / hook_3sec先问后答。
- **无提问**前3秒 = 金句/悬念/数据/问题型 hook`高光识别提示词.md`)。
- 高光识别按「3秒能抓人」的标准选段每段 30300 秒完整语义单元;输出里若有提问则必填 `question``hook_3sec` 与之一致。
---
## 四、去语助词(完整)
- **范围**整条成片含前3秒展示的提问部分与后面回答部分
- **执行**soul_enhance 对字幕/文字统一做语助词清理(嗯、啊、呃、那个、就是、然后等),见 `高光识别提示词.md` 语助词列表。
- **结果**:提问与回答的文案在成片中均为去语助词后的干净版。
---
## 五、剪辑与成片流程(与 Skill 一致)
```
原视频 → 转录(MLX) → 高光识别(含 question/hook_3sec) → batch_clip → soul_enhance(封面=提问/金句 + 字幕 + 去语助词 + 竖屏) → 成片/
```
- **高光识别**:按 `高光识别提示词.md`,有提问则填 questionhook_3sec 优先用提问。
- **batch_clip**:按 highlights.json 切出 clips/。
- **soul_enhance**:封面/前3秒用 `question ?? hook_3sec ?? title`,字幕与整片去语助词,竖屏直出到 成片/。
---
## 六、与高光识别提示词的关系
- 本结构中的「提问→回答」「前3秒=提问」「去语助词」均与 `高光识别提示词.md` 一致。
- 高光识别输出 JSON 需含:有提问时 `question` + `hook_3sec`(与 question 一致);成片阶段用 question 优先做前3秒展示并做完整去语助词。

View File

@@ -48,9 +48,21 @@
- 吊人胃口的开场
- "接下来这段更精彩"
# 提问→回答 结构前3秒优先用提问
**成片结构**:先展示**提问**前3秒再进入**回答**(正片)。若片段里有人提问,必须把**提问的问题**放到前3秒。
1. **判断**:看该高光片段文字稿中是否有人提问(观众/连麦者问的问题)。
2. **若有提问**
- 提取**提问的原文**(可去语助词、精简为一句),填入 `question` 字段;
- `hook_3sec` 优先用该提问内容(与 question 一致成片前3秒封面/贴片展示「问题」,正片从回答开始或先问后答。
3. **若无提问**`hook_3sec` 用金句/悬念/数据等抓眼球开场,`question` 可省略。
**目的**前3秒让用户看到「问题」再听回答形成完整**提问→回答**链路;提问与回答部分的语助词均在成片时完整去除。
# 前3秒Hook设计原则
Hook的目的是让用户在前3秒决定看完整个视频。
Hook的目的是让用户在前3秒决定看完整个视频。**有提问时 Hook = 提问问题。**
## Hook类型选择
@@ -96,9 +108,13 @@ CTA的目的是引导用户完成下一步动作。
2. **完整性**:必须是完整的语义单元,不能话说一半
3. **独立性**:单独播放能理解,不依赖上下文
4. **开场**尽量以金句或问题开头3秒内抓住注意力
5. **语助词与空格**:识别时标注或剔除语助词(嗯、啊、呃、那个、就是、然后等)及中间无意义空排/停顿,与主题片段提取规则一致,成片剪辑时由 soul_enhance 统一清理
6. **节奏感**:优先选讲话有步骤、有节奏的片段(起承转合清晰、不拖沓、少重复),避免大段碎碎念或断句混乱
# 输出格式严格JSON
每个片段若为「有人提问+回答」结构,必须填 `question`,且 `hook_3sec` 优先用提问内容前3秒先展示提问再回答
```json
[
{
@@ -108,9 +124,10 @@ CTA的目的是引导用户完成下一步动作。
"end_time": "00:13:56",
"duration_sec": 82,
"transcript_excerpt": "这段话的前50字...",
"hook_3sec": "99%的人都不知道这个方法",
"hook_type": "悬念型",
"hook_reason": "用数据+悬念制造好奇心",
"question": "为什么你做私域总是不赚钱?",
"hook_3sec": "为什么你做私域总是不赚钱?",
"hook_type": "问题型",
"hook_reason": "片段内有人提问前3秒用提问抓注意力",
"cta_ending": "想学更多评论区扣1拉你进群",
"cta_type": "group",
"reason": "为什么这个片段有传播力20字内",
@@ -121,6 +138,9 @@ CTA的目的是引导用户完成下一步动作。
]
```
- **question**(可选但推荐):片段中**观众/连麦者的提问原文**可精简、去语助词。有则成片前3秒展示该问题再播回答。
- **hook_3sec**:有提问时与 question 一致;无提问时用金句/悬念等。
# 评分说明
- **9-10分**:爆款潜力,必切
@@ -136,6 +156,8 @@ CTA的目的是引导用户完成下一步动作。
4. 每个片段必须包含 hook_3sec 和 cta_ending
5. Hook和CTA必须与片段内容强相关
6. 只输出JSON不要其他解释
7. **与主题片段提取一致**Soul 剪辑全流程(高光识别 → batch_clip → soul_enhance → 竖屏裁剪)继承本提示词;主题片段由章节拆解时,判断标准、语助词与节奏感要求与本提示词保持一致。
8. **有提问时**:片段内有人提问则必须填 `question`,且 `hook_3sec` 与 question 一致成片前3秒先展示提问再回答去语助词覆盖整条成片含提问与回答
# 视频文字稿(带时间戳)
@@ -156,6 +178,19 @@ CTA的目的是引导用户完成下一步动作。
| `{{TRANSCRIPT}}` | 完整文字稿 | Whisper输出 |
| `{{DEFAULT_CTA}}` | 默认CTA文案 | "关注我,获取更多干货" |
## 语助词与空排(成片剪辑时去除)
高光识别与主题片段提取时,应避免选「语助词密集、空排多」的段落;成片由 soul_enhance 统一清理:
- **语助词**嗯、啊、呃、额、哦、噢、唉、哎、诶、喔、那个、就是、然后、这个、所以说、怎么说、怎么说呢、对吧、是吧、好吧、行吧、那、就、就是那个、其实、那么、然后呢、还有就是、以及、另外、等等、你知道吗、我跟你说、好、对、OK
- **中间空排**:长时间静音、无意义停顿在剪辑时由 soul_enhance 做静音检测并压缩,高光识别时优先选节奏紧凑的片段
## 节奏感要求
- 优先:有步骤、有起伏、起承转合清晰的片段
- 避免:大段碎碎念、断句混乱、同一句话重复多遍、长时间无信息量停顿
- 与主题片段提取规则一致:每段为完整语义单元,时长 30300 秒,标题为一句刺激性观点
## 文字稿格式要求
输入给AI的文字稿应该是这样的格式

View File

@@ -0,0 +1,313 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
按章节正文提取主题片段
========================
以「第9章单场文章」的 .md 正文为来源提取核心主题再用本地最佳模型Ollama
在转录稿中匹配每段主题的起止时间,输出 highlights.json供 batch_clip + soul_enhance 使用。
- 主题来源:章节 .md按 --- 或段落拆成若干核心主题
- 不限 5 分钟:每段按内容完整度定时长,可 15 分钟或更长
- 文件名:由 batch_clip 按「前缀_序号_标题」生成标题来自 title仅保留中文与安全字符
"""
import argparse
import json
import re
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
OLLAMA_URL = "http://localhost:11434"
DEFAULT_CTA = "关注我,每天学一招私域干货"
# 本地优先用 7b失败则回退 1.5b
OLLAMA_MODELS = ["qwen2.5:7b", "qwen2.5:1.5b"]
def parse_chapter_themes(md_path: Path) -> list[dict]:
"""
从章节 .md 拆出主题列表。
每个主题:{"title": "简短标题", "text": "该段正文摘要或全文"}
按 --- 分块,第一块视为开篇/场次信息可跳过,其余每块为一个主题。
"""
text = md_path.read_text(encoding="utf-8")
# 按 --- 分块(兼容 --- 前后换行)
blocks = re.split(r"\n---+\s*\n", text.strip())
themes = []
for idx, block in enumerate(blocks):
block = block.strip()
# 跳过过短或纯元信息的块如仅「第112场135分钟」
if not block or len(block) < 25:
continue
# 去掉首行的 # 标题、日期、场次
lines = [ln.strip() for ln in block.split("\n") if ln.strip()]
first_line = lines[0] if lines else ""
if idx == 0 and (re.match(r"^#\s+", first_line) or re.match(r"^\d{4}", first_line) or re.match(r"^第\s*\d+\s*场", first_line)):
# 第一块若只是标题/日期/场次,取第二行起或整块
content_lines = lines[1:] if len(lines) > 1 else lines
if not content_lines:
continue
first_line = content_lines[0]
# 主题标题:首句或前 22 字
if len(first_line) > 25:
title = first_line[:22] + ""
else:
title = first_line or "片段"
excerpt = re.sub(r"\*\*[^*]+\*\*?", "", block)[:500]
themes.append({"title": title, "text": excerpt})
return themes
def parse_srt_segments(srt_path: Path) -> list[dict]:
"""解析 SRT 为 [{start_sec, end_sec, text, start_time, end_time}, ...]"""
content = srt_path.read_text(encoding="utf-8")
segments = []
pattern = r"(\d+)\n(\d{2}):(\d{2}):(\d{2}),(\d{3}) --> (\d{2}):(\d{2}):(\d{2}),(\d{3})\n(.*?)(?=\n\n|\Z)"
for m in re.findall(pattern, content, re.DOTALL):
sh, sm, ss = int(m[1]), int(m[2]), int(m[3])
eh, em, es = int(m[5]), int(m[6]), int(m[7])
start_sec = sh * 3600 + sm * 60 + ss
end_sec = eh * 3600 + em * 60 + es
text = m[9].strip().replace("\n", " ")
if len(text) > 2:
segments.append({
"start_sec": start_sec, "end_sec": end_sec,
"start_time": f"{sh:02d}:{sm:02d}:{ss:02d}",
"end_time": f"{eh:02d}:{em:02d}:{es:02d}",
"text": text,
})
return segments
def fallback_highlights_from_themes(transcript_path: Path, themes: list[dict], min_sec: int = 90, max_sec: int = 300) -> list[dict]:
"""
规则兜底:用主题关键词在转录稿中定位每段起止时间。
每个主题取标题/正文前 80 字中的关键词,在 segments 中找首次出现位置,再扩展为 min_secmax_sec 的完整段。
"""
segments = parse_srt_segments(transcript_path)
if not segments:
return []
total_sec = segments[-1]["end_sec"]
used_ranges: list[tuple[float, float]] = []
result = []
def _keywords(t: dict) -> list[str]:
raw = (t.get("title", "") + " " + t.get("text", ""))[:200]
# 去掉标点,取 2 字以上的词
words = re.findall(r"[\u4e00-\u9fff]{2,}", raw)
return list(dict.fromkeys(words))[:8]
for theme in themes:
title = theme.get("title", "片段")[:20]
kws = _keywords(theme)
if not kws:
result.append({"title": title, "start_time": "00:00:00", "end_time": "00:01:30", "hook_3sec": title, "cta_ending": DEFAULT_CTA, "transcript_excerpt": title, "reason": "无关键词"})
continue
# 找第一个包含任一关键词的 segment 索引
best_idx = None
for i, seg in enumerate(segments):
t = seg["text"]
if any(kw in t for kw in kws):
best_idx = i
break
if best_idx is None:
continue
seg = segments[best_idx]
start_sec = max(0, seg["start_sec"] - 15)
end_sec = min(total_sec, seg["end_sec"] + max_sec)
# 向后扩展至至少 min_sec最多 max_sec
end_sec = min(end_sec, start_sec + max_sec)
if end_sec - start_sec < min_sec:
end_sec = min(total_sec, start_sec + min_sec)
overlap = any(not (end_sec <= a or start_sec >= b) for a, b in used_ranges)
if overlap:
continue
used_ranges.append((start_sec, end_sec))
texts = [s["text"] for s in segments if s["end_sec"] >= start_sec and s["start_sec"] <= end_sec]
excerpt = (" ".join(texts)[:50] + "") if texts else title
result.append({
"title": title,
"start_time": _sec_to_hhmmss(start_sec),
"end_time": _sec_to_hhmmss(end_sec),
"hook_3sec": (title[:15] + "") if len(title) > 15 else title,
"cta_ending": DEFAULT_CTA,
"transcript_excerpt": excerpt,
"reason": "按章节主题关键词定位",
})
return result
def srt_to_timestamped_text(srt_path: Path) -> str:
"""SRT 转为带时间戳的纯文本,供模型阅读"""
content = srt_path.read_text(encoding="utf-8")
lines = []
pattern = r"(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n(.*?)(?=\n\n|\Z)"
for m in re.findall(pattern, content, re.DOTALL):
start = m[1].replace(",", ".")
text = m[3].strip().replace("\n", " ")
lines.append(f"[{start}] {text}")
return "\n".join(lines)
def _sec_to_hhmmss(sec: float) -> str:
s = int(sec)
h, m = s // 3600, (s % 3600) // 60
ss = s % 60
return f"{h:02d}:{m:02d}:{ss:02d}"
def _parse_time_to_sec(t: str) -> float:
"""解析 HH:MM:SS 或秒数为秒"""
t = str(t).strip().replace(",", ".")
parts = re.split(r"[:.]", t)
if len(parts) >= 3:
try:
return int(parts[0]) * 3600 + int(parts[1]) * 60 + float(parts[2])
except (ValueError, TypeError):
pass
try:
return float(t)
except ValueError:
return 0
def _filter_short_clips(data: list[dict], min_sec: float = 60) -> list[dict]:
"""过滤时长小于 min_sec 的片段"""
result = []
for item in data:
if not isinstance(item, dict):
continue
st = item.get("start_time") or item.get("start") or "00:00:00"
et = item.get("end_time") or item.get("end") or "00:01:00"
dur = _parse_time_to_sec(et) - _parse_time_to_sec(st)
if dur >= min_sec:
result.append(item)
else:
print(f" 过滤短片段: {item.get('title','?')} (仅{dur:.0f}秒)", file=sys.stderr)
return result
def call_ollama_chapter_themes(transcript_text: str, themes: list[dict], model: str, max_tokens: int = 8192) -> list[dict]:
"""
用 Ollama 根据「章节主题」在转录稿中定位每段起止时间。
返回 list[dict],每项含 start_time, end_time, title, hook_3sec, cta_ending, transcript_excerpt, reason。
"""
import requests
themes_desc = "\n".join([f"- {t['title']}: {t['text'][:200]}" for t in themes])
transcript_trim = transcript_text[:22000] if len(transcript_text) > 22000 else transcript_text
prompt = f"""你是短视频策划。下面是一篇「第9章单场文章」拆出的核心主题以及该场派对视频的带时间戳文字稿。
请为【每一个主题】在文字稿中找出最匹配的【一段连续内容】,给出精确的 start_time 和 end_time。
每段时长 60 秒5 分钟均可,以「主题完整、有头有尾」为准,不限于 5 分钟。
【章节主题】
{themes_desc}
【输出要求】
- 只输出一个 JSON 数组,不要 ``` 或其它说明
- 每个元素必须包含title, start_time, end_time, hook_3sec, cta_ending, transcript_excerpt, reason
- start_time / end_time 格式为 HH:MM:SS且必须来自下面文字稿中的时间戳
- title 用该主题的简短标题15 字内hook_3sec 用该段前 3 秒可用的金句15 字内)
- cta_ending 统一用:「{DEFAULT_CTA}
- transcript_excerpt该段内容 50 字内摘要
- reason为何这段对应该主题一句话
【视频文字稿】
---
{transcript_trim}
---"""
r = requests.post(
f"{OLLAMA_URL}/api/generate",
json={
"model": model,
"prompt": prompt,
"stream": False,
"options": {"temperature": 0.2, "num_predict": max_tokens},
},
timeout=180,
)
if r.status_code != 200:
raise RuntimeError(f"Ollama {r.status_code}")
raw = r.json().get("response", "").strip()
# 解析 JSON 数组
m = re.search(r"\[[\s\S]*\]", raw)
if not m:
raise ValueError("模型未返回合法 JSON 数组")
data = json.loads(m.group())
out = []
for item in data:
if not isinstance(item, dict):
continue
st = item.get("start_time") or item.get("start")
et = item.get("end_time") or item.get("end")
if isinstance(st, (int, float)):
item["start_time"] = _sec_to_hhmmss(st)
if isinstance(et, (int, float)):
item["end_time"] = _sec_to_hhmmss(et)
item.setdefault("cta_ending", DEFAULT_CTA)
item.setdefault("title", item.get("theme", "片段"))
out.append(item)
return out
def main():
parser = argparse.ArgumentParser(description="按章节正文提取主题片段 → highlights.json")
parser.add_argument("--chapter", "-c", required=True, help="章节 .md 路径第9章单场文章")
parser.add_argument("--transcript", "-t", required=True, help="transcript.srt 路径")
parser.add_argument("--output", "-o", required=True, help="highlights.json 输出路径")
parser.add_argument("--model", "-m", default="", help="Ollama 模型,默认优先 7b 再 1.5b")
args = parser.parse_args()
models_to_try = [args.model] if args.model else OLLAMA_MODELS
chapter_path = Path(args.chapter).resolve()
transcript_path = Path(args.transcript).resolve()
if not chapter_path.exists():
print(f"❌ 章节不存在: {chapter_path}", file=sys.stderr)
sys.exit(1)
if not transcript_path.exists():
print(f"❌ 转录稿不存在: {transcript_path}", file=sys.stderr)
sys.exit(1)
print("1. 从章节正文提取核心主题...")
themes = parse_chapter_themes(chapter_path)
print(f"{len(themes)} 个主题")
for i, t in enumerate(themes[:10], 1):
print(f" - {i}. {t['title']}")
if len(themes) > 10:
print(f" ... 等共 {len(themes)}")
print("2. 转录稿转带时间戳文本...")
transcript_text = srt_to_timestamped_text(transcript_path)
print("3. 调用本地模型匹配主题与时间...")
highlights = None
for model in models_to_try:
try:
print(f" 尝试 {model} ...")
highlights = call_ollama_chapter_themes(transcript_text, themes, model)
break
except Exception as e:
print(f" {model} 失败: {e}", file=sys.stderr)
if not highlights:
print(" 使用规则兜底:按主题关键词在转录稿中定位时间段...", file=sys.stderr)
highlights = fallback_highlights_from_themes(transcript_path, themes)
if not highlights:
print("❌ 无法生成 highlights模型失败且规则兜底无匹配", file=sys.stderr)
sys.exit(1)
highlights = _filter_short_clips(highlights)
out_path = Path(args.output).resolve()
out_path.parent.mkdir(parents=True, exist_ok=True)
with open(out_path, "w", encoding="utf-8") as f:
json.dump(highlights, f, ensure_ascii=False, indent=2)
print(f"✅ 已输出 {len(highlights)} 个主题片段: {out_path}")
print(" 后续batch_clip -i 视频 -l 本文件 -o clips/ -p soul112 → soul_enhance 带封面与字幕")
if __name__ == "__main__":
main()

View File

@@ -73,8 +73,8 @@ def fallback_highlights(transcript_path: str, clip_count: int) -> list:
return result
def srt_to_timestamped_text(srt_path: str) -> str:
"""将 SRT 转为带时间戳的纯文本"""
def srt_to_timestamped_text(srt_path: str, skip_repetitive_head: int = 150) -> str:
"""将 SRT 转为带时间戳的纯文本。跳过开头重复段落(如「我看你不太好」循环)"""
with open(srt_path, "r", encoding="utf-8") as f:
content = f.read()
lines = []
@@ -82,8 +82,36 @@ def srt_to_timestamped_text(srt_path: str) -> str:
for m in re.findall(pattern, content, re.DOTALL):
start = m[1].replace(",", ".")
text = m[3].strip().replace("\n", " ")
lines.append(f"[{start}] {text}")
return "\n".join(lines)
lines.append((start, text))
# 跳过开头重复段,直到出现连续 3 段不同且长度>=5 的内容
varied_start = 0
recent = []
for i, (s, t) in enumerate(lines):
if len(t) >= 5:
recent = (recent + [t])[-3:]
if len(recent) == 3 and len(set(recent)) >= 2:
varied_start = max(0, i - 2)
break
else:
recent = []
if varied_start > 0:
lines = lines[varied_start:]
# 过滤单字/短句重复段(如「你」循环),连续 5 次以上则整块跳过
out = []
prev, cnt = None, 0
skip_until = -1
for i, (s, t) in enumerate(lines):
if len(t) <= 2 and t == prev:
cnt += 1
if cnt >= 5 and skip_until < 0:
skip_until = i # 开始跳过
if skip_until >= 0:
continue
else:
prev, cnt = t, 1
skip_until = -1
out.append((s, t))
return "\n".join(f"[{s}] {t}" for s, t in out)
def _sec_to_hhmmss(sec: float) -> str:
@@ -127,44 +155,46 @@ def _filter_short_clips(data: list) -> list:
def _build_prompt(transcript: str, clip_count: int) -> str:
"""构建高光识别 prompt1-5分钟主题与内容必须一致全中文"""
txt = transcript[:25000] if len(transcript) > 25000 else transcript
return f"""你是资深短视频策划师。请从视频文字稿中识别尽可能多的**完整干货片段**,目标 {clip_count} 个以上
"""构建高光识别 prompt提问→回答:有提问时 question/hook_3sec 用提问问题"""
txt = transcript[:5000] if len(transcript) > 5000 else transcript
return f"""识别视频文字稿中的 {clip_count} 个高光片段,直接输出 JSON 数组,第一个字符必须是 [
【时长要求】每个片段 **60-300 秒1-5 分钟)**,少于 60 秒的不要输出
【主题一致】title、hook_3sec、transcript_excerpt 必须**完全对应**该时间段内的实际内容,禁止泛泛而谈。
- title从该段时间内的核心观点提炼15字内
- hook_3sec从该段第一句或核心金句提炼15字内
- transcript_excerpt该段内容的中文摘要50字内
重要:若某片段里有人提问(观众/连麦者问的问题),必须提取提问内容填 question且 hook_3sec 用该提问。成片前3秒先展示提问再播回答
【切片原则】
- 每段必须是完整话题,有头有尾
- 优先:金句、故事、方法论、反常识、情绪高点
- 相邻片段间隔至少 60 秒
- 从文字稿时间戳精确提取 start_time、end_time
示例(有提问):
[{{"title":"普通人怎么敢跟ZF搞","start_time":"01:12:30","end_time":"01:15:30","question":"普通人怎么敢跟ZF搞","hook_3sec":"普通人怎么敢跟ZF搞","cta_ending":"{DEFAULT_CTA}","transcript_excerpt":"维权起头跑通就成生意","reason":"提问+回答完整"}}]
示例(无提问):
[{{"title":"起头难","start_time":"00:05:55","end_time":"00:08:00","hook_3sec":"没人起头就起头","cta_ending":"{DEFAULT_CTA}","transcript_excerpt":"起头难跑通就能变成付费服务","reason":"核心观点"}}]
【输出字段】全部**简体中文**,英文原文需翻译
- title、start_time、end_time、hook_3sec、cta_ending"{DEFAULT_CTA}"、transcript_excerpt、reason
只输出 JSON 数组,不要 ``` 或其他文字。
视频文字稿:
---
文字稿(从时间戳提取 start_time、end_time每段 60-300 秒)
{txt}
---"""
直接输出 JSON 数组,以 [ 开头。有提问的片段必须带 question 且 hook_3sec 与 question 一致。"""
def _parse_ai_json(text: str) -> list:
"""从 AI 输出中提取 JSON 数组"""
if not text or not text.strip():
raise ValueError("AI 返回为空")
text = text.strip()
if text.startswith("```"):
text = re.sub(r"^```(?:json)?\s*", "", text)
text = re.sub(r"\s*```\s*$", "", text)
# 尝试找到 [...]
# 优先匹配完整 [...],若模型只返回单个对象则包成数组
m = re.search(r"\[[\s\S]*\]", text)
if m:
return json.loads(m.group())
return json.loads(text)
try:
return json.loads(m.group())
except json.JSONDecodeError:
pass
# 尝试解析为单个对象后包成数组
m = re.search(r"\{[\s\S]*\}", text)
if m:
try:
return [json.loads(m.group())]
except json.JSONDecodeError:
pass
raise ValueError("未找到合法 JSON 数组或对象")
def _is_mostly_chinese(text: str) -> bool:
@@ -205,7 +235,7 @@ def _ensure_chinese_highlights(data: list) -> list:
for i, item in enumerate(data):
if not isinstance(item, dict):
continue
for key in ["title", "hook_3sec", "transcript_excerpt"]:
for key in ["title", "hook_3sec", "question", "transcript_excerpt"]:
val = item.get(key)
if val and not _is_mostly_chinese(str(val)):
translated = _translate_to_chinese(str(val))
@@ -217,24 +247,41 @@ def _ensure_chinese_highlights(data: list) -> list:
return data
def call_ollama(transcript: str, clip_count: int = CLIP_COUNT) -> str:
"""调用卡若AI本地模型Ollama"""
OLLAMA_MODELS = ["qwen2.5:3b", "qwen2.5:1.5b"] # 优先 3b能力更强
def call_ollama(transcript: str, clip_count: int = CLIP_COUNT, model: str = "qwen2.5:3b") -> str:
"""调用卡若AI本地模型Ollama使用 chat 接口避免对话式误判"""
import requests
prompt = _build_prompt(transcript, clip_count)
system = (
"你是短视频策划师。用户会提供视频文字稿,你只输出一个 JSON 数组。"
"若某片段内有人提问(观众/连麦者问的问题),必须提取提问原文填 question且 hook_3sec 用该提问前3秒先展示提问再回答无提问则 hook_3sec 用金句/悬念。"
"格式含 title, start_time, end_time, hook_3sec, cta_ending, transcript_excerpt, reason有提问时加 question。"
"禁止输出任何非 JSON 内容。"
)
try:
r = requests.post(
f"{OLLAMA_URL}/api/generate",
f"{OLLAMA_URL}/api/chat",
json={
"model": "qwen2.5:1.5b",
"prompt": prompt,
"model": model,
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": prompt},
],
"stream": False,
"options": {"temperature": 0.3, "num_predict": 4096},
"options": {"temperature": 0.2, "num_predict": 8192},
},
timeout=90,
timeout=300,
)
if r.status_code != 200:
raise RuntimeError(f"Ollama {r.status_code}")
return r.json().get("response", "").strip()
body = r.json()
msg = body.get("message", {})
resp = (msg.get("content") or "").strip()
if not resp:
raise RuntimeError("Ollama 返回空响应")
return resp
except Exception as e:
raise RuntimeError(f"Ollama 调用失败: {e}") from e
@@ -244,6 +291,7 @@ def main():
parser.add_argument("--transcript", "-t", required=True, help="transcript.srt 路径")
parser.add_argument("--output", "-o", required=True, help="highlights.json 输出路径")
parser.add_argument("--clips", "-n", type=int, default=CLIP_COUNT, help="切片数量")
parser.add_argument("--require-ai", action="store_true", help="必须用 AI 识别,失败则退出不兜底")
args = parser.parse_args()
transcript_path = Path(args.transcript)
if not transcript_path.exists():
@@ -253,20 +301,27 @@ def main():
if len(text) < 100:
print("❌ 文字稿过短,请检查 SRT 格式", file=sys.stderr)
sys.exit(1)
# 级联Ollama(卡若AI本地) → 规则备用
# 级联Ollama 3b → 1.5b → 规则备用(--require-ai 时不用规则)
data = None
for name, fn in [
("Ollama (卡若AI本地)", call_ollama),
]:
raw = ""
for model in OLLAMA_MODELS:
try:
print(f"正在调用 {name} 分析高光片段...")
raw = fn(text, args.clips)
print(f"正在调用 Ollama {model} 分析高光片段...")
raw = call_ollama(text, args.clips, model)
if not raw:
raise ValueError("模型返回空")
data = _parse_ai_json(raw)
if data and isinstance(data, list) and len(data) > 0:
print(f"{model} 成功,识别 {len(data)}")
break
except Exception as e:
print(f"{name} 调用失败 ({e})", file=sys.stderr)
print(f" {model} 失败: {e}", file=sys.stderr)
if raw:
print(f" 返回预览: {str(raw)[:400]}...", file=sys.stderr)
if not data or not isinstance(data, list):
if getattr(args, "require_ai", False):
print("❌ 必须用 AI 识别,当前无可用模型或解析失败", file=sys.stderr)
sys.exit(1)
print("使用规则备用切分", file=sys.stderr)
data = fallback_highlights(str(transcript_path), args.clips)
if not data:

View File

@@ -10,12 +10,13 @@ Soul切片增强脚本 v2.0
"""
import argparse
import subprocess
import json
import os
import re
import json
import tempfile
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont, ImageFilter
@@ -39,6 +40,12 @@ SPEED_FACTOR = 1.10 # 加速10%
SILENCE_THRESHOLD = -40 # 静音阈值(dB)
SILENCE_MIN_DURATION = 0.5 # 最短静音时长(秒)
# Soul 竖屏裁剪(与 soul_vertical_crop 一致,成片直出用)
CROP_VF = "crop=608:1080:483:0,crop=498:1080:60:0"
# 竖屏成片时封面/字幕用此尺寸,叠在横版上的 x 位置(与 crop 后保留区域对齐)
VERTICAL_W, VERTICAL_H = 498, 1080
OVERLAY_X = 543 # 1920 下保留区域左缘483+60
# 繁转简OpenCC 优先,否则用映射)
_OPENCC = None
def _get_opencc():
@@ -104,6 +111,14 @@ COVER_FONT_PRIORITY = [
"/System/Library/Fonts/Supplemental/Songti.ttc",
]
# Soul 品牌绿(绿点/绿色社交)
SOUL_GREEN = (0, 210, 106) # #00D26A
SOUL_GREEN_DARK = (0, 160, 80)
# 竖屏封面高级背景:深色渐变(不超出界面)
VERTICAL_COVER_TOP = (12, 32, 24) # 深墨绿
VERTICAL_COVER_BOTTOM = (8, 48, 36) # 略亮绿
VERTICAL_COVER_PADDING = 44 # 左右留白,保证文字不贴边、不超出
# 样式配置
STYLE = {
'cover': {
@@ -159,6 +174,19 @@ def draw_text_with_outline(draw, pos, text, font, color, outline_color, outline_
# 主体
draw.text((x, y), text, font=font, fill=color)
def sanitize_filename(name: str, max_length: int = 50) -> str:
"""成片文件名仅保留中文、空格、_-,与 batch_clip 一致"""
name = _to_simplified(str(name))
safe = []
for c in name:
if c in " _-" or "\u4e00" <= c <= "\u9fff":
safe.append(c)
result = "".join(safe).strip()
if len(result) > max_length:
result = result[:max_length]
return result.strip(" _-") or "片段"
def clean_filler_words(text):
"""清理语助词 + 去除多余空格"""
result = text
@@ -322,83 +350,142 @@ def get_cover_font(size):
return ImageFont.load_default()
def _draw_vertical_gradient(draw, width, height, top_rgb, bottom_rgb):
"""绘制竖屏封面用深色渐变背景,高级感"""
for y in range(height):
t = y / max(height - 1, 1)
r = int(top_rgb[0] + (bottom_rgb[0] - top_rgb[0]) * t)
g = int(top_rgb[1] + (bottom_rgb[1] - top_rgb[1]) * t)
b = int(top_rgb[2] + (bottom_rgb[2] - top_rgb[2]) * t)
draw.rectangle([0, y, width, y + 1], fill=(r, g, b))
def create_cover_image(hook_text, width, height, output_path, video_path=None):
"""创建封面贴片(简体中文,字体优化)"""
"""创建封面贴片。竖屏 498x1080 时:高级渐变背景、文字严格在界面内居中不超出、左上角 Soul logo。"""
hook_text = _to_simplified(str(hook_text or "").strip())
if not hook_text:
hook_text = "精彩切片"
style = STYLE['cover']
hook_style = STYLE['hook']
is_vertical = (width, height) == (VERTICAL_W, VERTICAL_H)
# 从视频提取背景帧
if video_path and os.path.exists(video_path):
temp_frame = output_path.replace('.png', '_frame.jpg')
subprocess.run([
'ffmpeg', '-y', '-ss', '1', '-i', video_path,
'-vframes', '1', '-q:v', '2', temp_frame
], capture_output=True)
if os.path.exists(temp_frame):
bg = Image.open(temp_frame).resize((width, height))
bg = bg.filter(ImageFilter.GaussianBlur(radius=style['bg_blur']))
os.remove(temp_frame)
else:
bg = Image.new('RGB', (width, height), (30, 30, 50))
if is_vertical:
# 竖屏成片:高级深色渐变背景,不依赖视频帧,保证不超出界面
img = Image.new('RGB', (width, height), VERTICAL_COVER_TOP)
draw = ImageDraw.Draw(img)
_draw_vertical_gradient(draw, width, height, VERTICAL_COVER_TOP, VERTICAL_COVER_BOTTOM)
# 轻微半透明暗角,让文字更突出
overlay = Image.new('RGBA', (width, height), (0, 0, 0, 100))
img = img.convert('RGBA')
img = Image.alpha_composite(img, overlay)
draw = ImageDraw.Draw(img)
else:
bg = Image.new('RGB', (width, height), (30, 30, 50))
# 叠加暗层
overlay = Image.new('RGBA', (width, height), (0, 0, 0, style['overlay_alpha']))
bg = bg.convert('RGBA')
img = Image.alpha_composite(bg, overlay)
draw = ImageDraw.Draw(img)
# 顶部装饰线
for i in range(3):
alpha = 150 - i * 40
draw.rectangle([0, i*3, width, i*3+2], fill=(255, 215, 0, alpha))
# 底部装饰线
for i in range(3):
alpha = 150 - i * 40
draw.rectangle([0, height - i*3 - 2, width, height - i*3], fill=(255, 215, 0, alpha))
# Hook 文字(封面用更好看的字体)
font = get_cover_font(hook_style['font_size'])
# 计算换行
max_width = width - 80
lines = []
current_line = ""
for char in hook_text:
test_line = current_line + char
test_w, _ = get_text_size(draw, test_line, font)
if test_w <= max_width:
current_line = test_line
# 横版:沿用视频帧模糊背景
if video_path and os.path.exists(video_path):
temp_frame = output_path.replace('.png', '_frame.jpg')
subprocess.run([
'ffmpeg', '-y', '-ss', '1', '-i', video_path,
'-vframes', '1', '-q:v', '2', temp_frame
], capture_output=True)
if os.path.exists(temp_frame):
bg = Image.open(temp_frame).resize((width, height))
bg = bg.filter(ImageFilter.GaussianBlur(radius=style['bg_blur']))
os.remove(temp_frame)
else:
bg = Image.new('RGB', (width, height), (25, 35, 30))
else:
bg = Image.new('RGB', (width, height), (25, 35, 30))
overlay = Image.new('RGBA', (width, height), (0, 25, 15, style['overlay_alpha']))
img = bg.convert('RGBA')
img = Image.alpha_composite(img, overlay)
draw = ImageDraw.Draw(img)
# Soul 绿装饰线(顶部、底部)
for i in range(3):
alpha = 180 - i * 50
draw.rectangle([0, i * 3, width, i * 3 + 2], fill=(*SOUL_GREEN, alpha))
for i in range(3):
alpha = 180 - i * 50
draw.rectangle([0, height - i * 3 - 2, width, height - i * 3], fill=(*SOUL_GREEN, alpha))
# 左上角 Soul logo 小图标(绿圆 + 白字 S保证在界面内
logo_x, logo_y = 28, 28
logo_r = 20
draw.ellipse([logo_x - logo_r, logo_y - logo_r, logo_x + logo_r, logo_y + logo_r], fill=SOUL_GREEN, outline=(255, 255, 255))
try:
logo_font = get_cover_font(26)
draw.text((logo_x - 5, logo_y - 12), "S", font=logo_font, fill=(255, 255, 255))
except Exception:
pass
# 标题文字:竖屏时严格限制在 padding 内,多行居中,绝不超出界面
if is_vertical:
max_text_width = width - 2 * VERTICAL_COVER_PADDING # 498 - 88 = 410
cover_font_size = 48
font = get_cover_font(cover_font_size)
lines = []
for _ in range(20):
current_line = ""
lines = [] # 本轮换行结果
for char in hook_text:
test_line = current_line + char
test_w, _ = get_text_size(draw, test_line, font)
if test_w <= max_text_width:
current_line = test_line
else:
if current_line:
lines.append(current_line)
current_line = char
if current_line:
lines.append(current_line)
current_line = char
if current_line:
lines.append(current_line)
# 绘制文字(完全居中)
line_height = hook_style['font_size'] + 15
total_height = len(lines) * line_height
start_y = (height - total_height) // 2
for i, line in enumerate(lines):
line_w, line_h = get_text_size(draw, line, font)
x = (width - line_w) // 2
y = start_y + i * line_height
draw_text_with_outline(
draw, (x, y), line, font,
hook_style['color'],
hook_style['outline_color'],
hook_style['outline_width']
)
if cover_font_size <= 28 or len(lines) <= 6:
break
cover_font_size -= 2
font = get_cover_font(cover_font_size)
line_height = cover_font_size + 14
total_height = len(lines) * line_height
start_y = (height - total_height) // 2
for i, line in enumerate(lines):
line_w, line_h = get_text_size(draw, line, font)
x = (width - line_w) // 2
x = max(VERTICAL_COVER_PADDING, min(width - VERTICAL_COVER_PADDING - line_w, x))
y = start_y + i * line_height
draw_text_with_outline(
draw, (x, y), line, font,
hook_style['color'],
hook_style['outline_color'],
min(hook_style['outline_width'], 3)
)
else:
cover_font_size = hook_style['font_size']
font = get_cover_font(cover_font_size)
max_width = width - 80
lines = []
current_line = ""
for char in hook_text:
test_line = current_line + char
test_w, _ = get_text_size(draw, test_line, font)
if test_w <= max_width:
current_line = test_line
else:
if current_line:
lines.append(current_line)
current_line = char
if current_line:
lines.append(current_line)
line_height = cover_font_size + 12
total_height = len(lines) * line_height
start_y = (height - total_height) // 2
for i, line in enumerate(lines):
line_w, line_h = get_text_size(draw, line, font)
x = (width - line_w) // 2
y = start_y + i * line_height
draw_text_with_outline(
draw, (x, y), line, font,
hook_style['color'],
hook_style['outline_color'],
hook_style['outline_width']
)
img.save(output_path, 'PNG')
return output_path
@@ -406,29 +493,39 @@ def create_cover_image(hook_text, width, height, output_path, video_path=None):
# ============ 字幕图片生成 ============
def create_subtitle_image(text, width, height, output_path):
"""创建字幕图片(关键词加粗加大突出)"""
"""创建字幕图片(关键词加粗加大突出)。竖屏 498 宽时字号略小、保证居中且不溢出。"""
style = STYLE['subtitle']
img = Image.new('RGBA', (width, height), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# 竖屏窄幅时缩小字号,保证整行在画面内且居中
base_size = style['font_size']
kw_size = base_size + style.get('keyword_size_add', 4)
if (width, height) == (VERTICAL_W, VERTICAL_H):
base_size = min(base_size, 38)
font = get_font(FONT_BOLD, base_size)
kw_font = get_font(FONT_HEAVY, kw_size) # 关键词用粗体+大字
text_w, text_h = get_text_size(draw, text, font)
while text_w > width - 80 and base_size > 24:
base_size -= 2
font = get_font(FONT_BOLD, base_size)
text_w, text_h = get_text_size(draw, text, font)
kw_size = base_size + style.get('keyword_size_add', 4)
kw_font = get_font(FONT_HEAVY, kw_size)
# 字幕完全居中
# 字幕完全居中(水平+垂直正中间);竖屏时限制在界面内不超出
base_x = (width - text_w) // 2
base_y = height - text_h - style['margin_bottom']
if (width, height) == (VERTICAL_W, VERTICAL_H):
pad = 24
base_x = max(pad, min(width - pad - text_w, base_x))
base_y = (height - text_h) // 2
# 背景条
# 背景条(不超出画布)
padding = 15
bg_rect = [
base_x - padding - 10,
base_y - padding,
base_x + text_w + padding + 10,
base_y + text_h + padding
max(0, base_x - padding - 10),
max(0, base_y - padding),
min(width, base_x + text_w + padding + 10),
min(height, base_y + text_h + padding)
]
# 绘制圆角背景
@@ -562,8 +659,8 @@ def _parse_clip_index(filename: str) -> int:
def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_path,
force_burn_subs=False, skip_subs=False):
"""增强单个切片。检测原片是否已有字幕,有则跳过烧录,无则烧录中文"""
force_burn_subs=False, skip_subs=False, vertical=False):
"""增强单个切片。vertical=True 时最后裁成竖屏 498x1080 直出成片。"""
print(f"\n处理: {os.path.basename(clip_path)}")
@@ -573,16 +670,21 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
print(f" 分辨率: {width}x{height}, 时长: {duration:.1f}")
hook_text = highlight_info.get('hook_3sec') or highlight_info.get('title') or ''
# 前3秒优先用「提问问题」有 question 则封面/前贴先展示提问,再播回答
hook_text = highlight_info.get('question') or highlight_info.get('hook_3sec') or highlight_info.get('title') or ''
if not hook_text and clip_path:
m = re.search(r'\d+[_\s]+(.+?)(?:_enhanced)?\.mp4$', os.path.basename(clip_path))
if m:
hook_text = m.group(1).strip()
cover_duration = STYLE['cover']['duration']
# 竖屏成片:封面/字幕按 498x1080 做,叠在裁切区域,文字与字幕在竖屏上完整且居中
out_w, out_h = (VERTICAL_W, VERTICAL_H) if vertical else (width, height)
overlay_pos = f"{OVERLAY_X}:0" if vertical else "0:0"
# 1. 生成封面
cover_img = os.path.join(temp_dir, 'cover.png')
create_cover_image(hook_text, width, height, cover_img, clip_path)
create_cover_image(hook_text, out_w, out_h, cover_img, clip_path)
print(f" ✓ 封面生成")
# 2. 字幕逻辑:有字幕/图片则跳过,无则烧录中文
@@ -610,7 +712,7 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
print(f" ✓ 字幕解析 ({len(subtitles)}条),已转中文")
for i, sub in enumerate(subtitles[:50]):
img_path = os.path.join(temp_dir, f'sub_{i:04d}.png')
create_subtitle_image(sub['text'], width, height, img_path)
create_subtitle_image(sub['text'], out_w, out_h, img_path)
sub_images.append({'path': img_path, 'start': sub['start'], 'end': sub['end']})
if sub_images:
print(f" ✓ 字幕图片 ({len(sub_images)}张)")
@@ -622,12 +724,12 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
# 5. 构建FFmpeg命令
current_video = clip_path
# 5.1 添加封面
# 5.1 添加封面(竖屏时叠在 x=543与最终裁切区域对齐
cover_output = os.path.join(temp_dir, 'with_cover.mp4')
cmd = [
'ffmpeg', '-y',
'-i', current_video, '-i', cover_img,
'-filter_complex', f"[0:v][1:v]overlay=0:0:enable='lt(t,{cover_duration})'[v]",
'-filter_complex', f"[0:v][1:v]overlay={overlay_pos}:enable='lt(t,{cover_duration})'[v]",
'-map', '[v]', '-map', '0:a',
'-c:v', 'libx264', '-preset', 'fast', '-crf', '22',
'-c:a', 'copy', cover_output
@@ -638,7 +740,7 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
current_video = cover_output
print(f" ✓ 封面烧录")
# 5.2 分批烧录字幕
# 5.2 分批烧录字幕(封面结束后才显示,不盖住封面)
if sub_images:
batch_size = 8
for batch_idx in range(0, len(sub_images), batch_size):
@@ -650,12 +752,16 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
filters = []
last_output = '0:v'
for i, img in enumerate(batch):
input_idx = i + 1
output_name = f'v{i}'
enable = f"between(t,{img['start']:.3f},{img['end']:.3f})"
filters.append(f"[{last_output}][{input_idx}:v]overlay=0:0:enable='{enable}'[{output_name}]")
# 封面结束后才显示字幕,不盖住封面
sub_start = max(img['start'], cover_duration)
if sub_start < img['end']:
enable = f"between(t,{sub_start:.3f},{img['end']:.3f})"
filters.append(f"[{last_output}][{input_idx}:v]overlay={overlay_pos}:enable='{enable}'[{output_name}]")
else:
filters.append(f"[{last_output}]copy[{output_name}]")
last_output = output_name
filter_complex = ';'.join(filters)
@@ -693,8 +799,17 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
current_video = speed_output
print(f" ✓ 加速10%")
# 5.4 复制到输出
shutil.copy(current_video, output_path)
# 5.4 输出:竖屏则裁成 498x1080 直出,否则直接复制
if vertical:
r = subprocess.run([
'ffmpeg', '-y', '-i', current_video,
'-vf', CROP_VF, '-c:a', 'copy', output_path
], capture_output=True, text=True)
if r.returncode != 0:
print(f" ❌ 竖屏裁剪失败: {r.stderr[:200]}", file=sys.stderr)
shutil.copy(current_video, output_path)
else:
shutil.copy(current_video, output_path)
if os.path.exists(output_path):
size_mb = os.path.getsize(output_path) / (1024 * 1024)
@@ -709,7 +824,9 @@ def main():
parser.add_argument("--clips", "-c", help="切片目录")
parser.add_argument("--highlights", "-l", help="highlights.json 路径")
parser.add_argument("--transcript", "-t", help="transcript.srt 路径")
parser.add_argument("--output", "-o", help="输出目录")
parser.add_argument("--output", "-o", help="输出目录(成片时填 成片 文件夹路径)")
parser.add_argument("--vertical", action="store_true", help="成片直出竖屏 498x1080与封面+字幕一起输出到 -o 目录")
parser.add_argument("--title-only", action="store_true", help="输出文件名为纯标题无序号、无_enhanced与 --vertical 搭配用于成片")
parser.add_argument("--skip-subs", action="store_true", help="跳过字幕烧录(原片已有字幕时用)")
parser.add_argument("--force-burn-subs", action="store_true", help="强制烧录字幕(忽略检测)")
args = parser.parse_args()
@@ -729,12 +846,14 @@ def main():
print(f"❌ transcript 不存在: {transcript_path}")
return
vertical = getattr(args, 'vertical', False)
title_only = getattr(args, 'title_only', False)
print("="*60)
print("🎬 Soul切片增强处理Pillow无需 drawtext")
print("🎬 Soul切片增强" + ("(成片竖屏直出)" if vertical else ""))
print("="*60)
print(f"功能: 封面+字幕+加速10%+去语气词")
print(f"功能: 封面+字幕+加速10%+去语气词" + ("+竖屏498x1080" if vertical else ""))
print(f"输入: {clips_dir}")
print(f"输出: {output_dir}")
print(f"输出: {output_dir}" + ("(成片,文件名=标题)" if title_only else ""))
print("="*60)
output_dir.mkdir(parents=True, exist_ok=True)
@@ -754,13 +873,19 @@ def main():
clip_num = _parse_clip_index(clip_path.name) or (i + 1)
highlight_info = highlights[clip_num - 1] if 0 < clip_num <= len(highlights) else {}
output_path = output_dir / clip_path.name.replace('.mp4', '_enhanced.mp4')
if getattr(args, 'title_only', False):
title = (highlight_info.get('title') or highlight_info.get('hook_3sec') or clip_path.stem)
name = sanitize_filename(title) + '.mp4'
output_path = output_dir / name
else:
output_path = output_dir / clip_path.name.replace('.mp4', '_enhanced.mp4')
temp_dir = tempfile.mkdtemp(prefix='enhance_')
try:
if enhance_clip(str(clip_path), str(output_path), highlight_info, temp_dir, str(transcript_path),
force_burn_subs=getattr(args, 'force_burn_subs', False),
skip_subs=getattr(args, 'skip_subs', False)):
skip_subs=getattr(args, 'skip_subs', False),
vertical=getattr(args, 'vertical', False)):
success_count += 1
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
@@ -773,12 +898,12 @@ def main():
generate_index(highlights, output_dir)
def generate_index(highlights, output_dir):
"""生成目录索引(标题/Hook/CTA 统一简体中文)"""
index_path = output_dir.parent / "目录索引_enhanced.md"
"""生成目录索引(标题/Hook/CTA 统一简体中文),索引写在输出目录内"""
index_path = output_dir / "目录索引.md"
with open(index_path, 'w', encoding='utf-8') as f:
f.write("# Soul派对 - 增强版切片目录\n\n")
f.write(f"**优化**: 封面+字幕+加速10%+去语气词\n\n")
f.write("# Soul派对 - 片目录\n\n")
f.write("**优化**: 封面+字幕+加速10%+去语气词成片含竖屏时已裁为498×1080\n\n")
f.write("## 切片列表\n\n")
f.write("| 序号 | 标题 | Hook | CTA |\n")
f.write("|------|------|------|-----|\n")

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Soul 竖屏中段裁剪(批量)
横版 1920×1080 → 竖屏 498×1080去左右白边。
参数与 SKILL「Soul 竖屏成片」一致,以后剪辑 Soul 视频统一用此脚本。
"""
import argparse
import re
import subprocess
import sys
from pathlib import Path
# 固定参数(与 SKILL 一致)
CROP_VF = "crop=608:1080:483:0,crop=498:1080:60:0"
OUT_SUFFIX = "_竖屏中段"
def main():
parser = argparse.ArgumentParser(description="Soul 竖屏中段批量裁剪")
parser.add_argument("--dir", "-d", required=True, help="切片目录clips 或 clips_enhanced")
parser.add_argument("--suffix", "-s", default=OUT_SUFFIX, help="输出文件名后缀")
parser.add_argument("--pattern", "-p", default="*_enhanced.mp4", help="匹配文件,如 *.mp4 表示所有 mp4")
parser.add_argument("--out-dir", "-o", default="", help="输出目录(默认同 --dir")
parser.add_argument("--title-only", action="store_true", help="输出文件名仅用标题(去掉序号、竖屏中段等)")
parser.add_argument("--dry-run", action="store_true", help="只列文件不执行")
args = parser.parse_args()
base = Path(args.dir).resolve()
if not base.is_dir():
print(f"❌ 目录不存在: {base}", file=sys.stderr)
sys.exit(1)
out_dir = Path(args.out_dir).resolve() if args.out_dir else base
out_dir.mkdir(parents=True, exist_ok=True)
files = sorted(base.glob(args.pattern))
files = [f for f in files if OUT_SUFFIX not in f.stem and "_竖屏中段" not in f.stem]
if not files:
print(f" 未找到 {args.pattern}(已排除竖屏中段): {base}")
return
print(f"📁 {base}{out_dir}")
print(f"{len(files)} 个将做竖屏中段裁剪")
if args.dry_run:
for f in files:
print(f" - {f.name}")
return
for f in files:
if getattr(args, "title_only", False):
# 仅标题:去掉 soul112_01_、_enhanced、_竖屏中段 等,只保留标题
stem = re.sub(r"^soul\d+_\d+_", "", f.stem)
stem = re.sub(r"_enhanced$", "", stem)
stem = re.sub(r"_竖屏中段$", "", stem)
out_name = stem + f.suffix
else:
out_name = f.stem + args.suffix + f.suffix
out_path = out_dir / out_name
cmd = [
"ffmpeg", "-y", "-i", str(f),
"-vf", CROP_VF,
"-c:a", "copy",
str(out_path),
]
print(f" {f.name}{out_name}")
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0:
print(f" ❌ 失败: {r.stderr[:200]}", file=sys.stderr)
else:
print(f"{out_path.name}")
print("Done.")
if __name__ == "__main__":
main()

View File

@@ -77,6 +77,7 @@
| W01 | 文件整理 | 水溪 | 整理文件、外置硬盘 | `02_卡人/水溪_整理归档/文件整理/SKILL.md` | 文件分类、去重、归档 |
| W02 | 文档清洗 | 水溪 | PDF转Markdown | `02_卡人/水溪_整理归档/文档清洗/SKILL.md` | PDF/Word 转结构化 Markdown |
| W03 | 对话归档 | 水溪 | 归档今日对话 | `02_卡人/水溪_整理归档/对话归档/SKILL.md` | AI 对话记录收集与归类 |
| W03a | **项目调研** | 水溪 | **项目调研、平台分析、A群、A群聊天记录、聊天记录清理、对话分类、对话分类号、按项目归档、调研归档、APP资料、其他APP、各APP** | `02_卡人/水溪_整理归档/项目调研/SKILL.md` | 平台分析/项目调研/各APP资料/群聊/对话分类统一归档到 开发/7.项目调研,按项目分子目录 |
| W04 | 自动记忆管理 | 水溪 | 记忆、存入记忆 | `02_卡人/水溪_整理归档/自动记忆管理/SKILL.md` | 长期记忆写入与检索 |
| W05 | 需求拆解与计划制定 | 水泉 | 需求拆解、任务分析 | `02_卡人/水泉_规划拆解/需求拆解与计划制定/SKILL.md` | 大需求拆成可执行步骤 |
| W06 | 任务规划 | 水泉 | 任务规划、制定计划 | `02_卡人/水泉_规划拆解/任务规划/SKILL.md` | 制定执行计划与排期 |
@@ -87,6 +88,7 @@
| W11 | Soul派对运营报表 | 水桥 | **运营报表、派对填表、派对截图填表发群、派对纪要、智能纪要、106场、107场、本月运营数据** | `02_卡人/水桥_平台对接/飞书管理/运营报表_SKILL.md` | 派对截图+TXT→飞书运营报表→智能纪要→飞书群推送含Token自刷新与写入校验 |
| W12 | MCP 搜索与连接 | 水桥 | **MCP、找MCP、连接MCP、MCP搜索、发现MCP、添加MCP、需要MCP、MCP安装、MCP发现、查MCP、装MCP** | `02_卡人/水桥_平台对接/MCP管理/SKILL.md` | 搜索 5000+ MCP 服务器→生成安装配置→写入 Cursor/Claude 等 |
| W13 | Excel表格与日报 | 水桥 | **Excel写飞书、Excel导入飞书、批量写飞书表格、飞书表格导入、CSV写飞书、日报图表发飞书、表格日报** | `02_卡人/水桥_平台对接/飞书管理/Excel表格与日报_SKILL.md` | 本地 Excel/CSV→飞书表格→自动日报图表→发飞书群 |
| W14 | **卡猫复盘** | 水桥 | **卡猫复盘、婼瑄复盘、卡猫今日复盘、婼瑄今日、复盘到卡猫、发卡猫群** | `02_卡人/水桥_平台对接/飞书管理/卡猫复盘/SKILL.md` | 婼瑄目录→目标=今年总目标+完成%+人/事/数具体→飞书+卡猫群 |
## 木组 · 卡木(产品内容创造)
@@ -94,6 +96,7 @@
|:--|:---|:---|:---|:---|:---|
| M01 | 视频切片 | 木叶 | **视频剪辑、切片发布、切片动效包装、程序化包装、片头片尾、批量封面、视频包装** | `03_卡木/木叶_视频内容/视频切片/SKILL.md` | 长视频切片+字幕+发布;联动切片动效包装(片头/片尾/程序化) |
| M01b | 抖音视频解析 | 木叶 | **抖音视频、抖音链接、抖音解析、抖音下载、提取抖音文案、抖音无水印** | `03_卡木/木叶_视频内容/抖音视频解析/SKILL.md` | 链接→解析ID→提取文案→下载无水印视频 |
| M01c | 抖音发布 | 木叶 | **抖音发布、发布到抖音、抖音登录、抖音上传、腕推抖音** | `03_卡木/木叶_视频内容/抖音发布/SKILL.md` | 开放平台 OAuth 登录 + 上传/创建视频发布;可对接腕推/存客宝 |
| M02 | 网站逆向分析 | 木根 | 逆向分析、模拟登录 | `03_卡木/木根_逆向分析/网站逆向分析/SKILL.md` | 网站 API 分析、SDK 生成 |
| M03 | 项目生成 | 木果 | 生成项目、五行模板 | `03_卡木/木果_项目模板/项目生成/SKILL.md` | 按五行模板生成新项目 |
| M04 | 开发模板 | 木果 | 创建项目、初始化模板 | `03_卡木/木果_项目模板/开发模板/SKILL.md` | 前后端项目模板库 |
@@ -155,8 +158,8 @@
| 组 | 负责人 | 成员数 | 技能数 |
|:--|:---|:--|:--|
| 金 | 卡资 | 2 | 21 |
| 水 | 卡人 | 3 | 12 |
| 水 | 卡人 | 3 | 13 |
| 木 | 卡木 | 3 | 8 |
| 火 | 卡火 | 4 | 15 |
| 土 | 卡土 | 4 | 7 |
| **合计** | **5** | **14** | **63** |
| **合计** | **5** | **14** | **64** |

View File

@@ -0,0 +1,81 @@
# giffgaff 发短信 / 收短信 · 流程史记
> **用途**:一页查清 giffgaff 英国卡「发短信、收短信、保号」全流程搜索「发短信」「收短信」「giffgaff」即可定位本文。
> 更新2026-03-01
---
## 一、总览
| 动作 | 入口 | 说明 |
|:---|:---|:---|
| **发短信** | 手机自带「信息」App | 官网/My giffgaff **无网页发短信**,只能用手机发 |
| **收短信** | 手机「信息」App | 验证码、通知等均在手机端查看 |
| **登录/查余额** | giffgaff.com → Log in / My giffgaff | 网页可查余额、套餐、订单,不能看/发短信 |
---
## 二、激活与登录(前置)
1. **插卡**:收到 giffgaff SIM 后插入手机,约 **30 分钟内**会收到欢迎短信。
2. **登录官网**:打开 [giffgaff.com](https://www.giffgaff.com) → 右上角 **Log in** → 用手机号 + 收短信验证码登录。
3. **建议**:登录后改好邮箱、密码,方便以后找回与保号提醒。
---
## 三、发短信(完整流程)
### 3.1 在哪里发
- **唯一入口**:手机自带「信息 / Messages」App用 giffgaff 号码发送。
- **官网/My giffgaff**:仅支持查余额、套餐、订单,**不支持**在网页上发短信或看短信记录。
### 3.2 号码格式
- **发给英国号码**`+44 7XXX XXXXXX`(或 07XXX XXXXXX
- **发给中国号码**`+86138XXXXXXXX``0086138XXXXXXXX`(带国家区号)。
### 3.3 常见问题与排查
| 现象 | 处理办法 |
|:---|:---|
| **新卡发不出短信** | 手机设置 → 蜂窝网络 → 运营商:先选「中国移动」,仍不行再试「中国联通」。 |
| **eSIM 或换机后发不出** | 打开「信息」→ 检查 **短信中心号码** 是否为 `+447802002606`,不对则手动改为该号码。 |
| **无信号/无服务** | 查 [giffgaff 覆盖](https://www.giffgaff.com/coverage);新卡可等最多约 24 小时再试;必要时手动选网/重启。 |
---
## 四、收短信
- 所有短信(验证码、通知、普通短信)都在手机「信息」里收,与普通 SIM 一致。
- 可接收英国及国际号码发来的短信;收短信一般不单独扣费(以套餐/资费说明为准)。
---
## 五、保号(避免号码被回收)
- **规则****180 天内** 账户余额必须有 **至少一次变动**(充值或消费),否则号码可能被回收。
- **最低成本保号**:发一条短信即可,约 **0.3 英镑/条**(以官网当前资费为准)。
- **可操作**
- 给任意号码发一条短信(如给自己另一个号),或
- 发短信到 giffgaff 客服:**+447973000186**。
- **建议**:在日历里设「每 56 个月提醒」发一条短信或做一次充值,避免忘掉 180 天。
---
## 六、其他注意
- **关闭语音信箱**:在拨号盘输入 **##002#** 可关闭语音信箱,避免漏接来电被转语音信箱产生扣费。
- **资费与帮助**:以 [help.giffgaff.com](https://help.giffgaff.com) 及 My giffgaff 当前说明为准;发不出短信时可看 [Why can't I send a text?](https://help.giffgaff.com/en/articles/243850-why-can-t-i-send-a-text)。
---
## 七、快速检索关键词
本文档覆盖并可直接搜索:
- **发短信**、**收短信**、**giffgaff**、**英国卡**、**保号**、**短信中心号码**、**eSIM**、**无法发短信**、**My giffgaff**、**登录**、**180天**、**+447802002606**、**+447973000186**、**##002#**
---
*史记归位:运营中枢/参考资料 · 卡若AI*

View File

@@ -0,0 +1,73 @@
# 卡猫复盘格式(固定规则)
> **专用规则。** 卡猫复盘是**以婼瑄目录为唯一素材**的复盘,目标·结果**仅指卡猫今年总目标及完成百分比**;正文中**人、事、数必须具体**,不得泛写。
---
## 一、目标·结果·达成率的含义(强制)
- **🎯 目标·结果·达成率** 在本格式下**仅指**
- **目标** = **卡猫当年(如 2026 年)总目标**(一句说清年度主目标,可多句拆为 1 2 3每句 ≤30 字);
- **结果** = 截至本复盘日,相对该总目标的**当前达成情况**(一句);
- **达成率** = **完成百分比 XX%**(可估算,需有数字)。
- 不得用「本次任务目标」「本日复盘目标」等替代;若当年总目标尚未成文,可写「卡猫 20XX 年总目标XXX待补充当前完成约 X%」。
**示例(仅作格式参考):**
```
🎯 目标·结果·达成率
卡猫 2026 年总目标:融 2000 万 + 电竞实体落地 + 流水并表。当前:流水/开票线在对接,实体与融资节奏在推进;整体完成约 5%。
```
---
## 二、人、事、数必须具体(强制)
- **人**:出现即写**真实称呼/角色**(如叶倩如、郑朝阳、郑清土、财务、腾讯侧),不写「某人」「有人」「说话人」。
- **事**:写**具体事件/动作**如「2 月 28 日叶倩如侧聊流量开票与下游消化」「与财务对平台可开品名与票点」),不写「讨论了问题」「做了沟通」。
- **数**:能写数字的必须写(如年 10 亿流水/票、广告费流水 15%、6 点专票 vs 1 点普票、第一轮 20% 换 500 万、Gateway 端口 18789不写「很多」「若干」。
---
## 三、完整格式(与卡若复盘五块一致)
卡猫复盘仍用**完整复盘五块**,仅对「目标·结果·达成率」和「内容具体度」做上述约束:
```
[卡若复盘]YYYY-MM-DD HH:mm
🎯 目标·结果·达成率
卡猫 [年份] 年总目标:[一句或 1 2 3 句,每句 ≤30 字]。当前:[一句]。整体完成约 XX%。
📌 过程
1. [具体人] [具体事][具体数字]。(一句)
2. …
3. …
💡 反思
1. …
2. …
📝 总结
▶ 下一步执行
```
- 过程/反思/总结/下一步中涉及的人、事、数均须**具体**,见第二节。
---
## 四、素材目录与输出(强制)
- **素材目录****仅婼瑄目录**`/Users/karuo/Library/Mobile Documents/com~apple~CloudDocs/Documents/婼瑄`),含其下所有子目录(如 叶倩如、复盘、天恩 等)。不得以其他目录作为卡猫复盘素材来源。
- **文件选择**:当次复盘所依据的文件 = 婼瑄目录下**当日或指定日期新增/修改**的 txt、md 等可读文件;或用户指定的文件/日期范围。
- **输出**:复盘 Markdown 存于 **婼瑄/复盘/**,命名 `YYYY-MM-DD_卡猫复盘_主题.md`发布到飞书卡猫知识库节点并推送到卡猫群webhook
---
## 五、引用关系
- 本文件为**卡猫复盘**专用格式;通用复盘格式仍见 `卡若复盘格式_固定规则.md`
- 执行入口:`02_卡人/水桥_平台对接/飞书管理/卡猫复盘/SKILL.md`;触发词含「卡猫复盘」「婼瑄复盘」等。

View File

@@ -59,6 +59,16 @@
查报表、对账、同步 → 上表;做财务分析、报表生成 → 用土簿/财务管理 Skill。
### 项目调研(统一归档)
| 路径 | 说明 |
|:---|:---|
| **根目录** | `/Users/karuo/Documents/开发/7.项目调研` |
| 规范与项目列表 | `开发/7.项目调研/README.md` |
| Skill | 水溪 · 项目调研平台分析、聊天记录、对话分类、APP 资料 → 按项目归档) |
凡「项目调研」「平台分析」「A 群/聊天记录清理」「对话分类」「按项目归档」→ 产出与归档均进 `7.项目调研`,由水溪 项目调研 Skill 处理。
### 开发文档(已整合到 运营中枢)
| 路径/文档 | 说明 |

View File

@@ -13,7 +13,7 @@
────────────────────────────────────────────────────────────────────────
服务器/磁盘/NAS/备份/数据库/局域网/iPhone -> 卡资 → 金仓 -> 检查、清理、备份、管理
存客宝/远程部署/数据库/微信解析 -> 卡资 → 金盾 -> 存客宝、部署、数据
整理/归档/飞书/纪要/需求拆解/任务规划/小程序 -> 卡人 → 水溪/水泉/水桥 -> 整理、规划、同步
整理/归档/飞书/纪要/需求拆解/任务规划/小程序/项目调研/平台分析/聊天记录清理 -> 卡人 → 水溪/水泉/水桥 -> 整理、规划、同步、调研归档
视频切片/逆向/项目/模板/档案/前端 -> 卡木 → 木叶/木根/木果 -> 视频、逆向、项目
全栈/消息/读书/文档清洗/代码修复/追问/本地模型 -> 卡火 → 火炬/火锤/火眼/火种 -> 开发、修复、学习
商业/技能工厂/流量/招商/财务/报表 -> 卡土 → 土基/土砖/土渠/土簿 -> 算账、复制、流量、财务
@@ -33,7 +33,7 @@
| **设备管理** | iPhone管理、局域网控制、iCloud管理 | 金仓 |
| **数据安全** | 容灾备份、微信管理、照片分类 | 金仓 |
| **开发辅助** | 代码修复、智能追问、开发模板、项目生成、v0模型集成、Vercel部署、存客宝、个人档案生成器、任务规划 | 金盾 |
| **信息流程** | 飞书管理、智能纪要、小程序管理、需求拆解与计划制定、对话归档 | 水桥 |
| **信息流程** | 飞书管理、智能纪要、小程序管理、需求拆解与计划制定、对话归档、**项目调研** | 水桥/水溪 |
| **知识管理** | 全栈开发、读书笔记、文档清洗 | 火炬 |
| **效率工具** | 上帝之眼、前端生成、技能工厂、流量自动化、视频切片、网站逆向分析 | 火眸 |
| **商业运营** | 财务管理、商业工具集、手机流量自动操作 | 土簿 |

View File

@@ -170,3 +170,4 @@
| 2026-02-27 10:53:48 | 🔄 卡若AI 同步 2026-02-27 10:53 | 更新Cursor规则、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-02-28 06:25:45 | 🔄 卡若AI 同步 2026-02-28 06:25 | 更新:水桥平台对接、卡木、火炬、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-02-28 13:25:18 | 🔄 卡若AI 同步 2026-02-28 13:25 | 更新Cursor规则、金仓、火炬、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-02-28 13:27:29 | 🔄 卡若AI 同步 2026-02-28 13:27 | 更新:火炬、运营中枢工作台 | 排除 >20MB: 14 个 |

View File

@@ -173,3 +173,4 @@
| 2026-02-27 10:53:48 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-27 10:53 | 更新Cursor规则、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-28 06:25:45 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-28 06:25 | 更新:水桥平台对接、卡木、火炬、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-28 13:25:18 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-28 13:25 | 更新Cursor规则、金仓、火炬、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-28 13:27:29 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-28 13:27 | 更新:火炬、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |

View File

@@ -64,6 +64,7 @@
| 小程序管理 | 水桥/小程序管理/SKILL.md |
| 需求拆解与计划制定 | 水泉/需求拆解与计划制定/SKILL.md |
| 对话归档 | 水溪/对话归档/SKILL.md |
| **项目调研** | 水溪_整理归档/项目调研/SKILL.md |
---