From 31071e287ef04eb0600989abef194cec7887937b Mon Sep 17 00:00:00 2001 From: karuo Date: Tue, 3 Mar 2026 14:29:17 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20=E5=8D=A1=E8=8B=A5AI=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=202026-03-03=2014:29=20|=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=9A=E6=B0=B4=E6=A1=A5=E5=B9=B3=E5=8F=B0=E5=AF=B9=E6=8E=A5?= =?UTF-8?q?=E3=80=81=E5=8D=A1=E6=9C=A8=E3=80=81=E8=BF=90=E8=90=A5=E4=B8=AD?= =?UTF-8?q?=E6=9E=A2=E5=B7=A5=E4=BD=9C=E5=8F=B0=20|=20=E6=8E=92=E9=99=A4?= =?UTF-8?q?=20>20MB:=2014=20=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../飞书管理/参考资料/3月2日_飞书日志正文_可粘贴.md | 40 +++++++ .../飞书管理/脚本/.feishu_tokens.json | 6 +- .../飞书管理/脚本/write_0302_feishu_log.py | 78 ++++++++++++++ 03_卡木(木)/木叶_视频内容/视频切片/SKILL.md | 17 ++- .../木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md | 4 +- .../视频切片/参考资料/热点切片_标准流程.md | 10 +- .../木叶_视频内容/视频切片/脚本/batch_clip.py | 16 ++- .../视频切片/脚本/identify_highlights.py | 100 ++++++++++++++++-- .../木叶_视频内容/视频切片/脚本/soul_enhance.py | 37 +++++-- 运营中枢/工作台/gitea_push_log.md | 1 + 运营中枢/工作台/代码管理.md | 1 + 11 files changed, 282 insertions(+), 28 deletions(-) create mode 100644 02_卡人(水)/水桥_平台对接/飞书管理/参考资料/3月2日_飞书日志正文_可粘贴.md create mode 100644 02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_0302_feishu_log.py diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/参考资料/3月2日_飞书日志正文_可粘贴.md b/02_卡人(水)/水桥_平台对接/飞书管理/参考资料/3月2日_飞书日志正文_可粘贴.md new file mode 100644 index 00000000..d2e0d8ee --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/飞书管理/参考资料/3月2日_飞书日志正文_可粘贴.md @@ -0,0 +1,40 @@ +# 3月2日 · 飞书日志正文(补全版,百分比已写清) + +> 若已配置 `FEISHU_MARCH_WIKI_TOKEN`,可直接运行: +> `python3 脚本/write_0302_feishu_log.py` 写入 3 月文档。 +> 未配置时,可把下方内容复制到飞书 3 月文档中「3月2日」下。 + +--- + +## [重要紧急] 卡若(今日复盘、本月与最终目标、今日核心、一人公司、玩值电竞) + +**T(目标)** +- 昨日 3月1日:一人公司 5%、玩值电竞 25%、飞书日志 100% +- 本月目标约 **12%**,距最终目标差 **88%**(相对 2026 年总目标 100%) +- 一人公司 Agent → 视频切片/文章/直播/小程序/朋友圈/聚合 **5%** +- 玩值电竞 → Docker/功能推进 **25%** +- 今日核心:每天 20 条 Soul 视频 + 20:00 发 1 条朋友圈 + +**N(过程)** +- 【复盘】从聊天记录与今日文档统一整理;昨日目标与今年总目标一致 +- 【3月突破执行】本月/最终目标百分比已按 2026年整体目标 写入 +- 【今日】20 条视频 + 1 条朋友圈;一人公司第一、玩值电竞第二 + +**T(思考)** +- 今日一条核心:20 条 Soul 视频 + 8 点 1 条朋友圈,持续拉齐与最终目标 +- 百分比均相对总目标:本月 12%、一人公司 5%、玩值电竞 25% + +**W(工作)** +- 20 条 Soul 视频 +- 20:00 发 1 条朋友圈 +- 一人公司 / 玩值电竞推进 +- 飞书日志 + +**F(反馈)** +- 本月/最终目标 **12% / 100%**,差 **88%** +- 一人公司 **5%** 🔄 | 玩值电竞 **25%** 🔄 +- 今日核心→20 条 Soul + 8 点朋友圈 🔄 + +--- + +*脚本:`脚本/write_0302_feishu_log.py`;写前请读 运营中枢/工作台/2026年整体目标.md* diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json index fc84b743..7e66cd14 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json @@ -1,6 +1,6 @@ { - "access_token": "u-dTB8Oq8l5cwFnz.Mm7oowQlh3cbxghihiMGaJQg027gZ", - "refresh_token": "ur-eYivb2AhNfRozQiZ3WhpoGlh14b1ghoXPgGaUA0022hV", + "access_token": "u-ccSSnhQFpeIb1RCP8056ZGlh1AFxghqXigGaIM4023gJ", + "refresh_token": "ur-duDSaCqEBegFY36.u.uqEmlh3I91ghihhMGaJR00235Z", "name": "飞书用户", - "auth_time": "2026-03-03T10:19:28.119191" + "auth_time": "2026-03-03T14:28:44.789463" } \ No newline at end of file diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_0302_feishu_log.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_0302_feishu_log.py new file mode 100644 index 00000000..dab1b846 --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_0302_feishu_log.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +""" +补全 3月2日 飞书日志到 3 月文档,百分比写清楚。 +""" +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_0302(): + """3月2日:昨日3月1日完成度、本月与最终目标百分比、今日核心;百分比写清楚。""" + return [ + { + "person": "卡若", + "events": ["今日复盘", "本月与最终目标", "今日核心", "一人公司", "玩值电竞"], + "quadrant": "重要紧急", + "t_targets": [ + "昨日 3月1日:一人公司 5%、玩值电竞 25%、飞书日志 100%", + "本月目标约 12%,距最终目标差 88%(相对 2026 年总目标 100%)", + "一人公司 Agent → 视频切片/文章/直播/小程序/朋友圈/聚合 5%", + "玩值电竞 → Docker/功能推进 25%", + "今日核心:每天 20 条 Soul 视频 + 20:00 发 1 条朋友圈", + ], + "n_process": [ + "【复盘】从聊天记录与今日文档统一整理;昨日目标与今年总目标一致", + "【3月突破执行】本月/最终目标百分比已按 2026年整体目标 写入", + "【今日】20 条视频 + 1 条朋友圈;一人公司第一、玩值电竞第二", + ], + "t_thoughts": [ + "今日一条核心:20 条 Soul 视频 + 8 点 1 条朋友圈,持续拉齐与最终目标", + "百分比均相对总目标:本月 12%、一人公司 5%、玩值电竞 25%", + ], + "w_work": [ + "20 条 Soul 视频", + "20:00 发 1 条朋友圈", + "一人公司 / 玩值电竞推进", + "飞书日志", + ], + "f_feedback": [ + "本月/最终目标 12% / 100%,差 88%", + "一人公司 5% 🔄 | 玩值电竞 25% 🔄", + "今日核心→20 条 Soul + 8 点朋友圈 🔄", + ], + } + ] + + +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_0302() + ok = write_log(token, "3月2日", tasks, march_token, overwrite=True) + if ok: + open_result(march_token) + print("✅ 3月2日 飞书日志已补全(百分比已写清)") + else: + print("❌ 写入失败") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/03_卡木(木)/木叶_视频内容/视频切片/SKILL.md b/03_卡木(木)/木叶_视频内容/视频切片/SKILL.md index 3a38745e..dbd74ed5 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/SKILL.md +++ b/03_卡木(木)/木叶_视频内容/视频切片/SKILL.md @@ -19,7 +19,7 @@ updated: "2026-03-03" ## ⭐ Soul派对切片流程(默认) ``` -原始视频 → MLX转录 → 字幕转简体 → 高光识别(当前模型/AI) → 批量切片 → soul_enhance → 输出成片 +原始视频 → MLX转录 → 字幕转简体 → 高光识别(API 优先/最佳模型,失败则 Ollama→规则) → 批量切片 → soul_enhance → 输出成片 ↑ ↓ 提取后立即繁转简+修正错误 封面+字幕(已简体)+加速10%+去语气词 ``` @@ -53,8 +53,9 @@ eval "$(~/miniforge3/bin/conda shell.zsh hook)" conda activate mlx-whisper mlx_whisper audio.wav --model mlx-community/whisper-small-mlx --language zh --output-format all -# 2. 高光识别(Ollama → 规则;流水线会在读取 transcript 前自动转简体) +# 2. 高光识别(API 优先,未配置则 Ollama → 规则;流水线会在读取 transcript 前自动转简体) python3 identify_highlights.py -t transcript.srt -o highlights.json -n 6 +# 需配置 OPENAI_API_KEY 或 OPENAI_API_BASES/KEYS/MODELS,默认模型 gpt-4o # 3. 切片 python3 batch_clip.py -i 视频.mp4 -l highlights.json -o clips/ -p soul @@ -262,7 +263,7 @@ python3 scripts/burn_subtitles_clean.py -i enhanced.mp4 -s clean.srt -o 成片.m | **soul_vertical_crop.py** | Soul 竖屏中段批量裁剪(横版→498×1080 去白边) | ⭐⭐⭐ | | **scene_detect_to_highlights.py** | 镜头/场景检测 → highlights.json(PySceneDetect,可接 batch_clip) | ⭐⭐ | | chapter_themes_to_highlights.py | 按章节 .md 主题提取片段(本地模型→highlights.json) | ⭐⭐⭐ | -| identify_highlights.py | 高光识别(Ollama→规则) | ⭐⭐ | +| identify_highlights.py | 高光识别(API 优先→Ollama→规则,默认 gpt-4o) | ⭐⭐ | | batch_clip.py | 批量切片 | ⭐⭐ | | one_video.py | 单视频一键成片 | ⭐⭐ | | burn_subtitles_clean.py | 字幕烧录(无阴影) | ⭐ | @@ -291,6 +292,14 @@ conda activate mlx-whisper mlx_whisper audio.wav --model mlx-community/whisper-small-mlx --language zh --output-format all ``` +### 高光识别模型(API 优先) + +高光识别默认使用**当前可用最佳模型**:优先走 **OpenAI 兼容 API**(见下),未配置或失败时再用本地 Ollama,最后规则兜底。 + +- **单接口**:`OPENAI_API_BASE`、`OPENAI_API_KEY`、`OPENAI_MODEL`(默认 `gpt-4o`)。 +- **多接口故障切换**:`OPENAI_API_BASES`、`OPENAI_API_KEYS`、`OPENAI_MODELS`(逗号分隔,按顺序尝试)。 +- 不写死密钥,从环境变量读取;详见 `运营中枢/参考资料/卡若AI异常处理与红线.md` 与 API 稳定性规则。 + ### 依赖检查 ```bash @@ -303,7 +312,7 @@ conda activate mlx-whisper python -c "import mlx_whisper; print('OK')" # Python库 -pip3 list | grep -E "moviepy|Pillow|opencc" +pip3 list | grep -E "moviepy|Pillow|opencc|openai" ``` ### 安装依赖 diff --git a/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md b/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md index 84486039..9ad1c448 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md +++ b/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md @@ -34,7 +34,7 @@ ``` - **batch_clip**:输出到 `clips/` -- **soul_enhance -o 成片/ --vertical --title-only**:封面(优先用 question 作前3秒)+ 字幕 + **完整去语助词** + 竖屏裁剪,直接输出到 `成片/`,文件名为标题 +- **soul_enhance -o 成片/ --vertical --title-only**:**文件名 = 封面标题 = highlights 的 title**(去杠:`:|、—、/` 等替换为空格),名字与标题一致、无序号无杠;字幕烧录(随语音走动);完整去语助词;竖屏裁剪直出到 `成片/` --- @@ -52,7 +52,7 @@ ## 五、成片:封面 + 字幕 + 竖屏 -- **封面**:竖屏 498×1080 内**不超出界面**;**半透明质感**(背景 alpha=165,透出底层画面);深色渐变(墨绿→绿)、左上角 Soul logo、标题文字**严格居中**且左右留白 44px,多行自动换行不裁切。透明度在 `soul_enhance.py` 中由 `VERTICAL_COVER_ALPHA` 调节(0~255)。 +- **封面**:竖屏 498×1080 内**不超出界面**;**半透明质感**(背景 alpha=165);深色渐变、左上角 Soul logo;**封面显示标题 = 成片文件名 = highlights.title**(去杠后一致,无 `:|—/`、无序号);标题文字严格居中、多行自动换行。透明度由 `VERTICAL_COVER_ALPHA` 调节。 - **字幕**:封面结束后才显示,**居中**在竖屏内;烧录用**图像 overlay**(每张字幕图 `-loop 1` + `enable=between(t,a,b)`),若系统 FFmpeg 带 libass 可改用 SRT+subtitles 滤镜;语助词由 soul_enhance 统一清理。重新加字幕时加 `--force-burn-subs`。 - **竖屏**:498×1080,crop 参数与 `参考资料/竖屏中段裁剪参数说明.md` 一致 diff --git a/03_卡木(木)/木叶_视频内容/视频切片/参考资料/热点切片_标准流程.md b/03_卡木(木)/木叶_视频内容/视频切片/参考资料/热点切片_标准流程.md index 3ed7ba12..231dfa5b 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/参考资料/热点切片_标准流程.md +++ b/03_卡木(木)/木叶_视频内容/视频切片/参考资料/热点切片_标准流程.md @@ -66,8 +66,16 @@ **只保留两个目录**:**切片**、**成片**。其他中间目录不保留。 +**命名与标题统一**:成片文件名 = 封面显示标题 = `highlights.json` 的 `title`;对 title 做「去杠」(`:|、—、/` 等替换为空格),保证无序号、无多余符号,名字与标题一致。 + --- +## 本地处理说明(与剪映逆向分析一致) + +使用**本地管线**处理视频,不依赖剪映二进制:MLX Whisper 转录 → 高光/时间节点(highlights.json)→ batch_clip 切片 → soul_enhance(去语助词+封面+字幕)。封面标题**不显示 123 等序号**,仅显示高光/提问文案。 + +参考:`剪映_智能剪口播与智能片段分割_逆向分析.md` 第五节「自实现建议」。 + ## 命令速查(112 场示例) ```bash @@ -76,6 +84,6 @@ # 3. 切片 python3 batch_clip.py -i "原视频.mp4" -l highlights.json -o 切片/ -p soul112 -# 4~5. 成片(去语助词+封面+字幕) +# 4~5. 成片(去语助词+封面+字幕,覆盖原成片) python3 soul_enhance.py -c 切片/ -l highlights.json -t transcript.srt -o 成片/ --vertical --title-only --force-burn-subs ``` diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/batch_clip.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/batch_clip.py index 3a24e562..7c00087b 100755 --- a/03_卡木(木)/木叶_视频内容/视频切片/脚本/batch_clip.py +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/batch_clip.py @@ -7,6 +7,7 @@ import argparse import json import os +import re import subprocess import sys from pathlib import Path @@ -59,9 +60,20 @@ def _is_mostly_chinese(text: str) -> bool: return chinese / max(1, len(text.strip())) > 0.3 +def _title_no_slash(s: str) -> str: + """标题去杠::|、—、/ 等替换为空格,与 soul_enhance 一致""" + if not s: + return s + s = str(s).strip() + for c in "::||—--/、": + s = s.replace(c, " ") + s = re.sub(r"\s+", " ", s).strip() + return s + + def sanitize_filename(name: str, max_length: int = 50, chinese_only: bool = True) -> str: - """清理文件名,统一简体中文;若含英文则仅保留中文部分""" - name = _to_simplified(str(name)) + """清理文件名,先标题去杠,再仅保留中文、空格、_-""" + name = _title_no_slash(name) or _to_simplified(str(name)) safe_chars = [] for c in name: if c in " _-" or "\u4e00" <= c <= "\u9fff": diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/identify_highlights.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/identify_highlights.py index b532e2b3..27c949d7 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/脚本/identify_highlights.py +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/identify_highlights.py @@ -2,8 +2,8 @@ # -*- coding: utf-8 -*- """ 高光识别 - AI 分析视频文字稿,输出高光片段 JSON -级联:Ollama(卡若AI本地) → 规则备用 -只用已有能力,不依赖 Gemini/Groq +级联:API 优先(当前可用最佳模型)→ Ollama 本地 → 规则备用 +API 使用 OPENAI_API_BASE/KEY/MODEL 或 OPENAI_API_BASES/KEYS/MODELS(逗号分隔)故障切换。 """ import argparse import json @@ -17,6 +17,8 @@ DEFAULT_CTA = "关注我,每天学一招私域干货" CLIP_COUNT = 15 MIN_DURATION = 60 # 最少 1 分钟 MAX_DURATION = 300 # 最多 5 分钟 +# API 默认模型:优先用当前可用最佳(可被 OPENAI_MODEL / OPENAI_MODELS 覆盖) +DEFAULT_API_MODEL = "gpt-4o" def parse_srt_segments(srt_path: str) -> list: @@ -250,6 +252,62 @@ def _ensure_chinese_highlights(data: list) -> list: OLLAMA_MODELS = ["qwen2.5:3b", "qwen2.5:1.5b"] # 优先 3b,能力更强 +def _split_csv(s: str) -> list: + return [x.strip() for x in (s or "").split(",") if x.strip()] + + +def _build_api_provider_queue() -> list: + """ + 构建 API 接口队列:OPENAI_API_BASES/KEYS/MODELS 或单接口 OPENAI_API_BASE/KEY/MODEL。 + 返回 [{"base_url", "api_key", "model"}, ...],无配置时返回空列表。 + """ + bases = _split_csv(os.environ.get("OPENAI_API_BASES", "")) + keys = _split_csv(os.environ.get("OPENAI_API_KEYS", "")) + models = _split_csv(os.environ.get("OPENAI_MODELS", "")) + single_base = (os.environ.get("OPENAI_API_BASE") or "https://api.openai.com/v1").strip() + single_key = (os.environ.get("OPENAI_API_KEY") or "").strip() + single_model = (os.environ.get("OPENAI_MODEL") or DEFAULT_API_MODEL).strip() or DEFAULT_API_MODEL + queue = [] + if bases: + for i, b in enumerate(bases): + key = keys[i] if i < len(keys) and keys[i] else single_key + model = models[i] if i < len(models) and models[i] else single_model + if b and key: + queue.append({"base_url": b.rstrip("/"), "api_key": key, "model": model}) + elif single_key: + queue.append({"base_url": single_base.rstrip("/"), "api_key": single_key, "model": single_model}) + return queue + + +def call_openai_api(transcript: str, clip_count: int, provider: dict) -> str: + """调用 OpenAI 兼容 API(Chat Completion),使用指定 base_url / api_key / model。""" + try: + from openai import OpenAI + except ImportError: + raise RuntimeError("未安装 openai 库,请执行: pip install openai") + 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 内容。" + ) + client = OpenAI(api_key=provider["api_key"], base_url=provider["base_url"]) + resp = client.chat.completions.create( + model=provider["model"], + messages=[ + {"role": "system", "content": system}, + {"role": "user", "content": prompt}, + ], + temperature=0.2, + max_tokens=8192, + ) + content = (resp.choices[0].message.content or "").strip() + if not content: + raise RuntimeError("API 返回空内容") + return content + + def call_ollama(transcript: str, clip_count: int = CLIP_COUNT, model: str = "qwen2.5:3b") -> str: """调用卡若AI本地模型(Ollama),使用 chat 接口避免对话式误判""" import requests @@ -301,23 +359,47 @@ def main(): if len(text) < 100: print("❌ 文字稿过短,请检查 SRT 格式", file=sys.stderr) sys.exit(1) - # 级联:Ollama 3b → 1.5b → 规则备用(--require-ai 时不用规则) + # 级联:API 优先(当前可用最佳模型)→ Ollama → 规则备用(--require-ai 时不用规则) data = None raw = "" - for model in OLLAMA_MODELS: + api_queue = _build_api_provider_queue() + for provider in api_queue: try: - print(f"正在调用 Ollama {model} 分析高光片段...") - raw = call_ollama(text, args.clips, model) + print(f"正在调用 API {provider.get('model', '?')} 分析高光片段...") + raw = call_openai_api(text, args.clips, provider) if not raw: - raise ValueError("模型返回空") + raise ValueError("API 返回空") data = _parse_ai_json(raw) if data and isinstance(data, list) and len(data) > 0: - print(f" ✓ {model} 成功,识别 {len(data)} 段") + print(f" ✓ API ({provider.get('model', '?')}) 成功,识别 {len(data)} 段") break except Exception as e: - print(f" {model} 失败: {e}", file=sys.stderr) + print(f" API ({provider.get('model', '?')}) 失败: {e}", file=sys.stderr) if raw: print(f" 返回预览: {str(raw)[:400]}...", file=sys.stderr) + data = None + if (not data or not isinstance(data, list)) and not api_queue: + pass # 未配置 API,继续尝试 Ollama + elif data and isinstance(data, list) and len(data) > 0: + pass # API 已成功,保持 data + else: + data = None + if not data or not isinstance(data, list): + for model in OLLAMA_MODELS: + try: + 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" {model} 失败: {e}", file=sys.stderr) + if raw: + print(f" 返回预览: {str(raw)[:400]}...", file=sys.stderr) + data = None if not data or not isinstance(data, list): if getattr(args, "require_ai", False): print("❌ 必须用 AI 识别,当前无可用模型或解析失败", file=sys.stderr) diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py index 4a89ecf4..4224b59a 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py @@ -183,14 +183,26 @@ def draw_text_with_outline(draw, pos, text, font, color, outline_color, outline_ # 主体 draw.text((x, y), text, font=font, fill=color) +def _normalize_title_for_display(title: str) -> str: + """标题去杠、更清晰:将 :|、—、/ 等替换为空格""" + if not title: + return "" + s = _to_simplified(str(title).strip()) + for char in "::||—--/、": + s = s.replace(char, " ") + s = re.sub(r"\s+", " ", s).strip() + return s + + def sanitize_filename(name: str, max_length: int = 50) -> str: - """成片文件名:仅保留中文、空格、_-,与 batch_clip 一致""" - name = _to_simplified(str(name)) + """成片文件名:先标题去杠,再仅保留中文、空格、_-""" + name = _normalize_title_for_display(name) or _to_simplified(str(name)) safe = [] for c in name: if c in " _-" or "\u4e00" <= c <= "\u9fff": safe.append(c) result = "".join(safe).strip() + result = re.sub(r"\s+", " ", result).strip() if len(result) > max_length: result = result[:max_length] return result.strip(" _-") or "片段" @@ -416,9 +428,19 @@ def _draw_vertical_gradient(draw, width, height, top_rgb, bottom_rgb, alpha=255) draw.rectangle([0, y, width, y + 1], fill=(r, g, b, alpha)) +def _strip_cover_number_prefix(text): + """封面标题不显示序号:去掉开头的 1. 2. 01、切片1、123 等""" + if not text: + return text + text = re.sub(r'^\s*切片\s*\d+\s*[\.\s、::]*\s*', '', text) + text = re.sub(r'^\s*\d+[\.\s、::]*\s*', '', text) + return text.strip() + + def create_cover_image(hook_text, width, height, output_path, video_path=None): - """创建封面贴片。竖屏 498x1080 时:高级渐变背景、文字严格在界面内居中不超出、左上角 Soul logo。""" + """创建封面贴片。竖屏 498x1080 时:高级渐变背景、文字严格在界面内居中不超出、左上角 Soul logo;封面不显示 123 等序号。""" hook_text = _to_simplified(str(hook_text or "").strip()) + hook_text = _strip_cover_number_prefix(hook_text) if not hook_text: hook_text = "精彩切片" style = STYLE['cover'] @@ -725,12 +747,13 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa print(f" 分辨率: {width}x{height}, 时长: {duration:.1f}秒") - # 前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: + # 封面与成片文件名统一:都用主题 title(去杠),名字与标题一致、无杠更清晰 + raw_title = highlight_info.get('title') or highlight_info.get('hook_3sec') or '' + if not raw_title and clip_path: m = re.search(r'\d+[_\s]+(.+?)(?:_enhanced)?\.mp4$', os.path.basename(clip_path)) if m: - hook_text = m.group(1).strip() + raw_title = m.group(1).strip() + hook_text = _normalize_title_for_display(raw_title) or raw_title or '精彩切片' cover_duration = STYLE['cover']['duration'] # 竖屏成片:封面/字幕按 498x1080 做,叠在裁切区域,文字与字幕在竖屏上完整且居中 diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index ca1d6108..abd80e11 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -215,3 +215,4 @@ | 2026-03-03 05:02:46 | 🔄 卡若AI 同步 2026-03-03 05:02 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | | 2026-03-03 10:15:48 | 🔄 卡若AI 同步 2026-03-03 10:15 | 更新:水桥平台对接、卡木、火炬、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 14 个 | | 2026-03-03 10:20:17 | 🔄 卡若AI 同步 2026-03-03 10:20 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | +| 2026-03-03 12:01:38 | 🔄 卡若AI 同步 2026-03-03 12:01 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 39750722..5195e013 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -218,3 +218,4 @@ | 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) | | 2026-03-03 10:15:48 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-03 10:15 | 更新:水桥平台对接、卡木、火炬、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-03 10:20:17 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-03 10:20 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-03-03 12:01:38 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-03 12:01 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |