dsh-video-studio
v0.4.3
Published
⚠️ 仅支持 Windows;需自装 ffmpeg。DSH 视频创作工作台:把一个素材目录做成成片。四工具 video_scan/video_frames/video_build/video_cover + 创作技能 + 服务端渲染预览页。支持选段、真转场、字幕与文字动画、字体预设、调色、配乐、慢动作、多种收尾(落款卡/定格/淡出)、封面图嵌 MP4。纯 Node 直调 ffmpeg,零运行时依赖。
Maintainers
Readme
dsh-video-studio
DSH 视频创作工作台插件:把一个素材目录做成成片。
给它一个装着原始视频的目录,它会探测素材、抽帧供模型读图理解画面、按剪辑方案渲染 成片(真转场、文字、配乐、慢动作定格),并生成封面图。
使用
装上后对 agent 说:
用
D:\我的素材里的视频做一条成片,放在D:\输出
agent 会先扫描素材、问你几个关键问题,再抽帧看图选段、用 planOnly 把精确时间轴
给你看、确认后出片。
这一步是硬的,不是靠自觉。 video_build 真渲染时会校验 brief:
brief: {
purpose: '机构宣传片',
answers: { '用途': '机构宣传', '时长': '24 秒', '收尾': '深色落款卡' },
confirmed: true, // 或者 skippedByUser: true + skipReason(用户说不用问)
}没带 brief、或 brief.answers 是空的、或没声明用户已确认 → 直接拒绝渲染并列出
该问哪些问题。planOnly: true 不受此限制,因为出方案本身就是给人看的。
四个工具
| 工具 | 作用 |
| :-- | :-- |
| video_scan | 扫目录:列出素材并探测时长/分辨率/帧率/编码/音轨 |
| video_frames | 抽帧成图片供 agent 读图(every 等间隔 / times 定点) |
| video_build | planOnly:true 出精确方案;否则按方案渲染 |
| video_cover | 从成片取帧生成封面图 + 压落款文字,可用 embedInto 嵌进 MP4 |
配套技能 video-studio 会同步到 ~/.dsh/skills/,它固化了完整创作流程和踩过的坑。
创作能力
- 逐段速度/定格:
segments[].speed(0.5 = 半速)、segments[].freeze。 慢动作用来在片中做强调,不再默认拿来收尾。 - 收尾方式
ending:card深色落款卡(机构名 + 日期,音乐继续垫着走完)/freeze末帧定格压字 /slowmo末段慢动作 /fade直接淡出 /none硬切。 - 字体预设
font:yahei / hei / deng / yuan / kai / song / fang / li / shu, libass 与 drawtext 两边共用一个预设表,不会出现"正文和封面用了不同字体"。 - 文字动画
animate:none / fade / slideup / rise / pop。 - 调色
look:none / bright / vivid / warm / cool / soft,可逐段覆盖。 - 转场
transitions:fade / dissolve / wipe* / slide* / smooth* / circleopen / radial / pixelize / zoomin 等,逐边界可不同。 - 封面
cover:渲染完把封面图作为attached_pic附加流嵌进 MP4(不重编码)。 - 片头封面卡
coverCard(可选):片头 1.2 秒静止封面,用于"微信抓首帧"的兜底。
安装
先确认你怎么跑 DSH。 如果平时敲的是 npx @deepseek-ai/dsh web(没装全局 dsh),
那 dsh plugin ... 会直接报「无法将 dsh 项识别为 cmdlet」,要用下面的 npx 形式。
# 装了全局 dsh 的人
dsh plugin --profile web add dsh-video-studio
# 用 npx 跑 DSH 的人(更常见)——包名必须写全
npx @deepseek-ai/dsh plugin --profile web add dsh-video-studio
# 别人给你的 tarball / 本地目录 / GitHub
dsh plugin --profile web add ./dsh-video-studio-0.4.3.tgz
dsh plugin --profile web add ./dsh-video-studio
dsh plugin --profile web add github:litao36253/dsh-video-studio⚠️ 不要敲
npx dsh ...。 npm 上确实有一个叫dsh的包,但那是别人写的 「A shell written in JavaScript」,跟 DeepSeek Harness 毫无关系,装了会跑错东西。 Harness 的包名是@deepseek-ai/dsh。
装完必须完全重启 dsh web(bundle 列表在启动时读取,热加载不生效,光刷新页面没用)。
在插件市场(dshmarket)里搜不到、也不能一键装——市场的 GUI 只放行它自己的精选目录 内的来源,命令行安装没有这个限制。
平台要求
- 目前只支持 Windows。 文字功能(正文字幕、落款卡、片头封面卡)依赖
C:\Windows\Fonts下的中文字体,字体路径在dsh/fonts.js里是写死的。 macOS / Linux 上这些功能会失败;视频拼接、转场、调色、封面取帧不受影响。 - ffmpeg / ffprobe 要自己装。 建议
winget install Gyan.FFmpeg(full build 带 libx264/libass/xfade 等全部滤镜)。插件会自动探测 PATH 与常见安装位置, 也可以用环境变量DSH_FFMPEG_PATH/DSH_FFPROBE_PATH指定。 没装的话工具会直接报「找不到 ffmpeg」。 - 配乐自备。
music参数收的是本地音频文件路径。插件不调用任何付费 API, 也不依赖别的插件。
配置
在 profile 的 cordis.patch.yml 里覆盖:
- id: video-studio
name: 'dsh-video-studio'
config:
timeoutMs: 1800000 # 单次渲染超时(默认 30 分钟)
# ffmpegPath: C:\tools\ffmpeg\bin\ffmpeg.exe
# ffprobePath: C:\tools\ffmpeg\bin\ffprobe.exe为什么不用 dsh-ffmpeg
[email protected] 的十个工具在 DSH 0.1.5-rc.2 上全部不可用,报:
Cannot read properties of undefined (reading 'selectContainmentMode')原因是它用 ctx.subprocess.spawn 裸引用调用宿主 subprocess 服务,而该服务的 spawn
依赖 this。本插件自己用 node:child_process 起进程,因此不受影响。
本插件还把子进程输出重定向到文件而非管道:受限环境下 node 的管道 stdio
spawn 会直接抛 EPERM(实测 pipe → EPERM,ignore/file → 正常),用文件最稳。
另外 xfade(真转场)、amix(混音)这类能力 dsh-ffmpeg 根本没有暴露,
本插件的渲染器直接实现了。
实现要点(改动前请读)
渲染器把实测踩过的坑都固化了,细节见 dsh/render.js、dsh/subtitles.js、
dsh/cover.js 的注释:
- xfade 的时间基归一化(每段先 scale/pad/setsar/fps 再进 xfade)
- 视频链与音频链分开搭:xfade 会把输出补到较长输入的长度而 acrossfade 不会, 两条链长度不一致时容器时长会由较短的音频决定,画面被静默截短
- 音频用
adelay(片头封面卡)与apad+ 输出级-t对齐与精确截断 trim的duration从start算起 —— 早期版本给减速段漏了start=,已修- fade 计时相对段首;
fade=t=out统一放在整条链末尾按目标时长倒推 - tpad 补帧长度按需计算,宁可多补再用
-t截 - ASS 必须带 UTF-8 BOM,否则中文乱码
\move/\pos会屏蔽 style 的MarginV,动画锚点 y 必须自己算\t(...)缩放动画必须先给起始值- ASS 里多个
align=5的 cue 会叠在一起 → 落款卡多行必须放在同一个 Dialogue 里 drawtext必须显式给 fontfile,否则 fontconfig 缺失导致段错误(STATUS_ACCESS_VIOLATION)- 首帧不能淡入(聊天软件抓封面会抓黑图)
- 片尾不用纯黑(用
0x0a100e极深墨绿) - JPEG 需要全范围 YUV(
yuvj420p) - MP4 封面的
attached_pic必须是 mjpeg,且 copy 后要再走一次+faststart
测试
node test/selfcheck.mjs # 加载前自检(装之前务必先跑)
node test/v02.mjs # v0.2 回归:门禁 / 方案 / 收尾 / 封面嵌入 / 字体 / 动画
node test/panel.mjs # 面板:宿主路由(真实 HTTP) + 客户端 bundle 契约 + 组件渲染
node test/tools.mjs # 四工具端到端(含注册检查)
node test/smoke.mjs # 渲染器冒烟测试
node test/verify-install.mjs # 装机校验:从 profile 的安装路径实测(改 package.json 后必跑)test/verify-install.mjs 是专门为"启动失败前科"加的:它从
profiles/<name>/node_modules/<包名> 这个安装路径去验客户端声明与文件是否一致、
宿主半边能否加载并注册、客户端 bundle 能否被 __ModuleLoader__ 注册。
本地目录一切正常但安装路径少文件,正是上次炸掉的原因。
test/panel.mjs(68 项)不靠肉眼:宿主路由走真实 HTTP(206/416/403/404/HEAD 都测),
客户端 bundle 用模拟的 __ModuleLoader__ 验注册契约,并且用自制的假 React 把面板组件
真的跑一遍、检查产出的元素树(播放器 src、中文 URL 编码、空态、错误态)。
面板的视觉效果仍然只能靠人看,但"结构对不对"是自动验的。
test/v02.mjs 全部靠实际产物验证,不看代码自证:用 rawvideo 读首帧与片尾帧
的平均亮度(判断"封面是不是黑的""落款在不在")、用 ffprobe 查 attached_pic
附加流、用帧间像素差验证动画真的动了(slideup 的重心 72.3→41.3,fade/pop
的亮像素数从 986/1212 涨到 1969/2035)。
测试需要一个含真实视频的素材目录,路径写在测试文件顶部。
为什么要有 selfcheck
曾经把 dsh.client 声明留在 package.json 里、却没有对应的 lib/client.js,
结果 dsh web 整个 profile 启动失败:
client-modules: client bundle not found; run `pnpm run build` before launch:
package: dsh-video-studio
path: .../lib/client.jsDSH 会按声明去加载客户端 bundle,文件不存在就拒绝启动。selfcheck 专门校验
"声明与实际文件是否一致"(以及 exports 指向的文件是否存在),
改 package.json 后务必先跑它再安装。
预览页面「视频工作台」
打开这个地址就能看成片、预览封面、看素材清单,带内嵌播放器(支持拖进度条):
http://127.0.0.1:<dsh web 的端口>/api/dsh-video-studio/view默认端口是 3080。要在已经登录 dsh web 的那个浏览器里打开(页面走宿主鉴权)。
页面是服务端渲染的普通网页,不需要 JS,刷新即重新请求。
为什么是"一个页面"而不是"右侧栏的一个 tab"
原本做成了 DSH 右侧栏的 tab(需要写客户端半边,用 DSH 的 sidebarRightTabs / slots 服务)。
那条路连续翻车三次,每次用户都得把插件删掉才能重启 dsh web:
| 版本 | 位置 | 后果 |
| :-- | :-- | :-- |
| 0.3.0 | 宿主:inject 回调里用外层 ctx 取服务 | 抛异常被吞 → 路由静默没注册,面板不出来 |
| 0.3.1 | 宿主:ctx?.webServer 写在 try 之外 | 插件激活失败 → dsh web 起不来 |
| 0.3.4 | 客户端:诊断里写了 ctx?.sidebarRightTabs | 客户端半边抛异常 → Failed to load plugins |
三次都是同一条 Cordis 规则:读取没在 inject 里声明过的服务属性会抛异常,
而抛在 apply 里就会让整个插件(乃至整棵树)加载失败。
但更根本的问题不是这条规则,而是我看不到浏览器——改完只能靠用户一次次重启当我的眼睛。
这个代价不该由用户承担,所以 0.4.0 起弃用客户端半边(package.json 不再声明
dsh.client、不再导出 ./client,DSH 根本不会加载那段代码),预览改成只用我自己写的
HTTP 路由 + 服务端渲染。代价说清楚:它不是一个 tab,而是一个要自己打开的页面。
四条宿主路由
| 路由 | 作用 |
| :-- | :-- |
| GET /api/dsh-video-studio/view | 预览页面(服务端渲染的完整 HTML) |
| GET /api/dsh-video-studio/state | 工作区清单:成片 / 封面 / 素材(递归扫描子目录) |
| GET /api/dsh-video-studio/media/<相对路径> | 按 Range 提供文件(206 Partial Content) |
| GET/POST /api/dsh-video-studio/client-status | 留给客户端回报注册状态的座位(当前无客户端使用) |
Range 是必需的:<video> 拖进度条、seek 都要它。只把整个文件一次性 end() 出去的话
进度条是拖不动的(dsh-audiogen 的音频路由就那么做,因为音频小)。
state 必须递归:用户会把成片放进自建子目录(实测 输出视频/03/xxx.mp4),
只扫一层的话页面里是空的,会让人以为插件坏了。封面优先按同目录关联——
一条片子放一个子目录时,同目录那张 jpg 就是它的封面,不需要猜名字。
安全上做了三层:只接受本机回环请求、路径必须钉在工作区内(../ 逃逸返回 403)、
只提供白名单扩展名。文件名一律 HTML 转义后进页面(有测试专门盯这条)。
工作区是怎么定的
process.cwd() 靠不住:从主目录启动 dsh web 时 cwd 就是主目录,
页面会是空的(踩过)。所以按优先级解析(见 dsh/workspace.js):
| 优先级 | 来源 | 说明 |
| :-- | :-- | :-- |
| 1 | config.workspaceDir | 显式指定 |
| 2 | remembered | 工具调用时记住的目录(video_scan/video_build/video_cover 的路径反推后落盘) |
| 3 | registry | DSH 的 ctx.workspaceRegistry.list() |
| 4 | storages | $DSH_HOME/storages/workspace.json 兜底 |
| 5 | cwd | 最后兜底 |
反推规则:从任意路径往上爬最多 5 层,命中"自己就叫 输出视频/源视频"或
"包含这两个子目录"的祖先 → 那就是工作区。所以 输出视频/03/x.mp4 能爬到项目根。
页面顶部会显示目录来源,以后再"看错目录"一眼定位。
实现要点(踩过的坑)
- 客户端 bundle 是手写的,不经打包器:用
React.createElement而不是 JSX, 这样link:安装的开发目录不需要构建步骤(lib/client.js就是源码)。 - 依赖只有
react:它在 DSH 外壳的冻结基线模块表(PLATFORM_MODULES)里, 所以dsh.client.external什么都不用声明——每多声明一个外部模块就多一个失败点。 - 整个工厂包在
try/catch里:客户端半边在激活期抛异常最坏会让整个 GUI 起不来。 现在即使客户端代码写错,也只是"面板不出现",不会拖垮界面;失败会console.error留痕。 - 注册走 DSH 原生接口,不依赖 dsh-better-sidebar:
阶段一
ctx.sidebarRightTabs.register({ id, kind, title, guide })声明 tab 类型, 阶段二ctx.slots.register({ name:'sidebar.right.pane.tab', key }, Body)渲染 tab 体。slots是外壳基座服务(没有任何包通过dsh.client.inject声明它)。 webServer用 reactivectx.inject而不是模块级inject导出: 模块级 inject 是硬依赖,非 web 的 profile 没有webServer,写进去会让工具都不激活。- 面板的 tab 体宿主是"有确定高度的块级滚动容器",不是 flex 容器。
所以根元素写
height: 100%+overflowY: auto,不能只写flex: 1(会塌成内容高度)。 - 扫目录时不要一刀切跳过点开头的文件:手机导出的素材就叫
.20260911_1mp4.mp4。 第一版panel.js又踩了一遍这个坑(.vstudio-tmp是目录,isFile()已经挡掉了)。 - ⚠️ Cordis 里读未声明的服务属性会抛异常(不是 undefined)。所以:
- 永远不要写
ctx.webServer;要拿到它只能ctx.inject(['webServer'], (sctx) => ...)然后用sctx.get('webServer'); - 这类可选功能的注册代码,整个函数体都要包 try/catch——
它的异常如果冲出
apply,整个插件激活失败,dsh web直接起不来。
- 永远不要写
变更记录
0.4.3 — 修安装命令:
dsh plugin …对用 npx 跑 DSH 的人必然失败。真实的报错:
dsh : 无法将"dsh"项识别为 cmdlet、函数、脚本文件或可运行程序的名称。 原因是dsh这个命令只存在于全局安装、或者 npx 自己给子进程准备的 PATH 里—— 用npx @deepseek-ai/dsh web启动的人,自己的 shell 里根本没有dsh。 而 README 把dsh plugin …写成了唯一形式,等于让每个这样的人都撞一次。改成按启动方式给两种命令(全局
dsh/npx @deepseek-ai/dsh)。另外补了一条警告:不要写
npx dsh——npm 上确实有一个叫dsh的包,但那是别人 写的「A shell written in JavaScript」,跟 DeepSeek Harness 毫无关系,敲错会跑到 别的东西上。Harness 的包名是@deepseek-ai/dsh。0.4.2 — 修「npm 描述被截断,导致平台限制在页面上消失」。
发布 0.4.1 之后我核对了 registry 上的实际内容,发现:npm 把
description硬截断在 255 字符,而「仅支持 Windows」原来写在描述末尾(第 256~301 字符), 整句被截掉——npm 页面上只剩半句「⚠️ 目前」。这恰恰是用户最需要提前知道的前提。改成把平台警告放在最前面,整段压到 201 字符。
test/pack.mjs加了断言(长度 ≤255,且前 255 字符内必须出现 Windows),发版自动拦。这次只改包元数据:线上 0.4.1 的 tarball 校验和
f1677ae2…与我们本地逐项验收过的 完全一致,说明代码和打包都没问题。0.4.1 — 准备公开发布(只改包外包装,不动工具 API):
- 去私人化。
skills/video-studio/SKILL.md里作者的机构名、日期,以及 「用户明确说过…」「用户明确表达过不满」这类私人历史,全部换成占位符或中性表述。 技能是随包发布的(files含skills),不改就会把作者自己的机构名 带进每个装了插件的人的成片里,还会让 agent 把"某位用户确认过的偏好"当成 新用户也确认过。dsh/panel.js、dsh/workspace.js、本 README 里拿本机路径 举例的地方一并中性化。 - 补全包元数据:
repository/homepage/bugs/author/keywords, 以及publishConfig.registry——本机.npmrc指向的是 npmmirror 和企业内部源, 靠这一行让npm publish落到 npmjs.org(配置入库,不含任何密钥)。 - 补
LICENSE:此前package.json声明了 MIT,但仓库里没有这个文件。 另加.gitignore。 - 删掉残留的
pnpm-lock.yaml与node_modules/:那是早期尝试用 tsdown 打 客户端 bundle 时留下的,客户端半边已停用,本插件零运行时依赖。 - 新增
test/pack.mjs:对npm pack出来的 tarball 做验收——文件清单白名单 (不能带出test/、lib/、_work/、node_modules/)、包内dsh.bundle.patch指向的文件必须存在、不能出现dsh.client、 技能已去私人化、解开后能加载出 4 工具 + 4 路由。 test/verify-install.mjs支持从任意目录校验(VSTUDIO_ROOT),并修掉一个真缺陷: 它无条件读lib/client.js,而files已排除lib/,从 tarball 装的包里没有 这个文件,那项检查会 ENOENT 失败。test/selfcheck.mjs加防泄漏断言:files白名单内的文件不得出现作者私人信息, 以后每次发版自动拦。
- 去私人化。
0.4.0 — 弃用客户端半边,预览改成服务端渲染的独立页面。 起因:0.3.4 的诊断代码里写了
ctx?.sidebarRightTabs,客户端半边在激活期抛cannot get property "sidebarRightTabs" without inject,界面直接报 Failed to load plugins,用户只能又把插件删掉。这是第三次同类事故(0.3.0 路由静默没注册 / 0.3.1 dsh web 起不来 / 0.3.4 界面加载失败),根因都是同一条 Cordis 规则。所以这次不是"再修一次",而是换方案:
package.json不再声明dsh.client,不再导出./client,files去掉lib→ DSH 根本不会加载客户端代码,界面在结构上不可能再被它弄挂。lib/client.js改成停用说明,把三次事故的原始报错留在里面。- 新增
dsh/viewer.js:服务端渲染的完整预览页面(GET /api/dsh-video-studio/view), 纯 HTML + CSS、不需要 JS,不依赖 DSH 任何客户端机制。 - 修 media 供流的工作区 bug:0.3.3 只把
state路由改成新解析器,media还写着config.workspaceDir ?? process.cwd()→ 清单能列出来、 播放器一取文件就 404(被test/probe-e2e.mjs抓出来)。 - 测试重写
test/panel.mjs(91 项):断言客户端半边确实已停用(声明/导出/files/文件内容 四处都要对上)、预览页 HTML 正确(播放器 src、中文编码、文件名 HTML 转义、 空态、错误态)、4 条路由的真实 HTTP 行为、宿主激活安全(Proxy 复刻 Cordis 抛异常行为)、 工作区解析、以及 media 必须与 state 用同一个解析器。 - 新增
test/probe-e2e.mjs/probe-auth.mjs/probe-status.mjs: 带会话 cookie 的现场端到端探测(清单 / 客户端回执 / HEAD+Range), 用来在用户环境里验证,而不是靠猜。
0.3.3 — 修「面板出来了,但是空的」:面板把工作区取错了。 第一版用
process.cwd(),而从主目录启动dsh web时 cwd 就是主目录, 于是面板认定工作区是用户主目录 → 成片 0 部、素材 0 个。 新增dsh/workspace.js,按优先级解析工作区并在面板上显示来源:| 优先级 | 来源 | 说明 | | :-- | :-- | :-- | | 1 |
config.workspaceDir| 用户显式指定,永远最权威 | | 2 |remembered| 工具调用时记住的目录(video_scan/video_build/video_cover的路径反推后落盘)——"agent 刚用过的那个"比任何全局配置都贴切 | | 3 |registry| DSH 的ctx.workspaceRegistry.list()| | 4 |storages|$DSH_HOME/storages/workspace.json兜底 | | 5 |cwd| 最后兜底 |反推规则:从任意路径往上爬最多 5 层,命中"自己就叫
输出视频/源视频"或 "包含这两个子目录"的祖先 → 那就是工作区。所以输出视频/03/x.mp4能爬到项目根。另外:
state路由每次请求都重新解析工作区,不是注册时定死 (第一版就是定死的,结果永久锁在 cwd 上);workspaceSource显示在面板上, 以后再"看错目录"一眼定位。测试新增 F 节(7 种路径形态反推 / storages 解析 / 优先级 / 注册表抛异常不崩 / 每请求重解析)。0.3.2 — 修「插件激活失败,dsh web 起不来」(v0.3.1 引入;用户只能删插件才重启成功)。 根因是一条 Cordis 规则,两次事故都出自它:
读取一个没有在
inject里声明过的服务属性会抛异常,而不是返回undefined。ctx.webServer→cannot get property "webServer" without inject- v0.3.0:在
ctx.inject(['webServer'], cb)的回调里用外层ctx取ctx.webServer(外层没声明过)→ 抛异常 → 被内层try/catch吞掉 → 路由静默没注册。 - v0.3.1:为了"先试直接注册"写了
attach(ctx?.webServer, 'direct'), 属性访问发生在try之外 → 异常冲出apply→ 整个插件激活失败。
修法(规则也写进了代码注释,改之前必读):
- 任何地方都不许写
ctx.webServer,只能用sctx.get('webServer')——.get()拿不到只是undefined,不抛。 registerPanelRoutes整个函数体再包一层try/catch:面板是可选功能, 绝不允许让插件激活失败。- 新增
test/panel.mjs的 E 节:用Proxy复刻 Cordis 的行为 (读未声明的服务属性就抛),断言"未声明 webServer 时宿主apply不抛"、 "仍注册 4 个工具"、"inject 自己抛也不抛"、"register 抛也不抛"。 这一节能直接抓住上面两次事故——之前缺的就是它。
- v0.3.0:在
0.3.1 — 修「面板根本没出来」(v0.3.0 的实际故障):
- 宿主路由没注册:v0.3.0 只在
ctx.inject(['webServer'], cb)里裸调ctx.webServer.register(...),实测/api/dsh-video-studio/state返回 404, 而同机 dsh-audiogen 的路由正常(说明机制没问题、是我这一处的问题)。 对照可用的写法改成:先试直接注册(apply 时 webServer 已就绪就不绕注入), 否则用注入回来的sctx取 webServer,并把注册包在sctx.effect(fn, name)里。 - 加了可读的启动诊断:
$DSH_HOME/dsh-video-studio/boot-log.jsonl记录 apply 入口、注入是否回调、路由注册成败与异常栈。宿主 stdout 我读不到, 没有这条通道就只能靠猜——这次排查全程卡在这里。 - 客户端回执路由
/api/dsh-video-studio/client-status:客户端把注册成败、 缺哪个服务、异常栈 POST 回宿主,我用带认证的探测直接读, 不需要用户开 devtools 或贴日志。 - 清单扫描改递归:用户会把成片放进自建子目录(实测
输出视频/03/xxx.mp4), 只扫一层面板里是空的。封面同时改为优先按同目录关联。 - 新增
test/probe-auth.mjs/test/probe-control.mjs:带会话 cookie 的现场探测, 读真实启动图、对照别的插件路由、检查服务端发出的 bundle 是不是当前代码。
- 宿主路由没注册:v0.3.0 只在
0.3.0 — 右侧栏「视频工作台」预览面板(客户端半边 + 两条宿主路由):
lib/client.js:手写客户端 bundle,React.createElement,无构建步骤。 注册 DSH 原生的右侧栏 tab(类型 + tab 体两阶段),内嵌<video>播放成片、 预览封面、列素材。失败降级为"面板不出现",不会拖垮 GUI。dsh/panel.js:/api/dsh-video-studio/state清单 +/media/<rel>供流, 按 Range 支持拖动进度条;只允许回环请求、路径钉死在工作区内、扩展名白名单。package.json:加上exports["./client"]与dsh.client(platform: web, 只 injectdsh-client-ui-sidebar-right,external为空)。- 新增
test/panel.mjs(55 项):宿主机路由走真实 HTTP(含 206/416/403/404/HEAD), 客户端 bundle 用模拟的__ModuleLoader__验注册契约,并用自制假 React 真的把面板组件跑一遍检查元素树(播放器 src、中文 URL 编码、空态、错误态), 外加"客户端出错必须降级而不是抛异常"的兜底断言。
0.2.0 — 针对用户反馈"出的片子不好看、没问我细节、每次都是慢动作收尾、微信看不到封面"的改动:
- 先问再做变成硬门禁:
video_build真渲染必须带brief(用途 + 问答记录 + 用户已确认), 否则拒绝渲染并列出该问什么。新增planOnly模式:先把精确时间轴/文案/落款窗口给用户看。 技能里补齐了必问清单(用途/时长/画幅/文字/节奏与转场/收尾方式/片头封面卡/字体风格/文字动画)。 - 收尾不再默认慢动作:新增
ending(card / freeze / slowmo / fade / none),默认fade。card是深色落款卡(机构名 + 日期),音乐继续垫着走完;freeze是末帧定格压字, 用endingFreezeLines(不给则复用endingCardLines)。两者落款样式不同——card的底是纯色卡可以无描边,freeze的底是实拍画面,必须走带半透明底板的样式, 否则压在亮草地上读不出来。正文文字会自动截到收尾区之前,不会和落款叠在一起。finalSpeed/freezeSeconds保留但标记废弃(只作用末段),并会给出迁移提示。 - 慢动作移到片中:
segments[].speed/segments[].freeze逐段可控。 顺带修掉一个潜伏 bug:减速段原来的trim=duration=X漏了start=,会从 0 秒开始截。 - 微信封面:
cover参数把封面图作为attached_pic嵌进 MP4(-c copy不重编码, 写临时文件再 rename,并复验附加流真的存在)。同时新增coverCard(片头封面卡)作为兜底。 首帧仍不做淡入。 - 字体风格 + 文字动画:新增
font(9 种预设,libass 与 drawtext 共用一张表) 与animate(none / fade / slideup / rise / pop)。字号改为按min(w,h)/720缩放, 竖屏不再字号错位。 - 调色与转场:新增
look预设(none/bright/vivid/warm/cool/soft,可逐段覆盖), 转场恢复为逐边界可选并加了白名单校验。 - 时间轴重构为 clip 链(片头卡 → 片段 → 落款卡),视频链与音频链分开搭,
用
adelay/apad对齐,不再依赖"xfade 与 acrossfade 长度一致"的巧合。 - 新增
test/v02.mjs:用实际产物验证上述每一条(亮度、附加流、帧间差、md5)。 - 接口变更(破坏性):
video_build的返回值里segments改为每段的详细信息数组 (含时间轴区间、speed、freeze、look),段数改名为segmentCount。 旧的finalSpeed/freezeSeconds仍可用但会给出迁移警告,请改用segments[].speed/segments[].freeze。
- 先问再做变成硬门禁:
- 新增段起止点校验:
start超出源视频长度会直接点名报错("某某.mp4 只有 31.80s, 但你要求从 42s 开始切"),end超出则夹到片尾并警告。这个校验是修 bug 时挖出来的—— 旧代码减速段漏了start=,导致"切到源视频外面去"的错误方案也能渲染成功, 两个老测试其实一直在靠这个 bug 通过。
- 0.1.1 — 移除
dsh.client声明与exports["./client"]。 此前声明了客户端半边却没有lib/client.js,导致dsh web整个 profile 启动失败 (client-modules: client bundle not found)。新增test/selfcheck.mjs专门校验 这类"声明与文件不一致"的问题,改package.json后应先跑它再安装。 - 0.1.0 — 四个工具 + 创作技能。
License
MIT
