npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-video-studio

v0.4.3

Published

⚠️ 仅支持 Windows;需自装 ffmpeg。DSH 视频创作工作台:把一个素材目录做成成片。四工具 video_scan/video_frames/video_build/video_cover + 创作技能 + 服务端渲染预览页。支持选段、真转场、字幕与文字动画、字体预设、调色、配乐、慢动作、多种收尾(落款卡/定格/淡出)、封面图嵌 MP4。纯 Node 直调 ffmpeg,零运行时依赖。

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。 慢动作用来在片中做强调,不再默认拿来收尾。
  • 收尾方式 endingcard 深色落款卡(机构名 + 日期,音乐继续垫着走完)/ 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.jsdsh/subtitles.jsdsh/cover.js 的注释:

  • xfade 的时间基归一化(每段先 scale/pad/setsar/fps 再进 xfade)
  • 视频链与音频链分开搭:xfade 会把输出补到较长输入的长度而 acrossfade 不会, 两条链长度不一致时容器时长会由较短的音频决定,画面被静默截短
  • 音频用 adelay(片头封面卡)与 apad + 输出级 -t 对齐与精确截断
  • trimdurationstart 算起 —— 早期版本给减速段漏了 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 读首帧与片尾帧 的平均亮度(判断"封面是不是黑的""落款在不在")、用 ffprobeattached_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.js

DSH 会按声明去加载客户端 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 能爬到项目根。 页面顶部会显示目录来源,以后再"看错目录"一眼定位。

实现要点(踩过的坑)

  1. 客户端 bundle 是手写的,不经打包器:用 React.createElement 而不是 JSX, 这样 link: 安装的开发目录不需要构建步骤(lib/client.js 就是源码)。
  2. 依赖只有 react:它在 DSH 外壳的冻结基线模块表(PLATFORM_MODULES)里, 所以 dsh.client.external 什么都不用声明——每多声明一个外部模块就多一个失败点。
  3. 整个工厂包在 try/catch:客户端半边在激活期抛异常最坏会让整个 GUI 起不来。 现在即使客户端代码写错,也只是"面板不出现",不会拖垮界面;失败会 console.error 留痕。
  4. 注册走 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 声明它)。
  5. webServer 用 reactive ctx.inject 而不是模块级 inject 导出: 模块级 inject 是硬依赖,非 web 的 profile 没有 webServer,写进去会让工具都不激活。
  6. 面板的 tab 体宿主是"有确定高度的块级滚动容器",不是 flex 容器。 所以根元素写 height: 100% + overflowY: auto,不能只写 flex: 1(会塌成内容高度)。
  7. 扫目录时不要一刀切跳过点开头的文件:手机导出的素材就叫 .20260911_1mp4.mp4。 第一版 panel.js 又踩了一遍这个坑(.vstudio-tmp 是目录,isFile() 已经挡掉了)。
  8. ⚠️ 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):

    1. 去私人化skills/video-studio/SKILL.md 里作者的机构名、日期,以及 「用户明确说过…」「用户明确表达过不满」这类私人历史,全部换成占位符或中性表述。 技能是随包发布的(filesskills),不改就会把作者自己的机构名 带进每个装了插件的人的成片里,还会让 agent 把"某位用户确认过的偏好"当成 新用户也确认过。dsh/panel.jsdsh/workspace.js、本 README 里拿本机路径 举例的地方一并中性化。
    2. 补全包元数据repository / homepage / bugs / author / keywords, 以及 publishConfig.registry——本机 .npmrc 指向的是 npmmirror 和企业内部源, 靠这一行让 npm publish 落到 npmjs.org(配置入库,不含任何密钥)。
    3. LICENSE:此前 package.json 声明了 MIT,但仓库里没有这个文件。 另加 .gitignore
    4. 删掉残留的 pnpm-lock.yamlnode_modules/:那是早期尝试用 tsdown 打 客户端 bundle 时留下的,客户端半边已停用,本插件零运行时依赖。
    5. 新增 test/pack.mjs:对 npm pack 出来的 tarball 做验收——文件清单白名单 (不能带出 test/lib/_work/node_modules/)、包内 dsh.bundle.patch 指向的文件必须存在、不能出现 dsh.client、 技能已去私人化、解开后能加载出 4 工具 + 4 路由。
    6. test/verify-install.mjs 支持从任意目录校验VSTUDIO_ROOT),并修掉一个真缺陷: 它无条件读 lib/client.js,而 files 已排除 lib/,从 tarball 装的包里没有 这个文件,那项检查会 ENOENT 失败。
    7. 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 规则。所以这次不是"再修一次",而是换方案:

    1. package.json 不再声明 dsh.client,不再导出 ./clientfiles 去掉 lib → DSH 根本不会加载客户端代码,界面在结构上不可能再被它弄挂。 lib/client.js 改成停用说明,把三次事故的原始报错留在里面。
    2. 新增 dsh/viewer.js:服务端渲染的完整预览页面(GET /api/dsh-video-studio/view), 纯 HTML + CSS、不需要 JS,不依赖 DSH 任何客户端机制。
    3. 修 media 供流的工作区 bug:0.3.3 只把 state 路由改成新解析器, media 还写着 config.workspaceDir ?? process.cwd() → 清单能列出来、 播放器一取文件就 404(被 test/probe-e2e.mjs 抓出来)。
    4. 测试重写 test/panel.mjs(91 项):断言客户端半边确实已停用(声明/导出/files/文件内容 四处都要对上)、预览页 HTML 正确(播放器 src、中文编码、文件名 HTML 转义、 空态、错误态)、4 条路由的真实 HTTP 行为、宿主激活安全(Proxy 复刻 Cordis 抛异常行为)、 工作区解析、以及 media 必须与 state 用同一个解析器。
    5. 新增 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 里声明过的服务属性会抛异常,而不是返回 undefinedctx.webServercannot get property "webServer" without inject

    • v0.3.0:在 ctx.inject(['webServer'], cb) 的回调里用外层 ctxctx.webServer (外层没声明过)→ 抛异常 → 被内层 try/catch 吞掉 → 路由静默没注册
    • v0.3.1:为了"先试直接注册"写了 attach(ctx?.webServer, 'direct'), 属性访问发生在 try 之外 → 异常冲出 apply整个插件激活失败

    修法(规则也写进了代码注释,改之前必读):

    1. 任何地方都不许写 ctx.webServer,只能用 sctx.get('webServer')—— .get() 拿不到只是 undefined,不抛。
    2. registerPanelRoutes 整个函数体再包一层 try/catch:面板是可选功能, 绝不允许让插件激活失败
    3. 新增 test/panel.mjsE 节:用 Proxy 复刻 Cordis 的行为 (读未声明的服务属性就抛),断言"未声明 webServer 时宿主 apply 不抛"、 "仍注册 4 个工具"、"inject 自己抛也不抛"、"register 抛也不抛"。 这一节能直接抓住上面两次事故——之前缺的就是它。
  • 0.3.1 — 修「面板根本没出来」(v0.3.0 的实际故障):

    1. 宿主路由没注册: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) 里。
    2. 加了可读的启动诊断$DSH_HOME/dsh-video-studio/boot-log.jsonl 记录 apply 入口、注入是否回调、路由注册成败与异常栈。宿主 stdout 我读不到, 没有这条通道就只能靠猜——这次排查全程卡在这里。
    3. 客户端回执路由 /api/dsh-video-studio/client-status:客户端把注册成败、 缺哪个服务、异常栈 POST 回宿主,我用带认证的探测直接读, 不需要用户开 devtools 或贴日志
    4. 清单扫描改递归:用户会把成片放进自建子目录(实测 输出视频/03/xxx.mp4), 只扫一层面板里是空的。封面同时改为优先按同目录关联。
    5. 新增 test/probe-auth.mjs / test/probe-control.mjs:带会话 cookie 的现场探测, 读真实启动图、对照别的插件路由、检查服务端发出的 bundle 是不是当前代码。
  • 0.3.0 — 右侧栏「视频工作台」预览面板(客户端半边 + 两条宿主路由):

    1. lib/client.js:手写客户端 bundle,React.createElement,无构建步骤。 注册 DSH 原生的右侧栏 tab(类型 + tab 体两阶段),内嵌 <video> 播放成片、 预览封面、列素材。失败降级为"面板不出现",不会拖垮 GUI。
    2. dsh/panel.js/api/dsh-video-studio/state 清单 + /media/<rel> 供流, 按 Range 支持拖动进度条;只允许回环请求、路径钉死在工作区内、扩展名白名单。
    3. package.json:加上 exports["./client"]dsh.clientplatform: web, 只 inject dsh-client-ui-sidebar-rightexternal 为空)。
    4. 新增 test/panel.mjs(55 项):宿主机路由走真实 HTTP(含 206/416/403/404/HEAD), 客户端 bundle 用模拟的 __ModuleLoader__ 验注册契约,并用自制假 React 真的把面板组件跑一遍检查元素树(播放器 src、中文 URL 编码、空态、错误态), 外加"客户端出错必须降级而不是抛异常"的兜底断言。
  • 0.2.0 — 针对用户反馈"出的片子不好看、没问我细节、每次都是慢动作收尾、微信看不到封面"的改动:

    1. 先问再做变成硬门禁video_build 真渲染必须带 brief(用途 + 问答记录 + 用户已确认), 否则拒绝渲染并列出该问什么。新增 planOnly 模式:先把精确时间轴/文案/落款窗口给用户看。 技能里补齐了必问清单(用途/时长/画幅/文字/节奏与转场/收尾方式/片头封面卡/字体风格/文字动画)。
    2. 收尾不再默认慢动作:新增 ending(card / freeze / slowmo / fade / none),默认 fadecard 是深色落款卡(机构名 + 日期),音乐继续垫着走完;freeze 是末帧定格压字, 用 endingFreezeLines(不给则复用 endingCardLines)。两者落款样式不同—— card 的底是纯色卡可以无描边,freeze 的底是实拍画面,必须走带半透明底板的样式, 否则压在亮草地上读不出来。正文文字会自动截到收尾区之前,不会和落款叠在一起。 finalSpeed / freezeSeconds 保留但标记废弃(只作用末段),并会给出迁移提示。
    3. 慢动作移到片中segments[].speed / segments[].freeze 逐段可控。 顺带修掉一个潜伏 bug:减速段原来的 trim=duration=X 漏了 start=,会从 0 秒开始截。
    4. 微信封面cover 参数把封面图作为 attached_pic 嵌进 MP4(-c copy 不重编码, 写临时文件再 rename,并复验附加流真的存在)。同时新增 coverCard(片头封面卡)作为兜底。 首帧仍不做淡入。
    5. 字体风格 + 文字动画:新增 font(9 种预设,libass 与 drawtext 共用一张表) 与 animate(none / fade / slideup / rise / pop)。字号改为按 min(w,h)/720 缩放, 竖屏不再字号错位。
    6. 调色与转场:新增 look 预设(none/bright/vivid/warm/cool/soft,可逐段覆盖), 转场恢复为逐边界可选并加了白名单校验。
    7. 时间轴重构为 clip 链(片头卡 → 片段 → 落款卡),视频链与音频链分开搭, 用 adelay/apad 对齐,不再依赖"xfade 与 acrossfade 长度一致"的巧合。
    8. 新增 test/v02.mjs:用实际产物验证上述每一条(亮度、附加流、帧间差、md5)。
    9. 接口变更(破坏性)video_build 的返回值里 segments 改为每段的详细信息数组 (含时间轴区间、speed、freeze、look),段数改名为 segmentCount。 旧的 finalSpeed / freezeSeconds 仍可用但会给出迁移警告,请改用 segments[].speed / segments[].freeze
  1. 新增段起止点校验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