万象-数字世界
把「数字世界里的视频」双向打通:进来的看得懂,出去的发得出。
- A 篇 · 理解线(看进来):一条视频 → 可核验的结构化理解(本地听 + 云端看 + 按时间轴合并)
- B 篇 · 成片线(做出去):一段文案 → 可直接发布的竖屏成片(配图/配音/字幕卡/镜头运动/响度归一)
两条线共用一套底座:本机 Python 环境、ffmpeg 静态二进制、以及同一份 scripts/。
总路由:先判你要哪一头
| 你手上的东西 | 你要的结果 | 去哪 |
|---|---|---|
| 一条视频(文件/链接/视频号) | 它讲了什么、画面是什么 | A 篇 · 理解线 |
| 一段文案 | 一条能直接发出去的竖屏 mp4 | B 篇 · 成片线 |
| 一条视频,看完还要改写再发 | 先理解、后成片 | A → B |
| 只要逐字稿/字幕 | 转写文本或 srt | A 篇 transcribe.py/B 篇字幕导出 |
| 只有图片、不是视频 | 图片处理 | 都不是,属图像类任务 |
最容易串的一处:A 篇的「抽关键帧」是为了看懂(给人工核验画面);B 篇的「配图」是为了成片(生成新画面)。同名不同事,别互相套用。
执行纪律(两篇通用,先读)
> 来源:2026-09-20 吸收自视频号博主「晓辉博士」的 CodeBuddy 用法分享
> —— 她的「深度研究」Skill 强调全过程可见、可随时纠偏。
阶段汇报:长任务不许闷头跑到最后。
工具调用对用户是折叠的 —— 中途发生什么,用户看不见,只能等到最后一句"拿到了"。
在本机这种"失败重试是常态"的环境里,这会变成失控感:用户想喊停,都不知道该不该喊。
| 规矩 | 具体做法 |
|---|---|
| 分段报 | 超过 3 个阶段的活,每过一阶段报一句(定位到了 / 密钥没拿到 / 正在解密 / 转写完成 41 秒),不必等结果 |
| 卡住就喊 | 同一处连续卡 2 轮,立刻说明:卡在哪、已排除什么、下一步试什么 |
| 失败也报 | 不要说"稍等"然后沉默;宁可报一条"这次没通,我换 X 再试" |
| 不报也算报 | 前三个阶段若都顺利,可以合并成一句,但不能一路静默到交付 |
| 🔴 动桌面之前先问 | 要驱动微信 / 录屏 / 抢前台焦点之前,先停下确认用户此刻不在用电脑(本机实测:向日葵远程接管期间 60 余次点击全部落空,白跑半小时) |
> 实例(本机):2026-09-20 读视频号卡片时,60 余次点击失败全部静默,
> 用户直到最后才看到结果。若中途说一句"卡片点不动",
> 用户会立刻告知向日葵正在远程接管屏幕—— 可省半小时。
第 A 篇 · 看懂一条视频(理解线)
> 目标:把一条视频变成可核验的结构化理解 —— 有原帧、有时间轴,能被人复核,不是「我说它讲了什么」。
把「一段视频」变成「可核验的结构化理解」。两条腿走路:
- 听:ffmpeg 抽音频 → 本地 faster-whisper 转写(零云端、零 API Key、资料不出本机)
- 看:ffmpeg 抽关键帧 → Agent 读图(理解画面、OCR 画面字幕)
- 合:两路结果按时间轴对齐 → 摘要 + 报告(附原帧,可人工核验)
能力边界(先说清楚,不要承诺做不到的事)
| 输入 | 听(语音) | 看(画面) |
|---|---|---|
| 本地视频文件 mp4/mkv/mov/flv… | ✅ | ✅ |
| 网络视频链接(B站/抖音/YouTube/微博/小红书) | ✅ | ✅ |
| 微信视频号(拿到 mp4 后) | ✅ | ✅ |
| 纯图片型视频(无音轨) | — | ✅ |
| 无语音、无字幕的视频 | 只能给静音/无内容结论,不要编内容 |
视频号怎么拿到 mp4(2026-09-19 起,五条路线)
> 旧版这里写的是「视频号是硬限制,yt-dlp 不支持」——已作废。
> yt-dlp-patch 补上了视频号提取器,现在是有路的,只是取链接要用户配合一步。
> 2026-09-19 深夜新增 E 路线:全程零用户操作、无损拿到原始 mp4(含音轨),已端到端跑通。
| 路线 | 做法 | 前提 | 状态 |
|---|---|---|---|
| E 进程内存直取+解密 ★★ | wxchannels_pull.py:触摸注入驱动微信 UI → 播放卡片 → 扫 Weixin.exe/WeChatAppEx.exe 内存取「带 token 的 CDN 直链 + decode_key」→ 官方 wasm_video_decode 解密 → 得到原始 mp4 |
只需微信在运行;我用合成触摸点击,用户零操作 | ✅ 2026-09-19 打通:v1(56.7MB/9:15)已解出合法 mp4,encoder=Wxmm_9020230808,HEVC 720×1280 + AAC 立体声,ffmpeg 校验通过。这是无损、无录制损耗、不看屏幕的唯一路线 |
| A 链接直解 | channels_fetch.py <链接> <目录>,底层 yt-dlp + yt-dlp-patch 的 wechat_channel |
用户复制出链接(weixin.qq.com/sph/… 或 channels.weixin.qq.com/…)。手机端分享面板里有「复制链接」,PC 端正测无此入口 |
✅ 路由已实测;部分视频需 cookie |
| B 电脑端缓存 | 从 msg\video\ 捞刚播过的 mp4 |
用户点播放 | ❌ 2026-09-19 实测证伪:播放期间 45 分钟内 msg\video、cache、WMPF、temp 零写入。别再让用户「播一遍试试」浪费时间(见坑 15) |
| C 同源还原 | 卡片封面 OCR 出标题 → 公网检索博主原文 | 无 | ✅ 实测有效,无需用户操作;但拿到的是同源文字,不等于视频转写 |
| D 屏幕录制(画面+声音) | screen_record_av.py <out.ts> --seconds N:gdigrab 录画面 + WASAPI loopback 录系统声,自动合轨成 mp4。纯画面用 screen_record.py;只要关键页用 screen_capture.py |
用户点播放并保持窗口在前 | ✅ 2026-09-19 打通:loopback 自验 peak=0.2000 / rms=0.1223 / 72.4% 有声样本。E 路线走不通(如微信版本升级封掉内存可读)时的第一备选 |
路线选择的判据:视频是不是「画面即内容」(PPT 录屏 / 图表演示 / 带硬字幕)?
- 是 → D 路线够用,用户零学习成本。
- 否(纯口播、讲经验)→ D 路线(带声音版)同样能打,因为 loopback 已打通;
只有在「录制环境拿不到声音」(用户用别的输出设备、或不能外放)时才必须走 A(手机取链接)。
> 🔴 检查点 · 开始 E 路线之前(这条路线会接管你的屏幕):动手前逐条确认,缺一条这次取证就不算数:
> ① 用户此刻不在用电脑(本机实测:远程接管期间点击全部落空);
> ② 微信客户端正在运行(先查进程,别猜 —— 曾出现"驱动到一半微信被关",链路直接断);
> ③ 扫完内存必须立刻 close_player()(播放器开着会自动续播,把无关视频灌进内存,
> 污染下一次取证:实测混入过 21:17/29:27/240MB/252MB 的无关续播视频)。
E 路线的四个必知点:
1. ★★ 通道按「区域」分流,不是"一刀切屏蔽鼠标"(2026-09-20 实测推翻旧结论):
- Qt 自绘区(左栏头像/图标、会话列表、搜索框)→ 合成触摸与合成鼠标都能点。
- 聊天内容区(卡片、消息,由 MMUIRenderSubWindowHW 承载)→ 只认鼠标:
mouse_event(绝对坐标 MOVE|ABSOLUTE + LEFTDOWN/UP)/SendInput/PostMessage(WM_LBUTTONDOWN) 均有效,
而 InjectSyntheticPointerInput 触摸像素差恒为 0。
- 实测数据(点卡片播放键,全屏像素差):touch=0 ❌ / mouse_event=68901 ✅ /
SendInput=44844 ✅ / PostMessage 主窗=45226 ✅ / PostMessage 渲染子窗=1223 / SendMessage 渲染子窗=1628。
- ⇒ 结论:点卡片/播放键一律用鼠标通道;wxd.py 已加 mouse_tap()。
旧结论"微信屏蔽合成鼠标"是窗口被外部改动成 576×448、坐标全偏那段时期得出的误判,勿再引用。
- 触摸标誌位仍需写对(若确要走触摸):POINTER_FLAG_DOWN=0x00010000、UP=0x00040000(写 0x1/0x4 会返回 False 且看不出原因)。
- 键盘一直有效(keybd_event / SendInput),KEYEVENTF_UNICODE 直送中文可绕输入法。
- 判据不要用"主窗像素差":播放器是独立顶层窗(Chrome_WidgetWin_0,属 WeChatAppEx.exe),
它打开时主窗像素本来就不变;判据应是"枚举到新顶层窗"或"窗口矩形变化"。
2. 绝不能在微信未置前时注入:合成输入按前台窗口投递,会让点击落到用户正在用的软件上。
必须 is_fg() 实测校验,失败就 AttachThreadInput 重试;仍失败直接抛异常中止。
3. 加密方案:Isaac64 PRNG + XOR,只加密前 131072 字节,其余明文;密钥流直接 next() 逐字节取,
不要自己 reverse 或换字节序(自实现的三处都踩过坑)。decode_key 与 url 是同批 API 响应配对,
旧 URL 的 token 会过期(→ HTTP 400),天然可用来筛掉历史残留配对。
4. 播放器会自动续播(用户 2026-09-19 明确提醒):一张卡读完立刻关闭再开下一张,
否则视频号自动播推荐流,会把无关视频的直链混进同一轮读取。
★ 关闭方法(2026-09-20 修正):Esc 关不掉(会续播)。播放器是独立顶层窗:
Chrome_WidgetWin_0,标题为 '微信',pid = WeChatAppEx.exe,GetWindow(hwnd, GW_OWNER)==0。
注意 wxd.py 的 ls() 只枚举 Qt51514*,看不到它——必须全量 EnumWindows 再按
Area>50000 且 hwnd!=主窗 且可见 过滤,然后 PostMessage(hwnd, WM_CLOSE, 0, 0) 即可干净关闭
(实测一次命中;备用退化=鼠标点其右上角 (rect.right-75, rect.top+30))。
实测播放器窗矩形 (429,165,1451,883),MSE 日志里的 "hwnd":1771946 正是它。
5. ★★ 头号坑:微信的「搜索聊天记录」是一个模态窗,会把主窗点成"死"的(2026-09-20 定位):
只要它的窗口还开着(哪怕被你的 SetForegroundWindow 挤到后面、看不见),
主窗的所有鼠标/触摸输入都不产生任何响应,表现和"注入失效"一模一样:
InjectSyntheticPointerInput 返回 ok=True, err=(0,0)、WindowFromPoint 命中主窗、
IsWindowEnabled=True、IsHungAppWindow=False —— 一切正常,就是点不动。
- 识别:Qt51514QWindowIcon 类、标题 搜索聊天记录、独立顶层窗(实测 (591,195,1327,843))。
用 GetLastActivePopup(主窗) 查不可靠(这里返回主窗自己)。
- 干净做法:全量枚举顶层窗 → 凡 cls 以 Qt51514 开头且 hwnd!=主窗 的,WM_CLOSE 掉,
再回读确认只剩 WxTrayIconMessageWindow(那个本来就不可见,可无视)。
- ⇒ 优先直接点会话列表行(会话已选中时无需导航,只需确认聊天区在底部)。
非走搜索不可时,按本节第 8 条的三个条件做,并在点完结果后立刻清理该模态窗。
6. URL 尾部有日志碎片,_fast.valid() 会误杀(2026-09-20 定位):
内存里取到的 MSE url 字段后面直接粘着 \"startIdx\":…、\"respHeaders\":… 等,
且夹杂 \x00 二进制噪声。判据 " " not in u and "..." not in u 拦不住,会判为无效。
正确做法:按 URL 合法字符集截断,遇到第一个非法字符即停:
ALLOWED = set("A-Za-z0-9-._~:/?#[]@!$&'()*+,;=%"),逐字符收集到首个非法字符为止再 rstrip("&")。
实测清洗后 6 条候选里 2 条是真视频,解密全部成功。
7. ❌ 已证伪的判据:<type>51</type> 不是「PC 端不可播放」的标志(2026-09-20 自纠)。
曾据内存中 <type>51</type> + <title>当前微信版本不支持展示该内容,请升级最新版</title>
判定某张卡不可播放,这个判据是错的。实测同一批内存里 <type>51</type> 出现
1161 次,与 finderFeed 记录数(1071)近乎 1:1 —— 它对所有视频号消息都出现。
而在同一批数据下,四张同样带该标记的卡在聊天区全部正常渲染、且都带播放按钮。
⇒ 判「能不能播放」只能看 UI 实际渲染(截图看缩略图+播放键),不要读 XML 猜。
该判据若再被引用,会直接把「能读的卡」误判成「读不到」,且看起来证据充分、极难察觉。
8. ★ 导航与滚动的分工(2026-09-20 实测,纠两个易犯的错):
- End/PgDn 滚的是「左侧会话列表」,不是聊天区(焦点在列表上)。
拿它「把聊天区滚到底」会把列表滚到几百条之外,看起来像列表"变成了通讯录"
(滚远处会话时预览行不加载,只剩名字+分隔线,极易误判成切到了通讯录)。
聊天区点开会话时本来就在底部,无需滚。
- 滚轮在左侧列表无效,但在「聊天内容区」有效(2026-09-20 补正):
把光标放到聊天区中部(如窗口内 (470, 500))再 wheel(),能正常滚动聊天区 ——
这是把「被顶部截断、看不到播放键」的卡片滚进视野的正确方法。
Home / End 对聊天区仍无效(它们作用于列表)。遇到列表被滚走,
别靠键盘/滚轮找回来 —— 直接走搜索导航(见下条)。
- 导航可以走搜索框,但要满足三条:① 点搜索框连点两次(首击常被"激活窗口"吃掉,
只点一次会表现为"输入完全进不去");② 结果坐标取「功能」区首项
(OFF_RESULT_FUNC = (186, 133),960×1020、place(940,20) 后实测);
③ 点完结果立刻 kill_modal_dialogs。
⚠️ 原值 (166, 335) 落在「聊天记录」区,会打开错误的会话(实测打开了无关群聊)——已修正。
- ★ 一次读多张卡:污染甄别与「归属判定」(2026-09-20 实测,推翻一条自设判据):
播放器会自动续播推荐流,扫内存会把无关视频的直链一起抓进来。同一轮实测抓到的污染物:
1280×720 / 21:17(54,403,855 字节,重复出现 7 次)、1280×720 / 29:27(109,245,619 字节)、
以及 240MB、252MB 各一条;抽帧确认其中一条是「一个视频带你彻底上手 WorkBuddy」,与卡片无关。
- ❌ 已证伪:「竖屏 = 本卡视频」不成立。129 卡的视频本身就是968×720横版合成
(上部海报条 + 下部操作录屏);而候选里那条1080×1920竖屏,内容是「四川观察」新闻
(「张伟同志任中共巴中市委书记」)—— 分辨率比例与归属毫无关系。
- ⇒ 判归属只有一个可靠办法:抽帧看内容(首帧应与卡片封面同源,再看中段是否延续同一主题)。
抽帧用 ffmpeg-ss <t> -frames:v 1,多帧拼一张网格图比对最省事。
- 下载侧过滤(省时省配额):先用Range: bytes=0-262143探Content-Range拿总大小,
跳过 > 80MB 的(本轮真卡片视频都在 0.5–9MB),再逐条解密 +ffmpeg -i读时长/分辨率。
- 扫内存很快(约 14–15 秒),下载可能几分钟;扫完立刻close_player(),别让续播继续扩大污染面。
- 某些卡没有口播(2026-09-20 实测 127 转写 0 段):该卡本身是「图文 + 打字字幕 + 配乐」。
此时改从画面取内容(逐帧读字幕),交付时明确标注「画面字幕识读,非音频转写」——两者证据强度不同。
A 路线的三个必知点:
1. 必须显式 plugins.load_all_plugins() —— pip 装的插件默认不会自动注册,
不调用就静默落到 generic 提取器,表现为「解析不到视频」(channels_fetch.py 已内置)。
2. URL 形态只认三种:weixin.qq.com/sph/<id>、channels.weixin.qq.com/...、finder.video.qq.com/...。
3. 页面无内嵌 JSON 时插件走腾讯元宝中转,需要含 tencent.com 的 cookie:
--cookies <netscape.txt> 或环境变量 YUANBAO_COOKIE_FILE。缺 cookie 时插件会明确报警,不是静默失败。
拿不到 mp4 时的兜底顺序:E(内存直取+解密,首选,零用户操作) → D(电脑录屏,画面+声音)→ A(手机复制链接)→ 手机录屏后发回 → C(同源文字)。
绝不能因为拿不到视频就编内容;此时应改走 C 路线,并明确标注「同源文字、非视频转写」。
依赖(本机已就绪)
| 组件 | 位置 | 说明 |
|---|---|---|
| ffmpeg 7.1 | default venv 的 imageio_ffmpeg 静态二进制 |
抽音频/抽帧/录屏 |
| faster-whisper 1.2.1 | default venv |
本地转写,GPU 已实测可用 |
| yt-dlp 2026.08.19 | default venv |
网络视频下载(1752 个站点) |
| CUDA 运行库 | default venv 的 nvidia-*-cu12 pip 包 |
GPU 必需,见坑 #7 |
| soundcard 0.4.6 | default venv |
WASAPI loopback 录系统声(D 路线录音必需,纯 ctypes 无编译) |
| 模型 | <私有配置 whisper_model_root>\faster-whisper-<size>\ |
用户指定装 D 盘(C 盘仅剩 ~21GB) |
模型位置与查找顺序
model_roots() 按序查找,命中即用:
- 环境变量
WHISPER_MODEL_DIR(覆盖为单一根目录) <私有配置 whisper_model_root>← 本机唯一位置(small 464MB + medium 1.53GB)<skill>/models/← 兜底,便于把技能整体拷到别的机器
(本机该目录已删除,原先误在 C 盘多下一份 small 占 464MB;技能包因此保持轻量)
prepare_model.py 的 default_dest() 同理:私有配置里的根目录存在就装进去,否则回落技能内 models/。
GPU 依赖安装(一次性)
PY -m pip install nvidia-cublas-cu12 nvidia-cudnn-cu12 -i https://mirrors.aliyun.com/pypi/simple/
装完约 +1.5GB。装完不代表能用 —— ctranslate2 的 wheel 不自带 CUDA 运行库,
且 pip 包把 DLL 放在 site-packages/nvidia/*/bin,不在 PATH 上。
transcribe.py 的 register_cuda_dlls() 会 os.add_dll_directory() 注册这些目录,
缺了这步仍会报 cublas64_12.dll not found。
Python 一律用:~\.workbuddy\binaries\python\envs\default\Scripts\python.exe
用法
PY="~/.workbuddy/binaries/python/envs/default/Scripts/python.exe"
SK="~/.workbuddy/skills/omni-digital-world/scripts"
# 0) 首次准备模型(默认装到私有配置 whisper_model_root)
# 本机已装好 small + medium,无需再下。large-v3 按需装(见「模型策略」表)。
# $PY "$SK/prepare_model.py" --size medium # 支持断点续传,中断后重跑会继续
# 1) 一条命令跑完:抽帧 + 抽音频 + 转写 → 产出 bundle
$PY "$SK/run.py" "<视频路径或链接>" "<工作目录>" --max-frames 24
# 默认 medium(准)。长视频初筛加 --model small(快 ~3 倍,用于摸结构)
# 1b) 只转写(不经抽帧):默认已带简体 initial_prompt
$PY "$SK/transcribe.py" "<音频或视频>" "<out.json>" --model medium --lang zh --device cuda
# --no-prompt 关闭简体纠偏(更快,但 medium 会出繁体);--prompt "..." 自定义
# 2) Agent 读 <工作目录>/frames/*.jpg 理解画面,把要点写成 summary.md
# 3) 生成报告
$PY "$SK/report.py" "<工作目录>" --summary "<summary.md>" --title "标题"
流水线分层(可单独调用)
| 脚本 | 职责 |
|---|---|
prepare_model.py |
把 whisper 模型下到本地目录(绕开 HF 缓存坑) |
frames.py |
probe 探测元信息 / frames 抽关键帧(uniform/scene,自动回退) |
audio.py |
抽 16kHz 单声道 WAV |
transcribe.py |
本地转写,含 GPU→CPU 自动回退 |
fetch.py |
网络视频下载(yt-dlp 自动安装) |
channels_fetch.py |
微信视频号下载:yt-dlp + yt-dlp-patch 的 wechat_channel 提取器(A 路线) |
wxd.py |
微信输入注入驱动器。tap()=触摸(Qt 自绘区可用)、mouse_tap()=鼠标(卡片/消息区必须用)、ls_all()=全量枚举顶层窗、kill_modal_dialogs()=清「搜索聊天记录」模态窗(不清则主窗点不动)、close_player()=WM_CLOSE 关播放器、place_strict()=尺寸校验式摆窗 |
wxchannels_pull.py |
★★ E 路线首选:注入驱动微信 UI(等用户空闲→接管→清模态窗→进会话→鼠标点卡片播放→取链接→关播放器),并调 wxchannels_mem.py 取链接。内含 clean_url() 修掉 URL 尾部日志碎片导致的误杀 |
wxchannels_mem.py |
E 路线取数:扫 Weixin.exe/WeChatAppEx.exe 内存,提取 (CDN 直链, decode_key) 配对;--grab 可直接下载+解密+ffprobe 校验 |
card_mse_fetch.py |
单卡端到端取片:clean_url 清洗 → 并发 Range 探测 → 只下 video/mp4 → wasm 解密 → ffprobe 校验时长。用于区分「正片」与「推荐流预加载片段」 |
inj_probe.py |
注入通道真值探针:自建纯 Win32 窗口,记录收到的 WM_POINTER*/WM_TOUCH/WM_LBUTTON*。用来判断"点不动"是系统级注入坏了、还是微信侧不接收——本轮定位就靠它 |
wxchannels_decode.js |
用官方 wasm_video_decode(WxIsaac64)解密前 131072 字节;配套 wasm_video_decode.js/.wasm |
harvest_cached_video.py |
从 msg\video\ 找出刚播过的 mp4(B 路线,已证伪,勿再当主路) |
screen_record_av.py |
★ D 路线首选:屏幕录制(画面+系统声)—— gdigrab + WASAPI loopback,先出 .ts 再合轨成 mp4 |
screen_record.py |
屏幕录制(仅画面):gdigrab → mp4 |
screen_capture.py |
屏幕取样:只留画面变化帧 + 拼版总览(PPT 录屏型最省空间) |
run.py |
总编排,产出 bundle.json |
report.py |
合成可核验 HTML 报告(内嵌关键帧 + 转写时间轴) |
视频号相关依赖(一次性)
$PIP install yt-dlp-patch -i https://mirrors.aliyun.com/pypi/simple/
装完插件落在 <venv>/Lib/site-packages/yt_dlp_plugins/extractor/(含 wechatchannel.py、douyin.py)。
验证插件生效(不看 --list-extractors,它不列插件):
import yt_dlp.plugins as P; P.load_all_plugins()
from yt_dlp.globals import plugin_ies
print(list(plugin_ies.value)) # 期望看到 WeChatChannelIE
实测踩过的坑(务必照做)
- HF 的 Xet 协议对国内镜像 401 → 必须
HF_HUB_DISABLE_XET=1(脚本已内置)。
否则报CAS Client Error: HTTP status client error (401 Unauthorized) ... xethub。 - Windows 下 HF 快照符号链接退化成 0 字节 → ctranslate2 报
File model.bin is incomplete: failed to read a value of size 4 at position 0。
根因是blobs/里文件完整、snapshots/里却是 0 字节。
解法:用prepare_model.py把模型下到本地目录,transcribe.py会优先用本地目录。 - 场景检测(
select=gt(scene,x))对连续运动/手持视频会全灭(无硬切镜头 → 一帧不出)。
frames.py已内置:scene 模式结果 <2 帧时自动回退 uniform,保证永不返回空。 - pip 默认源在国内很慢(~138kB/s);清华源报 403 → 用阿里源
-i https://mirrors.aliyun.com/pypi/simple/。 - 本机 bash 缺 coreutils(无
grep/tail/head/ls),输出须重定向到文件再用 Read 读。 - PowerShell
Add-Type被安全策略拦截(无法用 SAPI 造 TTS 测试音频)。 - GPU 会「假可用」,这是本项目最隐蔽的坑:静音音频被 VAD 全滤 → 根本不触发 GPU 计算
→ 看着「成功」;一遇到真实语音才暴露缺cublas64_12.dll/cudnn64_9.dll。
两道防线都要有:① 加载期回退(load_model);② 运行期回退(transcribe里
_do()包 try/except)——只做加载期兜底会在真语音上直接崩。
修复:装nvidia-cublas-cu12+nvidia-cudnn-cu12,并由register_cuda_dlls()注册 DLL 目录。 - VAD 默认开启:安静的纯环境音视频可能得到 0 段,这是正确结果,不是故障。
判别方法:算音频 RMS,低于 ~0.01 基本就是环境噪音(实测工地视频 RMS=0.008 → 0 段正确)。 - 大模型下载必须能断点续传,否则永远下不完(下 medium 1.4GB 时实测):
hf-mirror 会中途 stall,urlopen(timeout=120)在第 834MB 处抛
model.bin: The read operation timed out。
原始写法的致命错误是失败时os.remove(tmp)删掉.part→ 已下的 834MB 全丢,
重跑从头开始,在慢/抖的镜像上等于永远下不完。
已修:fetch_one()带Range: bytes=<have>-续传 + 最多 6 次退避重试 +
失败保留.part+Content-Length完整性校验;服务器不回 206 时自动从头下。 - 模型没下完时不要静默退回联网:
resolve_model()命中不到本地目录会返回规格名,
于是 ctranslate2 偷偷去 HF 重新下载(慢且可能再撞 Xet 401)。
已加检测:存在model.bin.part时往 stderr 明确告警,提示先跑prepare_model.py。 medium模型对中文会输出繁体(实测手機/專業/銀行/轉賬/駐地/景區,繁体字种数 26),
对大陆用户是明显可用性问题。已在transcribe()加简体initial_prompt引导:
"以下是普通话的句子,请使用简体中文转写。"(lang为 zh 时自动生效)。
实测效果:繁体字种数 26 → 0;顺带修正君子→军资、假带→甲带、速限→速献。
代价:输出会带标点、token 变多,耗时 70s → 96s(+37%)。
要关掉用--no-prompt;要自定义用--prompt "..."。- yt-dlp 插件不会自动注册:
pip install yt-dlp-patch之后,
不显式调plugins.load_all_plugins()就不生效,且--list-extractors也不列出插件
(用plugin_ies查才对)→ 极易误判成「yt-dlp 不支持视频号」。 - 脚本文件头写 Windows 路径要用
r"""原始 docstring:
msg\video\...、\xwechat_files里的\x会被当转义 →SyntaxError: unicodeescape ... truncated \xXX。 - 视频号页面无内嵌 JSON 时走腾讯元宝中转,缺 cookie 会直接失败并提示
未提供腾讯元宝 cookie——这是预期行为,不是脚本坏了;补--cookies即可。 - 电脑微信播视频号=完全不落盘(2026-09-19 实测,比"未证实"更强):
播放一条 2 分钟视频号期间,45 分钟内对msg\video、cache、WMPF、all_users、
temp全量扫描,只有用户的截图与头像缓存被写入,无任何视频/音频文件落盘。
⇒ B 路线作废。不要再让用户"播一遍试试",直接改走 A 或 D。 dshow录不到声音 ≠ 录不到系统声(2026-09-19 订正,原结论偏保守):
ffmpeg -f dshow -list_devices报Could not enumerate audio only devices,
真实含义是本机没有 DirectShow 音频采集滤镜(无麦克风、立体声混音未启用),
不代表系统没声音可录。WASAPI loopback 走渲染端点,完全绕得开:
soundcard(纯 ctypes,无需编译依赖)取
sc.get_microphone(sc.default_speaker().name, include_loopback=True)即可录系统输出。
自验(播 440Hz 正弦、同时 loopback 录):peak=0.2000 / rms=0.1223 / 72.4% 有声样本。
⚠️ loopback 抓的是默认渲染端点:用户若把播放设备切到别的声卡(如前面板耳机),就录不到。- PC 端视频号播放器没有「复制链接」入口(2026-09-19 用户实测):右上角只有
分享/更多,点开都不给链接。链接只能从手机端拿(手机分享面板里才有)。
另:屏幕录制脚本用gdigrab全屏抓,实测 1920×1080 稳定可用(ImageGrab同样可用)。 - ★ 后台任务会在"我这一轮回复发出"时被回收(两次录屏翻车的真正原因,别只看表象):
run_in_background起的长任务(180s、210s 各一次)都在启动后约 2 分钟、
也就是我发出回复的那一刻被杀。产物 mp4 只有 1.3~2.2MB 且moov atom not found,
plain / h264 / ignore_err 三种强行抽帧全部失败。
⇒ 凡是要跨越"我的回复"的长任务,一律前台跑(timeout提到 300000ms 级),
或者让用户自己在系统侧发起。不要用后台任务录屏。 - 录屏容器优先
.ts(MPEG-TS)而非.mp4:ts 是流式容器,即使进程被强行中断仍可解码;
mp4 中途被杀会缺 moov,整段报废。ffmpeg 一律带-t让其自行到点退出,不要从外部 kill。 -
★ 点击通道按「区域」分流 —— 20 号旧结论已于 2026-09-20 被实测推翻,以下为修订版:
旧结论(作废,勿再引用):"微信 4.x 屏蔽合成鼠标、只接受合成触摸"。
该结论是在窗口被外部(向日葵远程控制)改成 576×448、所有坐标整体偏移的那段时间得出的,
当时"触摸有效/鼠标无效"的差异其实是坐标巧合,不是通道差异。修订结论(2026-09-20 隔离实测):
① 先用自建纯 Win32 窗口做真值测试,确认合成触摸在系统层完全正常
(窗口收到WM_POINTERDOWN/WM_POINTERUP/WM_POINTERENTER及系统转换的WM_LBUTTONDOWN/UP)。
② 再对同一张卡片逐通道对照(点播放键,全屏像素差):手法 结果 InjectSyntheticPointerInput(触摸)❌ 0 —— 卡片区不认触摸 mouse_event(MOVE\|ABSOLUTE+ 左键)✅ 68901 SendInput(绝对坐标左键)✅ 44844 PostMessage(WM_LBUTTONDOWN/UP)→ 主窗✅ 45226 PostMessage→MMUIRenderSubWindowHW⚠️ 1223(弱) SendMessage→MMUIRenderSubWindowHW⚠️ 1628(弱) 同批对照:左栏头像(Qt 自绘) 触摸/鼠标/ SendInput/PostMessage四种全部 4315 ✅⇒ 规律:Qt 自绘区(左栏头像图标、会话列表、搜索框)触摸与鼠标都行;
聊天内容区(卡片、消息,由MMUIRenderSubWindowHW承载)只认鼠标。
点卡片/播放键一律用mouse_tap()(已加入wxd.py)。
⚠️ 触摸标誌位若要使用仍必须DOWN=0x00010000/UP=0x00040000(写0x1/0x4返回 False 且看不出原因);
CreateSyntheticPointerDevice的POINTER_FEEDBACK_MODE只能取0x1/0x2/0x3,传0报ERROR_INVALID_PARAMETER(87)。
✅ 保留有效的排除项:UIPI/完整性级别(双方都 Medium)确实不是原因。
21. ★ 注入前必须实测校验前台窗口,否则会点到用户正在用的软件上(2026-09-19 踩到):
合成输入按前台窗口投递。我当时在微信未置前时连续发了几十个点击/按键,
全部打进了用户正在操作的 D5 渲染器(其"选择文件夹"对话框还开着)。
正确做法:每次注入前GetForegroundWindow() == 微信hwnd;不等就先
AttachThreadInput(本线程, 前台线程, True)→SetForegroundWindow→ 解附 重试若干次;
仍失败就抛异常中止,绝不盲目点。SetForegroundWindow对后台进程会被 Windows 拒绝,
AttachThreadInput是解这个锁的标准手法;SwitchToThisWindow可作补充。
22. ★ 不要在用户正在用电脑时抢屏:窗口最小化时GetWindowRect返回(-32000,-32000,…)。
抢屏前先用GetLastInputInfo()等用户空闲 ≥2s(wait_idle),超时即放弃并告知用户,
而不是硬抢。这条既尊重用户,也保证注入不会落错窗口。
23. 视频号播放器会自动续播(用户 2026-09-19 明确提醒):一张卡读完立刻关闭再开下一张;
否则播完自动进推荐流,会把无关视频的直链混进同一次内存读取,污染结论。
★ 关闭方法(2026-09-20 修订,Esc与"点头部 ✕"都不可靠):
播放器 = 独立顶层窗Chrome_WidgetWin_0,标题'微信',pid 属WeChatAppEx.exe,
GW_OWNER=0;实测矩形(429,165,1451,883),MSE 日志里的"hwnd":1771946就是它。
①wxd.py的ls()只枚举Qt51514*,看不到它 → 必须全量EnumWindows;
② 过滤「可见 ∧ 面积>50000 ∧ hwnd≠主窗」,或直接用主窗矩形之外的点WindowFromPoint+GetAncestor(GA_ROOT)反查;
③PostMessage(hwnd, WM_CLOSE, 0, 0)一次命中(实测生效,全屏差 26238)。
备用退化=鼠标点其右上角(rect.right-75, rect.top+30)。
24. ★★ 头号坑:「搜索聊天记录」模态窗会把主窗点成"死的"(2026-09-20 定位,排查耗时最长):
只要该窗还开着(哪怕被SetForegroundWindow挤到后面、肉眼看不见),
主窗的所有鼠标/触摸输入都不产生任何响应。表现与"注入失效"完全一致:
InjectSyntheticPointerInput返回ok=True, err=(0,0)、WindowFromPoint命中主窗、
IsWindowEnabled=True、IsHungAppWindow=False、SendMessageTimeout(WM_NULL)有应答、
坐标与 DPI 全对 —— 一切指标正常,就是点不动。
- 识别特征:cls=Qt51514QWindowIcon、title='搜索聊天记录'、独立顶层窗(实测(591,195,1327,843))。
GetLastActivePopup(主窗)不可靠(这里返回主窗自己),别依赖它。
- 处置:全量枚举顶层窗 → 凡cls以Qt51514开头且hwnd≠主窗的WM_CLOSE全部清掉,
回读确认只剩WxTrayIconMessageWindow(本身不可见,可无视)。
- ⇒ 导航策略要改:别用搜索框(点了就会打开它)。直接点会话列表行;
若会话已是当前会话,根本不用导航,滚到底即可。
25. 微信主窗标题会变、WS_VISIBLE可能被外部清除(2026-09-20 实测):
主窗标题实测出现过'zp'(不是「微信」),且窗口可能被外部(远程控制/用户操作)最小化。
⇒ 选主窗必须按类名Qt51514QWindowIcon取面积最大者,不可依赖标题;
activate()要先判IsWindowVisible,不可见就ShowWindow(SW_RESTORE=9)再置前。
另:窗口尺寸可能被外部改成576×448之类 → 每次动作前用place_strict()循环校验目标尺寸,
不符就重试,否则坐标整体偏移、所有点击都会静默落空(这是本轮绕远路的总根源)。
26. 内存里取到的 URL 尾部粘着日志碎片,_fast.valid()会误杀(2026-09-20 定位):
MSE 的url字段后面直接跟着\"startIdx\":…、\"respHeaders\":…,还夹\x00二进制噪声。
判据" " not in u and "..." not in u拦不住。
正确做法:按 URL 合法字符集截断——逐字符收集到第一个非法字符即停,再rstrip("&"):
ALLOWED = set("A-Za-z0-9-._~:/?#[]@!$&'()*+,;=%")。
实测清洗后 6 条候选 → 2 条真video/mp4(47.51MB / 8:26.85 与 1.42MB / 0:31.62),解密全部成功。
⚠️ 同一轮里会同时出现多条流:正片与推荐预加载片段混在一起,靠ffprobe时长区分
(本次 8:26.85 才是正片,0:31.62 是预热片段)。
★Esc关不掉视频号播放器(实测:Esc 后仍在播,且已自动续播到下一条推荐视频)。
必须点播放器头部最右侧的关闭按钮:把窗口固定在SetWindowPos(940,20,960,1020)后,
窗口内偏移(885, 55)即可关掉((795,55)是"不感兴趣",别点错)。
关闭判据:窗口右侧中部区域平均色由视频画面色 → 变回浅灰(221,223,225)。
另外:窗口打开播放器后会变宽(实测 896→965,右边缘 2127 超出 1920 屏),
关闭按钮落到屏幕外点不到 ⇒ 驱动前先用SetWindowPos把窗口固定到屏内固定矩形。
24. 旧 URL 的 token 会过期,这反而是筛配对的好判据:内存里常同时残留历史配对的
stodownload直链,重新下载会得到 HTTP 400。因此「播放后扫一遍内存 → 逐条试下载 →
只有新的那条能下」即可自动锁定目标视频,不必先建基线再比对。
实测性能基线(本机 RTX 2080 SUPER 8G + 8 核 CPU,113 秒中文解说)
| 模型 | 设备 | 耗时 | 相对实时 | GPU 提速 |
|---|---|---|---|---|
small |
CPU int8 | 35.7s | 3.2× | — |
small |
GPU float16 | 23.3s | 4.8× | 1.53× |
medium |
CPU int8 | 95.3s | 1.2× | — |
medium |
GPU float16 | 70.0s | 1.6× | 1.36× |
medium |
GPU float16 +简体prompt(默认) | 96.2s | 1.2× | 见坑 #11 |
> ⚠️ 注意:上表 medium 的 70.0s 是未加简体 prompt 的旧值。简体纠偏是当前默认行为,
> 实际默认耗时是 96.2s。要跑得快就 --no-prompt(但会出繁体)。
⚠️ 一条被自己实测否证的推断,必须记下来:曾推测「模型越大,GPU 收益越显著」——
实测相反:small 提速 1.53×,medium 反而只有 1.36×。
原因推断(未单独验证):本机 8 核 CPU 的 int8 量化效率很高,而 RTX 2080 SUPER 属 Turing 架构,
float16 加速有限;ctranslate2 在 CPU 侧的开销摊薄了差距。
结论:不要把 GPU 当作大模型的性能解药,它只是稳定省下约 1/3 时间。
- GPU 与 CPU 结果段数/字数完全一致,仅 1 个同音字差异(float16 vs int8 数值差)→ 精度无实质损失。
- 抽帧:20 帧(uniform)秒级完成;模型加载与音频抽帧是主要固定开销。
模型策略(用户定档 2026-09-19,三档固定)
| 档位 | 模型 | 定位 | 何时用 | 本机状态 |
|---|---|---|---|---|
| 初筛 | small |
快 | 长视频先摸结构、只需要大意 | ✅ 已装 464MB |
| 默认 | medium |
准 | 不特别说明时一律用这档(脚本默认值已改为 medium) | ✅ 已装 1.53GB |
| 精修 | large-v3 |
最准 | 需要的时候才装,平时不预装 | ⬜ 未装(约 3.1GB) |
- 默认值已落在代码里:
run.py/transcribe.py不带--model即为 medium,无需每次手写。
实测确认(22s 片断、不带--model):model=medium / device=cuda / ct=float16,10.1s 出稿,简体纠偏正常(繁体命中 0)。 - 初筛提速:
--model small(实测同样 113s 音频:small 23.3s vs medium 70s,快约 3 倍)。 - large-v3 按需安装(想用再装,别提前占盘):
bash $PY "$SK/prepare_model.py" --size large-v3 # 装到私有配置 whisper_model_root
装完直接--model large-v3即可,查找链会自动命中;支持断点续传,可随时中断重跑。 - ⚠️
large-v3本机尚未实测(耗时/显存占用/中文质量均无数据)。
在真跑过一次之前,不得对外声称它更快或更准——只能说"规格上更大"。
GPU 收益不是随模型变大而上升(small 1.53× > medium 1.36× 已实测反例),
large-v3 在 8G 显存的 2080S 上更可能受显存限制,首次使用请先留意是否回落 CPU。
质量纪律
- 转写与读图都可能有误差 → 报告必须附原帧 + 转写时间轴供人工核验。
- 视频无语音、画面无可读信息时,明确说「无内容」,绝不脑补。
- 长视频先用
small摸清结构,定稿再换medium/large-v3精修。 - 转写文本可能含真实人名/甲方/证件号 → 参照用户脱敏红线,产出前扫描并提示。
第 B 篇 · 把文案做成竖屏成片(成片线)
> 目标产物:1080×1920 / 30fps / 带配音与字幕的 mp4,并附字幕 srt 与可二次剪辑的素材包。
> 与本篇外的关系:ImageGen 负责出图,ffmpeg 负责成片;两者都是本地调用,不上传素材。
一条可复制的 6 步流水线。目标产物:1080×1920 / 30fps / 带配音与字幕的 mp4,
并附字幕 srt 与可二次剪辑的素材包。
0. 本机硬约束(先读,能省掉一半返工)
| 事实 | 应对 |
|---|---|
| 本机没有 ffmpeg | pip install imageio-ffmpeg,用 imageio_ffmpeg.get_ffmpeg_exe() 拿静态二进制(7.1 版自带 libx264 / aac / drawtext / libass / zoompan / xfade,够用) |
| Bash 工具不可用(PortableGit shim 缺 dirname/head/grep) | 一律走 PowerShell |
| PowerShell 工具的 stdout 常被吞掉 | 让 Python 脚本自己写 UTF-8 日志文件,再用 Read 读 |
Start-Process 被安全策略拦截 |
用 & $py script.py;需要 tee 就在 Python 里用 runpy + 自定义 Tee 落盘(见 scripts/run_build.py) |
Bash 里 cmd /c 也被禁 |
用原生 PowerShell 语法 |
| 中文字体可用 | msyhbd.ttc(粗) / msyhl.ttc(细) / Deng.ttf(等线) |
| 图像生成工具会在右下角烧水印 | 见第 2 步,必须裁掉 |
1. 写文案 + 分镜(JSON 驱动)
先落一份 script/xxx_script.json,每段包含:
{ "id": "s1", "label": "开场 · 钩子",
"narration": "……(会被 TTS 逐字读出,也是字幕的唯一来源)",
"image_prompt": "……(实拍风格描述,结尾固定加『竖构图,主体居中,上下留白充足』)",
"duration_hint": 11 }
要点
- 段数 8–10、每段旁白 20–40 字,成片约 2 分钟。太短撑不起科普,太长完播率掉。
- image_prompt 必须统一色调(如「暖白 5600K + 奶油黄 + 原木棕」),否则九张图拼起来像九个片子。
- 提示词结尾固定加 「主体居中,上下留白充足」 —— 后面要按 9:16 裁两侧,留白是对裁剪的保险。
- 字幕不要另写一份。字幕直接由 narration 自动断句生成,避免「配音念了但字幕没写」的错位。
2. 生成配图并去水印(必做)
先 ImageGen 逐段出图(size: 1024x1536,quality: high)。
图像工具会在 1024×1536 画布的 y=1483..1518、右下角烧入「AI生成 WORKBUDDY」水印。
原图裁掉底部 66px(保留 1470)即可完全去除,构图无损。完成后务必复检:
拼一条「干净图底部 110px」的对照条带肉眼确认(scripts/dewatermark.py 会生成)。
> 别用边缘密度/亮度阈值做自动水印检测 —— 木纹、大理石纹、窗帘高光会大量误判,
> 直接实测固定位置裁切更可靠。
3. 分场景配音 + 时长测量
edge-tts(zh-CN-XiaoxiaoNeural,rate="+6%"),每段一个 mp3。
用 ffmpeg 解析 Duration: 拿每段真实时长(不要按字数估算,中文语速波动很大)。
4. 字幕卡:用 Pillow 预渲染,不要用 drawtext
每张卡 = 一张 1080×1920 透明 PNG:墨绿圆角底卡 + 左侧砂金竖条 + 白字 + 数字/关键词砂金高亮。
再在 ffmpeg 里 overlay=0:0:enable='between(t,a,b)' + fade=t=in:st=a:d=.22:alpha=1。
要点
- 断句:按 ,。!?;: 切从句 → 合并到 ≤24 字 → 贪心折行,保证每行 ≤14 字
(14 字 × 60px ≈ 840px,加内边距 932px < 1080 安全)。
- 折行优先在标点后断,否则硬断。
- 再做一层自适应字号:超出安全边距就 −2px 重排。
- 卡片底边放在 y=1400:抖音/视频号底部约 400px 被 UI 遮挡,字幕压在 1400 以内才不被盖。
- 页眉(左上角色块 + 03 / 09 + 章节名)单独一张 PNG,整段常驻。
5. 镜头运动 + 转场
关键:先裁成 9:16,再进 zoompan,否则 zoompan 会把非等比方图拉伸变形。
[0:v]scale=-2:2160:flags=lanczos,crop=1216:2160:(iw-1216)/2:0,setsar=1,
zoompan=z='1.02+(1.17-1.02)*on/N':x='(iw-iw/zoom)*(0.5)':y='(ih-ih/zoom)*(0.34+0.18*on/N)':
d=1:s=1080x1920:fps=30
z/x/y用on做线性插值(比zoom+0.001累加更可控,能精确控制起止)。d=1+ 输入-loop 1 -framerate 30 -t L:每输入帧出 1 帧,zoom状态跨帧连续,效率最高。- 逐段换推/拉方向和位移方向(9 段一套参数表),否则九段都是一个味儿。
- 段间转场
xfade=transition=fade:duration=0.5。 - 单段时长
L = 0.45(前导) + 旁白时长 + 0.85(尾部),末段尾部给 1.7s 收住。
6. 音频对位(xfade 下的时间轴算法)
xfade 链式拼接时,第 i 段在总时间轴上的起点不是 ΣL,而是:
start_i = Σ(L_k, k<i) − i × T # T = 过渡时长(0.5s)
总长 V = ΣL − (n−1) × T
旁白按 adelay=<ms>|<ms> + amix=inputs=n:normalize=0 放到各自起点,
再 apad,atrim=0:V 对齐总长。这套比 concat 更可控,且天然处理了过渡期的音频交叠。
响度:人声直混通常只有 −19 ~ −21 LUFS,手机上明显偏轻。
必须做 EBU R128 两遍法 loudnorm 归一到 I=-15 LUFS / TP=-1.5 dBTP:
第一遍:-af loudnorm=I=-15:TP=-1.5:LRA=11:print_format=json -f null -
第二遍:-af loudnorm=I=-15:TP=-1.5:LRA=11:measured_I=..:measured_LRA=..:measured_TP=..:measured_thresh=..:offset=..:linear=true
最后 -c:v copy 直接封装,不再重编码画面。
7. 交付与自检
- 成片:
output/*.mp4 - 字幕:标准
srt(时间码按第 6 步的绝对起点换算) - 素材包:
01_图片 / 02_配音 / 03_字幕 / 分镜表.csv,可直接拖进剪映二次剪辑 - 自检:抽 16 帧拼 contact sheet 肉眼过一遍(
scripts/qc_check.py),
并用ffmpeg -i核对分辨率 / fps / 时长 / 音轨。
踩过的坑(务必避开)
- 字幕时间轴把
len(cue)当字符串长度用 —— 但 cue 是[[line,…],…]结构,
len()返回的是行数。结果:时间轴被拉满,每段只有第一张字幕卡生效、
其余卡因t0 > t1被between()静默丢弃。统计一律用len("".join(lines))。 scale=W:H写死两组数会拉伸画面 —— 用scale=-2:2160让高度定、宽度自适应。- 字幕卡溢出:折行没做硬约束时会出现 17 字一行撑破卡片,必须「贪心折行 + 自适应字号」双保险。
- 别把成片旁白写死在字幕里:字幕由 narration 自动切,改稿后两边一起变。
附:脚本
scripts/ 下是本篇(B 篇 · 成片线)的可用实现,工作根目录由环境变量 OMNI_VIDEO_ROOT 指定(不设则取「当前目录/egg-video」),换项目只需设这一个变量:
| 文件 | 作用 |
|---|---|
run_build.py |
运行入口:把 build 的输出以 UTF-8 落盘(绕开 PowerShell stdout 吞输出) |
step1_prepare.py |
图片归位映射 + edge-tts 配音 + 时长测量 |
dewatermark.py |
裁掉底部 66px 去水印,生成 images_clean/ 并出自检条带 |
build_video.py |
主流程:字幕卡/页眉 PNG → 逐段镜头 → xfade 拼接 → BGM 合成 → 混音 → 响度归一 → 封装 → 导出 srt 与素材包 |
fix_audio.py |
只重做响度归一与封装(画面已渲好时用,省掉 5 分钟重渲) |
qc_check.py |
抽 16 帧拼预览图 + 打印成片参数 |
附 · 融合说明与位次依据
融合性质:视频域两源融合(多部并成一部),不是吸收。
| 项 | 内容 |
|---|---|
| 产物 | omni-digital-world(显示名 万象-数字世界) |
| 宿主 | 万象-视频-理解(资产最全:12 脚本 + 完整工具链文档),零拷贝改造其原目录 |
| 成员 | omni-video-vertical(万象-视频-竖屏成片,6 脚本移入同一 scripts/) |
| 篇界 | A 篇 = 理解线(一产一消中的「消」);B 篇 = 成片线(「产」)。判据边界清晰,故分篇而非混合 |
| 反判据 R1 | 原勘察判出「一产一消」,按规则须用户裁决;2026-09-19 用户裁定执行合并,理由成立:两线共用同一套 ffmpeg/Python 底座,且实际使用中常串联(先看懂再改写发出),分属两部时每次都要跨技能手工搬路径 |
| U1 能力不缩水 | 两源的每一节全部进入产物,销账 FAILS=0 |
| U2 调用省一步 | 「先理解再成片」由跨两部 → 一部到底,中间产物不再跨技能搬运 |
| U3 底座只一份 | scripts/ 只有一份(18 个脚本),ffmpeg/Python 依赖表只写一次 |
| 名称合规 | 目录名 = frontmatter.name = omni-digital-world(纯 kebab,符合 Agent Skills 开放标准);中文显示名 万象-数字世界(L0 未习) 住 _user_meta.json |
| 旧指纹 | 两源旧 copyright.fingerprint 已随正文重组失效,产物暂不盖章(版权章统一在整改收口后重盖) |
外置私有配置(不随包外发,因原值是本机绝对路径):
| 用途 | 位置 | 覆盖方式 |
|---|---|---|
| whisper 模型根目录 | ~/.workbuddy/omni-digital-world.json 的 whisper_model_root |
环境变量 WHISPER_MODEL_DIR 或 OMNI_DW_CONFIG |
| 成片工作根目录 | 环境变量 OMNI_VIDEO_ROOT(未设则取当前目录下 egg-video) |
— |