🔄 卡若AI 同步 2026-03-03 10:15 | 更新:水桥平台对接、卡木、火炬、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 14 个

This commit is contained in:
2026-03-03 10:15:45 +08:00
parent 64a81a4b3b
commit e48aa3afdd
19 changed files with 613 additions and 78 deletions

View File

@@ -18,6 +18,7 @@
- **脚本**:永平项目下 `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`。无需复制链接,直接运行命令即可。
- **自定义 webhook--webhook**:用 `--webhook "https://..."` 可指定其他群。**推送前必须确认**目标群名称含有「瘦」或「IP」否则不要推送脚本无法从 webhook 获知群名,需人工在飞书里核对。
- **命令示例**(上传完成后执行):
```bash

View File

@@ -8,7 +8,9 @@
| 项目 | 规范 |
|:---|:---|
| **「我」** | **整篇文章最多出现三次**。多用「这边」「直接」「就」「场上」等替代;成稿后全文搜索「我」,超过 3 处必须改写。 |
| **「我」** | **整篇文章最多出现三次**(仅指叙述者视角;对话引用里的「我」不占名额)。成稿后全文搜索「我」,超过 3 处必须改写。 |
| **「这边」「那边」** | **不要用**。改用灵活表述:房主、场上、就、手头、这种模式、知识星球/小程序 等指代或省略主语。 |
| **「回答说」** | **不要用**。房主是强势角色,文章目的是让读者产生深度认同感;房主的话用**直接陈述**呈现,不写「回答说,……」,让语气与性格一致、话一出口就立住。 |
| **「卡若」** | 每篇最多提一次;不需要时可完全不出现。 |
---
@@ -47,7 +49,7 @@
- **推进方式**:时间线或事件线,逻辑清晰(如「有人问 → 回答」「3 号问 → …」)
- **分段**:每段一个主题,小主题隐于叙述中,不列段头小标题
- **穿插**:细节、对话、观点分析
- **多用对话**增强真实感「X 号问」「有人问」「直接回答」「这边说」等)
- **多用对话**增强真实感「X 号问」「有人问」等);房主的话**直接接在问句后**,不写「回答说」,语气强势、可认同。
- **分享句(两处,强制)**:约 20% 处一句、结尾一句,各不超过 50 字,围绕本节主题、紧扣内容,留余味或可执行。**不要出现「干货」二字**,不要用「干货:」或「**干货**:」等格式,直接写一句金句即可,可单独成段。
---

View File

@@ -86,7 +86,7 @@ OUT_DIR = Path("/Users/karuo/Documents/聊天记录/soul")
def run_playwright_page_export(from_num: int, to_num: int) -> int:
"""在 Playwright 页面内直接请求导出,绕过 Cookie,保证成功"""
"""在 Playwright 用系统默认浏览器打开,页面内直接请求导出,复用已有登录/Cookie。"""
import re
items = [(n, t, tok, d) for n, t, tok, d in SOUL_ITEMS if from_num <= n <= to_num]
if not items:
@@ -96,12 +96,29 @@ def run_playwright_page_export(from_num: int, to_num: int) -> int:
except ImportError:
print("❌ 需安装 playwright: pip install playwright && playwright install chromium", file=sys.stderr)
return 1
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_export_")
sys.path.insert(0, str(SCRIPT_DIR))
try:
from playwright_default_browser import launch_playwright_with_default_browser
except ImportError:
launch_playwright_with_default_browser = None
try:
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(ud, headless=False, timeout=15000)
pg = ctx.pages[0] if ctx.pages else ctx.new_page()
if launch_playwright_with_default_browser:
ctx, get_page, cleanup = launch_playwright_with_default_browser(p, headless=False, timeout=15000)
pg = get_page()
else:
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_export_")
ctx = p.chromium.launch_persistent_context(ud, headless=False, timeout=15000)
pg = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
import shutil
shutil.rmtree(ud, ignore_errors=True)
pg.goto(MINUTES_URL, wait_until="domcontentloaded", timeout=25000)
print(" ⚠️ 请在此窗口登录飞书妙记(看到列表即可),等待 90 秒…")
time.sleep(90)
@@ -128,47 +145,66 @@ def run_playwright_page_export(from_num: int, to_num: int) -> int:
except Exception as e:
print(f"{topic}: {e}")
time.sleep(1)
ctx.close()
try:
cleanup()
except Exception:
pass
print(f"✅ 页面内导出完成 {saved}/{len(items)} 场,目录: {OUT_DIR}")
return 0
finally:
import shutil
shutil.rmtree(ud, ignore_errors=True)
except Exception as e:
print(f"❌ Playwright 导出异常: {e}", file=sys.stderr)
return 1
def collect_cookie_via_playwright_standalone() -> str:
"""Playwright 启动独立 Chromium,用户登录后抓 Cookie。"""
"""Playwright 用系统默认浏览器打开,用户登录后抓 Cookie,复用已有登录态"""
try:
from playwright.sync_api import sync_playwright
except ImportError:
return ""
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_cookie_")
sys.path.insert(0, str(SCRIPT_DIR))
try:
from playwright_default_browser import launch_playwright_with_default_browser
except ImportError:
launch_playwright_with_default_browser = None
cookie_str = ""
try:
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(
user_data_dir=ud,
headless=False,
args=["--start-maximized"],
viewport={"width": 1280, "height": 900},
timeout=15000,
)
try:
if launch_playwright_with_default_browser:
ctx, get_page, cleanup = launch_playwright_with_default_browser(p, headless=False, timeout=15000)
pg = get_page()
else:
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_cookie_")
ctx = p.chromium.launch_persistent_context(
user_data_dir=ud,
headless=False,
args=["--start-maximized"],
viewport={"width": 1280, "height": 900},
timeout=15000,
)
pg = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
import shutil
shutil.rmtree(ud, ignore_errors=True)
try:
pg.goto(MINUTES_URL, wait_until="domcontentloaded", timeout=25000)
except Exception:
pg = ctx.new_page() if not ctx.pages else ctx.pages[0]
try:
pg.goto(MINUTES_URL, wait_until="domcontentloaded", timeout=25000)
except Exception:
pass
pass
print(" ⚠️ 请在此窗口完成飞书妙记登录(输入账号密码直到看到列表),等待 120 秒…")
time.sleep(120)
cookies = ctx.cookies("https://cunkebao.feishu.cn")
if not cookies:
cookies = ctx.cookies()
ctx.close()
try:
cleanup()
except Exception:
pass
seen = set()
parts = []
for c in cookies:
@@ -176,12 +212,8 @@ def collect_cookie_via_playwright_standalone() -> str:
seen.add(c["name"])
parts.append(f"{c['name']}={c.get('value','')}")
cookie_str = "; ".join(parts)
finally:
import shutil
try:
shutil.rmtree(ud, ignore_errors=True)
except Exception:
pass
except Exception as e:
print(f" Playwright 取 Cookie 失败: {e}", file=sys.stderr)
return cookie_str if len(cookie_str) > 200 else ""

View File

@@ -71,18 +71,37 @@ def export_via_cookie(object_token: str) -> str | None:
def export_via_playwright_page(object_token: str, title: str = "", wait_sec: int = 30) -> str | None:
"""Playwright 打开妙记页,在页面内 fetch 导出接口(带 credentials无感拿正文"""
"""Playwright 用系统默认浏览器打开妙记页,在页面内 fetch 导出接口(带 credentials复用已有登录/Cookie"""
try:
from playwright.sync_api import sync_playwright
except ImportError:
return None
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_one_")
sys.path.insert(0, str(SCRIPT_DIR))
try:
from playwright_default_browser import launch_playwright_with_default_browser
except ImportError:
launch_playwright_with_default_browser = None
result = [None]
cleanup = lambda: None
try:
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(ud, headless=False, timeout=15000)
pg = ctx.pages[0] if ctx.pages else ctx.new_page()
if launch_playwright_with_default_browser:
ctx, get_page, cleanup = launch_playwright_with_default_browser(p, headless=False, timeout=15000)
pg = get_page()
else:
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_one_")
ctx = p.chromium.launch_persistent_context(ud, headless=False, timeout=15000)
pg = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
import shutil
shutil.rmtree(ud, ignore_errors=True)
pg.goto(f"https://cunkebao.feishu.cn/minutes/{object_token}", wait_until="domcontentloaded", timeout=25000)
print(f" 页面已打开,等待 {wait_sec} 秒(若未登录请先登录)…")
time.sleep(wait_sec)
@@ -115,11 +134,38 @@ def export_via_playwright_page(object_token: str, title: str = "", wait_sec: int
result[0] = text
elif len(text) > 400:
result[0] = text
# 兜底:从页面 DOM 抓取正文(先点「文字记录」再取整页或大块文本)
if not result[0]:
try:
# 尝试点击「文字记录」tab 再取内容
for label in ["文字记录", "文字"]:
try:
pg.get_by_text(label, exact=False).first.click(timeout=3000)
time.sleep(1.5)
break
except Exception:
pass
dom_text = pg.evaluate("""() => {
let out = '';
const candidates = document.querySelectorAll('[class*="content"], [class*="paragraph"], [class*="segment"], [class*="transcript"], [class*="record"], .ne-doc-body, [role="main"]');
for (const el of candidates) {
const t = (el.innerText || el.textContent || '').trim();
if (t.length > 800 && (t.includes('') || t.includes(':') || /\\d{1,2}:\\d{2}:\\d{2}/.test(t))) { out = t; break; }
}
if (!out) out = document.body.innerText || document.body.textContent || '';
return out;
}""")
if dom_text and len(dom_text.strip()) > 300:
result[0] = dom_text.strip()
except Exception:
pass
except Exception as e:
print(f" Playwright 失败: {e}", file=sys.stderr)
finally:
import shutil
shutil.rmtree(ud, ignore_errors=True)
try:
cleanup()
except Exception:
pass
return result[0]

View File

@@ -0,0 +1,192 @@
#!/usr/bin/env python3
"""
检测系统默认浏览器,并用该浏览器启动 Playwright优先使用其用户数据目录以保留登录/Cookie。
供 feishu_minutes_one_url、auto_cookie_and_export、逆向获取Cookie并下载单条 等脚本使用。
"""
from __future__ import annotations
import platform
import subprocess
import sys
from pathlib import Path
def get_system_default_browser():
"""
检测系统当前使用的默认浏览器。
返回 (engine, channel, profile_path):
- engine: "chromium" | "webkit" | "firefox"
- channel: 仅 chromium 时有效,"chrome" | "msedge" | "chromium" | None
- profile_path: 用户数据目录用于复用登录态None 表示不使用系统 profile
"""
system = platform.system()
home = Path.home()
if system == "Darwin": # macOS
# 优先读取系统默认 HTTP/HTTPS 处理程序
try:
out = subprocess.run(
["defaults", "read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"],
capture_output=True,
text=True,
timeout=5,
)
if out.returncode == 0 and out.stdout:
for line in out.stdout.splitlines():
if "https" in line.lower() or "http" in line.lower():
continue
# 解析 LSHandlers 较复杂,改为按应用存在性检测
except Exception:
pass
# 按优先级检测已安装的浏览器与多数用户习惯一致Chrome/Edge 常用)
apps = [
("Google Chrome", "chromium", "chrome", home / "Library/Application Support/Google/Chrome"),
("Microsoft Edge", "chromium", "msedge", home / "Library/Application Support/Microsoft Edge"),
("Chromium", "chromium", "chromium", home / "Library/Application Support/Chromium"),
("Safari", "webkit", None, None),
("Firefox", "firefox", None, home / "Library/Application Support/Firefox"),
]
for app_name, engine, channel, profile in apps:
app_path = Path(f"/Applications/{app_name}.app")
if app_path.exists():
if engine == "firefox" and profile:
# Firefox 使用 Profiles/xxx.default 子目录
profiles = profile / "Profiles"
if profiles.exists():
for d in profiles.iterdir():
if d.is_dir() and (d / "prefs.js").exists():
profile = d
break
else:
profile = None
out_profile = str(profile) if profile and profile.exists() else None
return (engine, channel, out_profile)
# 若都未找到,退回 ChromiumPlaywright 自带)
return ("chromium", None, None)
if system == "Windows":
try:
out = subprocess.run(
["reg", "query", "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice", "/v", "ProgId"],
capture_output=True,
text=True,
timeout=5,
)
if out.returncode == 0 and "chrome" in out.stdout.lower():
return ("chromium", "chrome", None)
if out.returncode == 0 and "edge" in out.stdout.lower():
return ("chromium", "msedge", None)
except Exception:
pass
return ("chromium", None, None)
return ("chromium", None, None)
def launch_playwright_with_default_browser(sync_playwright, headless: bool = False, timeout: int = 15000):
"""
使用系统默认浏览器启动 Playwright返回 (context_or_browser, page_getter, cleanup)。
page_getter 用于获取当前 page可能是 context.pages[0] 或 new_page
cleanup 用于关闭 context/browser。
若使用持久 context则尽量复用系统 profile避免 Cookie 无法登录。
"""
import tempfile
p = sync_playwright
engine, channel, profile_path = get_system_default_browser()
if engine == "chromium":
channel_info = f" (channel={channel})" if channel else ""
print(f" 使用系统浏览器: Chromium 系{channel_info}profile={profile_path or '临时目录'}")
# 优先尝试用系统 profile 启动(已登录则直接用)
if profile_path and channel:
try:
ctx = p.chromium.launch_persistent_context(
profile_path,
channel=channel,
headless=headless,
timeout=timeout,
args=["--no-first-run"],
)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
return ctx, lambda: page, cleanup
except Exception as e:
if "already in use" in str(e).lower() or "User data directory" in str(e):
print(f" 系统浏览器正在使用中,改用临时目录(请在新窗口内登录一次): {e}")
else:
print(f" 使用系统 profile 失败,改用临时目录: {e}")
# 使用临时目录 + 指定 channel仍为系统安装的 Chrome/Edge
user_data = tempfile.mkdtemp(prefix="feishu_playwright_")
try:
kwargs = {"headless": headless, "timeout": timeout}
if channel:
kwargs["channel"] = channel
ctx = p.chromium.launch_persistent_context(user_data, **kwargs)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
import shutil
shutil.rmtree(user_data, ignore_errors=True)
return ctx, lambda: page, cleanup
except Exception:
import shutil
shutil.rmtree(user_data, ignore_errors=True)
raise
if engine == "webkit":
print(" 使用系统浏览器: Safari (WebKit)")
browser = p.webkit.launch(headless=headless)
ctx = browser
page = browser.new_page()
def cleanup():
try:
browser.close()
except Exception:
pass
return ctx, lambda: page, cleanup
if engine == "firefox":
print(" 使用系统浏览器: Firefox")
browser = p.firefox.launch(headless=headless)
ctx = browser
page = browser.new_page()
def cleanup():
try:
browser.close()
except Exception:
pass
return ctx, lambda: page, cleanup
# 默认
print(" 使用 Playwright 自带 Chromium")
user_data = tempfile.mkdtemp(prefix="feishu_playwright_")
ctx = p.chromium.launch_persistent_context(user_data, headless=headless, timeout=timeout)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
import shutil
shutil.rmtree(user_data, ignore_errors=True)
return ctx, lambda: page, cleanup

View File

@@ -0,0 +1,35 @@
/**
* 在已打开的飞书妙记页面cunkebao.feishu.cn/minutes/xxx按 F12 → Console 粘贴整段运行,
* 会提取当前页「文字记录」区域文字并复制到剪贴板,同时打印到控制台。
*/
(function () {
function getText() {
const candidates = document.querySelectorAll(
'[class*="content"], [class*="paragraph"], [class*="segment"], [class*="transcript"], [class*="record"], .ne-doc-body, [role="main"]'
);
for (const el of candidates) {
const t = (el.innerText || el.textContent || "").trim();
if (t.length > 800 && (t.includes("") || t.includes(":") || /\d{1,2}:\d{2}:\d{2}/.test(t))) {
return t;
}
}
return (document.body && (document.body.innerText || document.body.textContent)) || "";
}
const text = getText().trim();
if (!text) {
console.warn("未找到长文本,请先点击「文字记录」选项卡再运行本脚本。");
return;
}
const title = (document.title || "妙记").replace(/\s*\|\s*.*$/, "");
const out = "标题: " + title + "\n\n文字记录:\n\n" + text;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(out).then(function () {
console.log("已复制到剪贴板,共 " + out.length + " 字。可粘贴到记事本保存。");
}).catch(function () {
console.log(out.slice(0, 500) + "\n...(共 " + out.length + " 字,请手动选择上方输出复制)");
});
} else {
console.log(out.slice(0, 2000) + "\n...(共 " + out.length + " 字)");
}
console.log("前 500 字预览:", out.slice(0, 500));
})();

View File

@@ -122,7 +122,7 @@ def main() -> int:
try:
from feishu_minutes_one_url import export_via_playwright_page
print(" Cookie 导出 401改用 Playwright 页面内获取…")
text = export_via_playwright_page(token, title=title, wait_sec=45)
text = export_via_playwright_page(token, title=title, wait_sec=60)
except Exception as e:
print(" Playwright 兜底失败:", e, file=sys.stderr)
if text:

View File

@@ -347,6 +347,14 @@ python3 scripts/feishu_wiki_create_doc.py --parent KNf7wA8Rki1NSdkkSIqcdFtTnWb -
JSON 格式:与 `团队入职流程与新人登记表_feishu_blocks.json` 相同,含 `children` 数组(飞书 docx blocks
### 读书笔记发飞书(链接与子目录)
- **原则**:读书笔记写完后同步发到飞书知识库,发到**指定链接(父节点)下的对应子目录**。
- **链接分析**:飞书知识库链接格式为 `https://cunkebao.feishu.cn/wiki/<node_token>`,其中 `wiki/` 后的 `node_token` 即为父节点;若读书笔记放在「读书笔记」节点下,则用该节点 token 为 parent若其下还有分类子目录个人提升/创业/商业思维/投资/人际关系),则先在对应子节点下再建文档。
- **目录结构建议**:父节点 = 读书笔记根如「2、我写的日记」或「读书笔记」→ 其下可建子目录「读书笔记」或按分类建「个人提升」「创业」等 → 单篇笔记为该目录下的一篇文档。
- **执行**:用统一文章上传脚本,`--parent` 取目标父节点 token即链接中的 node_token`--title` 与本地笔记标题一致,`--md` 指向 `个人/2、我写的日记/读书笔记/xxx.md`。若需按分类落子目录,需先有该分类子节点 token再以该 token 为 parent 创建文档。
- **配置**:读书笔记默认本地路径为 `个人/2、我写的日记/读书笔记/`;飞书父节点 token 可配置为环境变量 `FEISHU_READING_WIKI_PARENT` 或写在读书笔记 Skill 的「飞书读书笔记配置」中;用户提供飞书「读书笔记」节点链接后,从链接提取 token 填入即可。
---
## 飞书导出 JSON 按原格式上传

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""
写入 3月3日 飞书日志到 3 月文档。昨日目标与今年总目标一致,百分比按总目标;今日 20 条视频 + 1 朋友圈,视频 Skill 四屏切片完成 20 个视频。
"""
import os
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))
from auto_log import get_token_silent, write_log, open_result, CONFIG
def _get_march_wiki_token():
raw = (CONFIG.get("MONTH_WIKI_TOKENS") or {}).get(3) or os.environ.get("FEISHU_MARCH_WIKI_TOKEN") or ""
return (raw or "").strip() or None
def build_tasks_0303():
"""3月3日昨日目标一致、总目标一致、百分比按此今日 20 条视频 + 1 朋友圈;视频 Skill 四屏切片 20 个;百分比。"""
return [
{
"person": "卡若",
"events": ["今日复盘", "本月与最终目标", "今日核心", "视频Skill四屏切片"],
"quadrant": "重要紧急",
"t_targets": [
"昨日目标与今年总目标一致,百分比按总目标执行",
"本月目标约 12%,距最终目标差 88%",
"今日核心:每天 20 条 Soul 视频 + 20:00 发 1 条朋友圈",
"视频 Skill 四屏切片:完成 20 个视频(当日完成度见反馈)",
],
"n_process": [
"【复盘】昨日目标一致、今年总目标一致,百分比按 2026年整体目标 对齐",
"【2月突破执行】延续 3 月,本月/最终目标百分比已按进度写入",
"【今日】20 条视频(四屏切片)+ 1 条朋友圈;视频切片 Skill 操作执行",
],
"t_thoughts": [
"今日一条核心20 条 Soul 视频 + 8 点 1 条朋友圈,持续拉齐与最终目标",
"四屏切片完成 20 个视频,按当日完成数看百分比",
],
"w_work": [
"20 条 Soul 视频(四屏切片)",
"20:00 发 1 条朋友圈",
"视频 Skill 操作",
"飞书日志",
],
"f_feedback": [
"本月/最终目标 12% / 100%,差 88%",
"今日核心→20 条 Soul + 8 点朋友圈 🔄",
"四屏切片 20 条→当日完成度待填 % 🔄",
],
}
]
def main():
token = get_token_silent()
if not token:
print("❌ 无法获取飞书 Token")
sys.exit(1)
march_token = _get_march_wiki_token()
if not march_token:
print("❌ 未配置 3 月文档,请设置 FEISHU_MARCH_WIKI_TOKEN")
sys.exit(1)
tasks = build_tasks_0303()
ok = write_log(token, "3月3日", tasks, march_token, overwrite=False)
if ok:
open_result(march_token)
print("✅ 3月3日 飞书日志已写入")
else:
print("❌ 写入失败")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -17,7 +17,7 @@
## 二、视频结构:提问→回答 + 前3秒高光 + 去语助词
- **前3秒**:先看片段有没有人提问;**有提问**则把**提问的问题**放到前3秒封面/前贴),先展示问题再播回答;无提问则用金句/悬念作 hook。
- **每个话题前均优先提问→回答**:先看片段有没有人提问;**有提问**则把**提问的问题**放到前3秒封面/前贴),先展示问题再播回答;无提问则用金句/悬念作 hook。
- **成片链路**前3秒展示问题或金句→ 正片回答 → **整片去除语助词**(提问与回答部分均由 soul_enhance 清理)。
- **高光**按「3秒高光亮点」剪每段 30300 秒完整语义单元;高光识别若有提问须填 `question`,且 `hook_3sec` 与之一致。
@@ -50,8 +50,8 @@
## 五、成片:封面 + 字幕 + 竖屏
- **封面**:竖屏 498×1080 内**不超出界面**;深色渐变背景(墨绿→绿)、左上角 Soul logo、标题文字**严格居中**且左右留白 44px多行自动换行不裁切。
- **字幕**:封面结束后才显示,**居中**在竖屏内;语助词由 soul_enhance 统一清理。重新加字幕时加 `--force-burn-subs`
- **封面**:竖屏 498×1080 内**不超出界面****半透明质感**(背景 alpha=165透出底层画面深色渐变(墨绿→绿)、左上角 Soul logo、标题文字**严格居中**且左右留白 44px多行自动换行不裁切。透明度在 `soul_enhance.py` 中由 `VERTICAL_COVER_ALPHA` 调节0255
- **字幕**:封面结束后才显示,**居中**在竖屏内;烧录用**图像 overlay**(每张字幕图 `-loop 1` + `enable=between(t,a,b)`),若系统 FFmpeg 带 libass 可改用 SRT+subtitles 滤镜;语助词由 soul_enhance 统一清理。重新加字幕时加 `--force-burn-subs`
- **竖屏**498×1080crop 参数与 `参考资料/竖屏中段裁剪参数说明.md` 一致
---

View File

@@ -0,0 +1,13 @@
# 视频切片 · 参考资料
本目录为视频切片 Skill 的参考资料若出现「Unable to open 高光识别提示词.md」类错误请用下面方式之一打开
- **本目录下的文件**[高光识别提示词.md](./高光识别提示词.md)
- **快速打开**:在 Cursor 中 `Cmd+P`(或 Ctrl+P输入 `高光识别提示词`,选择 `参考资料/高光识别提示词.md`
| 文件 | 说明 |
|------|------|
| 高光识别提示词.md | AI 高光识别用提示词Hook/CTA/提问→回答) |
| 视频结构_提问回答与高光.md | 提问→回答结构、前3秒、去语助词 |
| 竖屏中段裁剪参数说明.md | Soul 竖屏 498×1080 crop 参数 |
| 主题片段提取规则.md | 按章节主题提取片段规则 |

View File

@@ -1,6 +1,7 @@
# 高光识别提示词
> 所有输出Hook、CTA、标题统一**简体中文**。
> 所有输出Hook、CTA、标题统一**简体中文**。
> 本文件路径:`视频切片/参考资料/高光识别提示词.md`卡若AI 项目内)
用于分析视频文字稿识别最有传播力的片段并生成前3秒Hook和结尾CTA。
@@ -48,9 +49,9 @@
- 吊人胃口的开场
- "接下来这段更精彩"
# 提问→回答 结构(前3秒优先用提问
# 提问→回答 结构(每个话题前均优先
**成片结构**:先展示**提问**前3秒再进入**回答**(正片)。若片段里有人提问,必须把**提问的问题**放到前3秒。
**每个话题/高光片段均优先采用提问→回答形式**:先展示**提问**前3秒再进入**回答**(正片)。若片段里有人提问,必须把**提问的问题**放到前3秒。
1. **判断**:看该高光片段文字稿中是否有人提问(观众/连麦者问的问题)。
2. **若有提问**

View File

@@ -159,7 +159,7 @@ def _build_prompt(transcript: str, clip_count: int) -> str:
txt = transcript[:5000] if len(transcript) > 5000 else transcript
return f"""识别视频文字稿中的 {clip_count} 个高光片段,直接输出 JSON 数组,第一个字符必须是 [。
重要:若某片段里有人提问(观众/连麦者问的问题),必须提取提问内容填 question且 hook_3sec 用该提问成片前3秒先展示提问再播回答。
重要:每个话题均优先提问→回答。若某片段里有人提问(观众/连麦者问的问题),必须提取提问内容填 question且 hook_3sec 用该提问成片前3秒先展示提问再播回答。
示例(有提问):
[{{"title":"普通人怎么敢跟ZF搞","start_time":"01:12:30","end_time":"01:15:30","question":"普通人怎么敢跟ZF搞","hook_3sec":"普通人怎么敢跟ZF搞","cta_ending":"{DEFAULT_CTA}","transcript_excerpt":"维权起头跑通就成生意","reason":"提问+回答完整"}}]

View File

@@ -89,6 +89,13 @@ FILLER_WORDS = [
'怎么说呢', '你知道吗', '我跟你说', '', '', 'OK', 'ok',
]
# 不烧录的无关/模板句(仅整句完全匹配或极短规则说明,避免误杀正片对白)
SKIP_SUBTITLE_PHRASES = (
"回复1", "回复 1", "排序分享", "上麦后按格式介绍自己", "进资源泡泡群", "做矩阵切片",
"合作私聊", "群主必", "时间5~10分钟", "我能帮到大家什么", "我需要什么帮助",
"廿四先生", "进来陪你聊天", "建房领开工红包",
)
# 关键词高亮(重点突出,按长度排序避免短词覆盖长词)
KEYWORDS = [
'100万', '50万', '30万', '10万', '5万', '1万',
@@ -118,6 +125,8 @@ SOUL_GREEN_DARK = (0, 160, 80)
VERTICAL_COVER_TOP = (12, 32, 24) # 深墨绿
VERTICAL_COVER_BOTTOM = (8, 48, 36) # 略亮绿
VERTICAL_COVER_PADDING = 44 # 左右留白,保证文字不贴边、不超出
# 成片封面半透明质感:背景层 alpha便于透出底层画面
VERTICAL_COVER_ALPHA = 165 # 0~255越大越不透明
# 样式配置
STYLE = {
@@ -243,6 +252,53 @@ def parse_srt_for_clip(srt_path, start_sec, end_sec):
return subtitles
def _filter_relevant_subtitles(subtitles):
"""只过滤整句为规则/模板的条目,保留所有对白与重复句以便字幕连续"""
out = []
for sub in subtitles:
text = (sub.get("text") or "").strip()
if len(text) < 2:
continue
if text in SKIP_SUBTITLE_PHRASES:
continue
out.append(sub)
return out
def _sec_to_srt_time(sec):
"""秒数转为 SRT 时间格式 HH:MM:SS,mmm"""
h = int(sec) // 3600
m = (int(sec) % 3600) // 60
s = int(sec) % 60
ms = int((sec - int(sec)) * 1000)
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
def write_clip_srt(srt_path, subtitles, cover_duration):
"""写出用于烧录的 SRT仅保留封面结束后的字幕时间已相对片段"""
lines = []
idx = 1
for sub in subtitles:
start, end = sub['start'], sub['end']
if end <= cover_duration:
continue
start = max(start, cover_duration)
text = (sub.get('text') or '').strip().replace('\n', ' ')
if not text:
continue
lines.append(str(idx))
lines.append(f"{_sec_to_srt_time(start)} --> {_sec_to_srt_time(end)}")
lines.append(text)
lines.append("")
idx += 1
if not lines:
return None
with open(srt_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(lines))
return srt_path
def _is_mostly_chinese(text):
if not text or not isinstance(text, str):
return False
@@ -350,14 +406,14 @@ def get_cover_font(size):
return ImageFont.load_default()
def _draw_vertical_gradient(draw, width, height, top_rgb, bottom_rgb):
"""绘制竖屏封面用深色渐变背景,高级"""
def _draw_vertical_gradient(draw, width, height, top_rgb, bottom_rgb, alpha=255):
"""绘制竖屏封面用深色渐变背景alpha<255 时为半透明质"""
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))
draw.rectangle([0, y, width, y + 1], fill=(r, g, b, alpha))
def create_cover_image(hook_text, width, height, output_path, video_path=None):
@@ -370,13 +426,12 @@ def create_cover_image(hook_text, width, height, output_path, video_path=None):
is_vertical = (width, height) == (VERTICAL_W, VERTICAL_H)
if is_vertical:
# 竖屏成片:高级深色渐变背景,不依赖视频帧,保证不超出界
img = Image.new('RGB', (width, height), VERTICAL_COVER_TOP)
# 竖屏成片:半透明质感封面,渐变背景带 alpha透出底层画
img = Image.new('RGBA', (width, height), (*VERTICAL_COVER_TOP, VERTICAL_COVER_ALPHA))
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')
_draw_vertical_gradient(draw, width, height, VERTICAL_COVER_TOP, VERTICAL_COVER_BOTTOM, alpha=VERTICAL_COVER_ALPHA)
# 轻微半透明暗角,不盖死
overlay = Image.new('RGBA', (width, height), (0, 0, 0, 60))
img = Image.alpha_composite(img, overlay)
draw = ImageDraw.Draw(img)
else:
@@ -687,7 +742,7 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
create_cover_image(hook_text, out_w, out_h, cover_img, clip_path)
print(f" ✓ 封面生成")
# 2. 字幕逻辑:有字幕/图片则跳过,无则烧录中文
# 2. 字幕逻辑:有字幕则烧录(图像 overlay每张图 -loop 1 才能按时间 enable 显示)
sub_images = []
do_burn_subs = not skip_subs and (force_burn_subs or not detect_burned_subs(clip_path))
if skip_subs:
@@ -709,8 +764,10 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
for sub in subtitles:
if not _is_mostly_chinese(sub['text']):
sub['text'] = _translate_to_chinese(sub['text']) or sub['text']
print(f" ✓ 字幕解析 ({len(subtitles)}条),已转中文")
for i, sub in enumerate(subtitles[:50]):
# 仅过滤整句为规则/模板的条目,保留所有对白(含重复句,保证字幕连续)
subtitles = _filter_relevant_subtitles(subtitles)
print(f" ✓ 字幕解析 ({len(subtitles)}条)")
for i, sub in enumerate(subtitles):
img_path = os.path.join(temp_dir, f'sub_{i:04d}.png')
create_subtitle_image(sub['text'], out_w, out_h, img_path)
sub_images.append({'path': img_path, 'start': sub['start'], 'end': sub['end']})
@@ -740,22 +797,19 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
current_video = cover_output
print(f" ✓ 封面烧录")
# 5.2 分批烧录字幕(封面结束后才显示,不盖住封面
# 5.2 烧录字幕(图像 overlay每张图 -loop 1 才能按 enable=between(t,a,b) 显示
if sub_images:
batch_size = 8
batch_size = 5
for batch_idx in range(0, len(sub_images), batch_size):
batch = sub_images[batch_idx:batch_idx + batch_size]
inputs = ['-i', current_video]
for img in batch:
inputs.extend(['-i', img['path']])
inputs.extend(['-loop', '1', '-i', img['path']])
filters = []
last_output = '0:v'
for i, img in enumerate(batch):
input_idx = i + 1
output_name = f'v{i}'
# 封面结束后才显示字幕,不盖住封面
sub_start = max(img['start'], cover_duration)
if sub_start < img['end']:
enable = f"between(t,{sub_start:.3f},{img['end']:.3f})"
@@ -763,22 +817,20 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
else:
filters.append(f"[{last_output}]copy[{output_name}]")
last_output = output_name
filter_complex = ';'.join(filters)
batch_output = os.path.join(temp_dir, f'sub_batch_{batch_idx}.mp4')
cmd = [
'ffmpeg', '-y', *inputs,
'-filter_complex', filter_complex,
'-map', f'[{last_output}]', '-map', '0:a',
'-c:v', 'libx264', '-preset', 'fast', '-crf', '22',
'-c:a', 'copy', batch_output
'-c:a', 'copy', '-shortest', batch_output
]
result = subprocess.run(cmd, capture_output=True)
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f" ⚠ 字幕批次 {batch_idx} 报错: {(result.stderr or '')[-500:]}", file=sys.stderr)
if result.returncode == 0 and os.path.exists(batch_output):
current_video = batch_output
print(f" ✓ 字幕烧录")
# 5.3 加速10% + 音频同步

View File

@@ -1,16 +1,39 @@
---
name: 读书笔记
description: 五行结构化读书笔记。触发词拆解这本书、写入读书笔记、读书笔记、五行拆书、XMind笔记、卡读、金水木火土。使用金水木火土五行框架拆解书籍自动写入XMind。包含完整的拆书提示词、XMind写入脚本、操作流程
description: 五行结构化读书笔记。触发词拆解这本书、写入读书笔记、读书笔记、五行拆书、XMind笔记、卡读、金水木火土。使用金水木火土五行框架拆解书籍自动写入XMind;本地默认存 2、我写的日记/读书笔记,写完后可同步发飞书知识库对应子目录
group: 火
triggers: 拆解这本书、五行拆书、读书笔记
triggers: 拆解这本书、五行拆书、读书笔记、读书笔记发飞书
owner: 火炬
version: "1.0"
updated: "2026-02-16"
version: "1.1"
updated: "2026-03-03"
---
# 读书笔记
为卡若提供五行结构化读书笔记服务,支持自动写入 XMind。
为卡若提供五行结构化读书笔记服务,支持自动写入 XMind**本地固定存放 + 可选飞书同步**
## 默认存放路径(强制)
- **本地**`/Users/karuo/Documents/个人/2、我写的日记/读书笔记/`
- 文件名:`作者或主题_书名或简称_读书笔记.md`(例:`曾仕强_易经_读书笔记.md``Dan_Koe_如何在一天内彻底改造你的人生_读书笔记.md`
- 格式与规则见:`运营中枢/参考资料/卡若读书笔记格式_固定规则.md`
## 飞书同步(写完后发到飞书链接下子目录)
- **原则**:读书笔记写完后,**同时发到飞书知识库**指定链接下的**对应子目录**与本地「2、我写的日记/读书笔记」一致。
- **链接与目录**:飞书读书笔记父节点(链接)与子目录规则见下方「飞书读书笔记配置」;先分析该链接对应的 Wiki 节点与目录结构,再按**分类或单目录**落到对应子目录。
- **分类与子目录对应**:与主导图分类一致——个人提升 / 人际关系 / 创业 / 商业思维 / 投资;发飞书时可按该分类建子目录(如 `读书笔记/创业/纳瓦尔访谈_读书笔记`),或统一放在 `读书笔记/` 单目录下。
- **执行**:使用 `02_卡人/水桥_平台对接/飞书管理/脚本/` 下的统一文章上传或 md→blocks 脚本,`--parent` 取飞书读书笔记父节点 token标题与本地文件名一致内容为笔记正文若需按分类分子目录则先确保飞书侧存在对应子节点再在该节点下创建文档。
### 飞书读书笔记配置(链接与子目录)
| 项目 | 说明 |
|:---|:---|
| **飞书知识库链接** | 读书笔记要发到的**父节点**对应链接,格式:`https://cunkebao.feishu.cn/wiki/<node_token>`。若尚未确定,请在飞书知识库中新建或选中「读书笔记」节点,复制浏览器地址栏 `wiki/` 后的 token填入下方或环境变量 `FEISHU_READING_WIKI_PARENT`。 |
| **父节点 token** | 链接中 `wiki/` 后的一段,即发书时的 `--parent`。示例:`KNf7wA8Rki1NSdkkSIqcdFtTnWb`(日记/新研究);若读书笔记单独建了节点,用该节点 token。 |
| **子目录结构** | 可选:在父节点下建子目录「读书笔记」,再其下按分类建「个人提升」「创业」「商业思维」「投资」「人际关系」,与主导图分类一致;或父节点下直接为「读书笔记」单目录,所有笔记均发至此。 |
**未配置时**:若暂无飞书链接,笔记仍只写本地 `2、我写的日记/读书笔记/`;配置好父节点后,写新笔记时再同时发飞书并落到对应子目录。
## XMind 文件

View File

@@ -23,6 +23,7 @@
- **图片与流程图**:笔记内引用的图片、流程图建议与笔记**同目录**存放,用 `文件名.png``./文件名.png` 引用,避免用 `../../图片/` 等跨目录路径导致在不同预览/工作区下**不显示**。若用 Mermaid可在同节内再插入一张导出好的流程图 PNG保证不依赖渲染也能看到图。
- **图裂了怎么办**:若在 Cursor/VS Code 里图片不显示1用 Typora 打开该 md以文件所在目录为基准2在 Finder 中打开笔记所在文件夹,双击 md 用系统默认预览打开3直接打开同目录下对应的 PNG 文件。建议在每张图下方加一句备用说明:「若图不显示,请打开本笔记所在文件夹中的 `xxx.png` 查看。」
- **记忆写入**:在 `个人/1、卡若本人/记忆.md` 当日日期下追加 12 行摘要 + 本笔记文件路径;便于后续从记忆调出。
- **飞书同步**:读书笔记写完后,**同时发到飞书知识库**指定链接下的**对应子目录**。链接即飞书知识库「读书笔记」所在节点的地址(`https://cunkebao.feishu.cn/wiki/<node_token>`),子目录可与五行分类对应(个人提升/创业/商业思维/投资/人际关系)或统一为「读书笔记」单目录。父节点 token 与脚本用法见读书笔记 Skill「飞书同步」与「飞书读书笔记配置」。
---

View File

@@ -0,0 +1,50 @@
# 2026-03-03 综合复盘:大目标与本月完成度
> 依据本机今日相关内容、2026 年整体目标、当前任务看板与昨日未完成项综合分析与思考,并更新完成比例。
---
**[卡若复盘]2026-03-03**
**🎯 目标·结果·达成率**
大目标(全年 100%)与本月小目标(约 12%)对齐;昨日未完成已叠加今日;完成比例已按本机内容更新。达成率按项区分见下。
**📌 过程**
1. **本机今日与目标相关内容**
- 已读:`2026年整体目标.md`(总目标 100%、本月 % = 当前相对总目标完成度)、`当前任务看板.md`T003 卡若AI 4 项优化、T004 一人公司 5%、T005 玩值电竞 25%)、`2026年2月_整体月复盘.md`2 月主线 12%、一人公司/玩值电竞/今日核心延续)。
- 今日相关3 月飞书日志3月3日已写「昨日目标与总目标一致、本月 12% 距最终 88%」;视频 Skill四屏切片、20 条 Soul、20:00 朋友圈为当日执行项。
2. **大目标 vs 本月小事**
- **大目标**:全年终态 100%;当前相对总目标完成度 **约 12%**,距最终差 **88%**
- **本月小事**:一人公司 Agent 5%;玩值电竞 25%卡若AI 4 项优化执行中;每日 20 条 Soul 视频 + 20:00 发 1 条朋友圈;视频 Skill 四屏切片完成 20 个/日;飞书日志每日迭代。大目标统摄本月小事,百分比均相对总目标。
3. **昨日未完成叠加今日**
- 昨日3月2日若未打满**20 条 Soul 视频**、**20:00 发 1 条朋友圈**、**四屏切片 20 条**、**飞书日志** → 今日继续执行并计入当日完成度。
- 看板未闭环项T003/T004/T005持续执行中今日仍计入一人公司、玩值电竞、卡若AI 优化相关动作。
4. **完成比例更新(按本机与目标一致性)**
- **相对总目标**:本月目标 **12%**,距最终 **88%**(维持,与 2026年整体目标 一致)。
- **T004 一人公司****5%**(看板已载明,维持)。
- **T005 玩值电竞****25%**(看板已载明,维持)。
- **今日核心**20 条 Soul + 1 条朋友圈 + 四屏切片 20 条 → 当日完成度建议在飞书 F(反馈) 中填「四屏切片 20 条→X%」X = 当日完成数/20×100 或按实际条数折算)。
- **飞书日志**3月3日已写迭代 100%。
**💡 反思**
1. **大目标与本月**:总目标 100% 为锚点,本月 12% 与一人公司 5%、玩值电竞 25% 等均为相对总目标的进度,避免本月小事与全年脱节。
2. **昨日未完成叠加**:每日 20 条视频、1 条朋友圈、四屏切片 20 条若昨日未满额,今日应叠加执行并在反馈中更新当日完成度,便于看趋势。
3. **本机内容与目标**工作台与看板、2026年整体目标、2 月整体月复盘、飞书 3 月日志形成「总目标→本月→每日」链条,复盘时先读整体目标再更新比例,保证一致性。
**📝 总结**
综合本机今日相关文档与目标大目标100%与本月12%)、本月小事(一人公司 5%、玩值电竞 25%、今日核心 20 条+1 朋友圈+四屏切片 20 条)已对齐;昨日未完成项已叠加到今日计划;完成比例已按上述更新,飞书 3月3日 日志中可填「四屏切片 20 条→当日%」。
**▶ 下一步执行**
今日执行20 条 Soul 视频四屏切片、20:00 发 1 条朋友圈、视频 Skill 操作、飞书日志;晚些在飞书 3月3日 F(反馈) 补「四屏切片 20 条→X%」。明日写日志前先读《2026年整体目标》昨日未完成继续叠加。看板 T003/T004/T005 按进度更新上下文摘要与完成度。
---
*依据:运营中枢/工作台当前任务看板、2026年整体目标、2月整体月复盘、飞书 3月3日 日志设计、昨日未完成叠加规则。*

View File

@@ -212,3 +212,4 @@
| 2026-03-02 20:52:08 | 🔄 卡若AI 同步 2026-03-02 20:52 | 更新:金盾、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-03-03 04:51:53 | 🔄 卡若AI 同步 2026-03-03 04:51 | 更新:金盾、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-03-03 04:58:10 | 🔄 卡若AI 同步 2026-03-03 04:58 | 更新Cursor规则、总索引与入口、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-03-03 05:02:46 | 🔄 卡若AI 同步 2026-03-03 05:02 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 |

View File

@@ -215,3 +215,4 @@
| 2026-03-02 20:52:08 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-02 20:52 | 更新:金盾、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-03 04:51:53 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-03 04:51 | 更新:金盾、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-03 04:58:10 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-03 04:58 | 更新Cursor规则、总索引与入口、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-03 05:02:46 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-03 05:02 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |