Skip to content

dsh-video-skin

Verified

dsh-video-skin · v0.1.1 · MIT

DSH 视频皮肤:一段短片循环铺成整个界面的聊天背景(无声、可调透明度),另一段做有声的开机动画(片尾交叉溶解进界面、点击画面随时跳过)。零依赖。

Install

dsh plugin add dsh-video-skin

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Tags

Readme

dsh-video-skin

DSH 的视频皮肤:把一段短片铺成整个 Web 界面的聊天背景,并在启动时用另一段短片做开机动画—— 有声播放、完整放完再渐变淡出进界面、点击画面随时跳过。

  • 开机动画:全窗短片(带声音)+ 真实启动进度条 + 常驻的「点击画面跳过」提醒;片子先完整放完 (ended),再用固定时长 fadeMs 渐变淡出,音量也跟着一起淡、不会被硬切。

    早先的版本是"从片尾往前 fadeMs 开始交叉溶解、片子放完那一刻正好淡完",而且淡出时长用的是 当时的真实剩余时间 —— 界面就绪得晚时剩余可能只剩 100~200ms,那次退场看起来就是硬切 (用户报的"有时候还没放完就切掉了"正是它)。现在默认 exitMode: 'end':先放完,再淡; 想要片尾交叉溶解可以设成 'tail',淡出时长同样固定。

  • 聊天背景:无声、循环、object-fit: cover 铺满视口,面板半透明化,画面明显看得见、文字照样读得清。
  • 界面上就能调透明度:右下角一个小圆钮 → 两个滑块 + 三个预设 + 实时文字对比度读数; 改完就生效、刷新还在,不必碰 JSON、不必重启 DSH(见 §3.1)。
  • 一个依赖都没有:宿主半边只用 node: 内建,页面侧是一段内联脚本,产物里没有任何 import。

英文说明见 README.md。

安装(一行)

设置 → 插件 → 添加插件,贴进这一行,点「安装」,然后 完全退出并重新打开 DSH:

github:Something11235/dsh-video-skin#main
  • 桌面版(Electron)的索引注入表是宿主启动时收集一次的快照 ⇒ 装完必须完全重启 DSH(刷新页面不够)。
  • 仓库里已经提交了运行所需的一切(index.js / lib / runtime / assets 的 mp4), 而且没有 prepare 脚本 —— 这不是省事,是必须:pnpm 会拦 git 依赖的构建脚本, 带 prepare 的仓库会让 dsh plugin add "github:..." 直接失败(ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED), 即使产物已经提交。
  • 卸载:dsh plugin --profile <你的 profile> remove dsh-video-skin,或用仓库里的 scripts/uninstall.ps1。
  • 国内网络直连 github 可能慢:装不上时可以加代理,或先把仓库 fork 到你自己的账号再装 fork 的地址。

0. 它长什么样(本机实测截图)

开机动画 亮色主题下的背景
开机动画:短片铺满视口(有声)+ 真实启动进度条 + 右下角常驻的「点击画面跳过」 亮色主题:视频铺满,面板半透明,正文对比度 5% 分位 7.90
暗色主题下的背景 透明度调节面板
暗色主题:视频清晰可见,正文对比度 5% 分位 6.86、中位 16.06(正文要求 4.5) 右下角圆钮展开的调节面板:两个滑块、三个预设、实时对比度 7.9(很充裕)

三张都是 scripts/verify-browser.mjs 在真 Chromium里跑出来的(见 §6.1)。


1. 它长什么样

启动 → 覆盖层立刻占住视口(同步,先于任何网络请求)
        ├─ 短片全窗播放(有声——桌面版默认就允许;被浏览器拦下时自动退回静音)
        ├─ 底部细进度条 = 内核真实的插件激活进度
        └─ 右下角常驻胶囊:「▶ 点击画面跳过」
     → 片子放完(ended)且界面已就绪 → 整体渐变淡出 fadeMs(底色/短片/UI/**音量**一起淡)
     → 淡完节点从 DOM 移除,背景层这时才建(避免和开机抢带宽)

exitMode: 'tail' 时第 4 步换成"片子剩余 ≤ fadeMs 且界面已就绪就开始淡"(片尾交叉溶解)。 两种模式淡出时长都是固定的 fadeMs,不会随"当时还剩多少"缩水成硬切。

参数:fadeMs(默认 1000)· holdMs 18000 / holdGraceMs 4000 · noFrameMs 12000 · endlessMs 20000。

任何时候都可以进入:点一下画面、按 Esc / 空格 / 回车。

关于声音:桌面版(Electron)没有设置 webPreferences.autoplayPolicy,于是用 Electron 的默认值 no-user-gesture-required —— 未静音的 play() 直接成功,开机动画一起就是有声的。普通浏览器 (dsh web 用 Chrome 打开)默认策略会拒掉未静音自动播放;那时运行时自动退回静音继续播 (不扣住用户),并在左下角多出一个独立的声音开关 —— 点它只开声音、不跳过,两个意图不互相踩。 如果只想要安静的开机,把 boot.sound 设成 false。

2. 装进桌面版 DSH(当前正在用的这个)

DSH 桌面版(Electron)的 index.html 来自安装包里的静态 dist,tapIndex 之类的函数式变换永远不生效; 唯一的注入通道是 Host 侧 webserver/index-inject 贡献的结构化行,而且那张表是宿主启动时收集一次、 之后没有任何刷新路径。所以:

装完必须完全退出并重新打开 DSH。 只刷新页面不够——注入表是开机快照。 而且第一次装完之后,"开机动画"本来也只在下次启动时才看得到。

一条命令安装(不用重启就能写进 profile,但注入行要等下次启动):

powershell -ExecutionPolicy Bypass -File "D:\Projects\DSH工作台\插件制作\dsh-video-skin\scripts\install.ps1"

# 可选参数:-Profile <名字>(默认 desktop)、-DshHome <DSH_HOME>(默认 $env:DSH_HOME)
#   —— 参数名是 -DshHome,不是 -Home:`$HOME` 是 PowerShell 的只读自动变量,赋值会让脚本直接中止。

它会:备份该 profile 的 package.json / pnpm-lock.yaml / cordis.patch.yml → 写 dependencies(link: 指向本包)→ 把包名加进 dsh.profile.bundles → 在 profile 的 node_modules 里建目录链接(junction,不需要管理员)→ 打印"请重启 DSH"。

先看一眼当前状态(不改任何东西):

node "D:\Projects\DSH工作台\插件制作\dsh-video-skin\scripts\profile-link.mjs" status --profile desktop

卸载 / 回滚(同样不用重装 DSH,但注入行仍是下次启动才消失):

powershell -ExecutionPolicy Bypass -File "D:\Projects\DSH工作台\插件制作\dsh-video-skin\scripts\uninstall.ps1"

不要动 desktop 以外的 profile 来验证:仓库里的 plugindev 是干净的开发 profile, 拿它试装不会影响你正在用的界面(见 §7)。

3. 调参:改一个 JSON,刷新页面

所有可调项都有默认值,不改也能用。要改,写这两个文件之一(后者覆盖前者):

文件 用途
<插件目录>/video-skin.config.json 部署期默认值(跟着插件走)
%USERPROFILE%\.dsh\dsh-video-skin.json 你自己的覆盖(推荐改这个)

改完刷新页面即生效(配置由一条 no-store 的路由在页面加载时读取)。 桌面版例外:注入行里的那份是开机快照,所以开机动画的参数要等下次启动。

包根目录的 video-skin.config.example.json 是一份逐项带默认值的模板, 改个名字放进上面两个位置之一即可。

{
  "boot": {
    "enabled": true,
    "file": "assets/boot.mp4",
    "fadeMs": 1000,
    "exitMode": "end",
    "skipFadeMs": 320,
    "holdMs": 18000,
    "holdGraceMs": 4000,
    "noFrameMs": 12000,
    "endlessMs": 20000,
    "hint": "正在启动…",
    "skipLabel": "点击画面跳过",
    "sound": true,
    "volume": 1,
    "soundLabel": "开启声音"
  },
  "background": {
    "enabled": true,
    "file": "assets/background.mp4",
    "veil": 0.14,
    "surface": 0.2,
    "surfaceLight": 0.6,
    "veilLight": 0.1,
    "delayMs": 1200,
    "playbackRate": 1
  }
}

开机动画

项 默认 作用
enabled true 关掉就是完全不注入那一行覆盖层,回到 DSH 原生启动页
file assets/boot.mp4 片子在包内的相对路径,可以换成你自己的
fadeMs 1000 淡出时长(固定值)。退场永远用这个时长,不会随"当时还剩多少"缩水
exitMode end end = 片子放完再淡(默认,动画一定看全);tail = 片尾交叉溶解(从"剩余 ≤ fadeMs"开始淡,片子最后 fadeMs 会盖在溶解里)
skipFadeMs 320 点击跳过时的淡出时长(要短,用户已经决定不等了)
holdMs 18000 界面迟迟不就绪时的上界。到点只把状态转成"启动较慢",不截断动画
holdGraceMs 4000 holdMs 之后再给片子的宽限;宽限用完才硬让开(这块屏是装饰品)
noFrameMs 12000 开始播但一帧都出不来,判定素材坏了并跳过。给得宽是因为宿主刚启动时可能应答得慢
endlessMs 20000 时长未知(duration = Infinity)时的唯一上界
hint 正在启动… 覆盖层底部那行状态文字(状态会自己变:正在启动 / 正在进入 / 界面就绪后自动进入 / 启动较慢… / 启动失败)
skipLabel 点击画面跳过 右下角常驻胶囊的文案
sound true 是否尝试带声音播放。桌面版能直接成功;浏览器拦下时自动退回静音并亮出左下角的声音开关
volume 1 音量 0..1
soundLabel 开启声音 声音被拦下时那个独立开关的文案

exitMode: 'tail' 且片子比 2 × fadeMs 还短时,尾部溶解会自动退化成"放完再淡"——否则溶解得 从片子还没出现就开始,那是坏效果。

素材报错不会立刻切掉动画:先重新挂一次 src 重试,只有第二次也失败才交接 —— 宿主刚启动时那条本地素材路由偶尔应答不上,一次抖动不该让开机动画提前退场。

聊天背景

项 默认 作用
enabled true 关掉就不建背景层、也不动任何主题变量
file assets/background.mp4 背景片子在包内的相对路径
veil 0.14 暗主题视频蒙版不透明度(压黑)。越大越易读、画面越弱
surface 0.2 暗主题界面表面不透明度。0 = 表面全透明(画面最强),1 = 原生不透明(看不见视频)
surfaceLight 0.6 亮主题表面不透明度。默认 = surface + 0.4。亮主题必须更实,原因见下
veilLight 0.1 亮主题蒙版不透明度。默认 = veil × 0.7
controls true 是否显示右下角的透明度调节控件(圆钮 + 滑块面板)。关掉就只能改 JSON
delayMs 1200 界面就绪后多久开始加载背景视频(把开机阶段的带宽留给界面)
playbackRate 1 播放速度

为什么亮主题要单独一个值:文字在暗主题是浅色、在亮主题是深色,而这条素材整体偏暗 (实测平均亮度 YAVG ≈ 0.25)。同样薄的白色表面放在亮主题上,有效底色会变成中灰, 深色文字的对比度立刻掉到 3:1 以下;深色表面则不会。实测(同一帧,真 Chromium 采样):

surface(暗) surfaceLight(亮) 暗主题 最小 / 5% / 中位 亮主题 最小 / 5% / 中位
0.2 0.4 2.52 / 6.85 / 16.06 4.34 / 4.52 / 5.37
0.2 0.5 2.50 / 6.81 / 16.05 5.87 / 6.06 / 6.91
0.2 0.6 2.49 / 6.81 / 16.05 7.74 / 7.91 / 8.72
0.05 0.6 1.53 / 4.89 / 15.24 7.38 / 7.56 / 8.37
0 0.55 1.22 / 4.14 / 14.71 5.87 / 6.06 / 6.90

读法:5% 分位才是有意义的可读性指标(画面 95% 的区域),正文要求 4.5。上表也顺带说明 为什么暗主题的蒙版不能去掉 —— 一旦 veil 归零,暗主题的 5% 分位就掉到 4.14 了。 "最小"那列对应的是画面里极少数的镜面高光像素;在"让视频真的看得见"这个前提下它满足不了 AA, 想满足只能把蒙版加到 0.4 以上 —— 那正是"完全看不清视频"的那一版。这里选择 5% 分位与中位达标。

这五个值都可以改完刷新页面即生效(不必重启),调参时用 node scripts/verify-browser.mjs --url <带令牌地址> --sweep 直接打印上面这张表。

3.1 界面上直接调(不用改 JSON、不用重启)

右下角有一个半透明的小圆钮(鼠标靠近才变实),点开是调节面板:

面板里的东西 说明
面板不透明度 滑块 调 surface;亮主题跟着按 +0.4 派生,滑块下方把两个主题的实际值都写出来(暗色 20% · 亮色 60%)
视频蒙版 滑块 调 veil;亮主题按 ×0.7 派生
三个预设 「接近原生」0.85/0.5、「均衡」0.2/0.14(默认)、「视频最清」0/0
文字对比度 xx(结论) 实时量出来的:采样当前视频帧,按「视频→蒙版→表面」合成后算 WCAG 对比度。绿=很充裕(≥7)、蓝=够用(≥4.5)、琥珀=偏低
恢复默认 把写进用户配置的那几项摘掉,值退回部署期默认

交互细节(都是特意做的):

  • 拖动即时生效(input 事件只改内存),松手才写盘(change 事件)—— 拖动过程不会打出几十个请求。
  • 写盘走宿主的一条 POST/DELETE /dsh-video-skin/override 路由,落到 $DSH_HOME/dsh-video-skin.json。只写那四个旋钮,文件里别的内容原样保留。 选文件而不是 localStorage:桌面壳的页面来自 dsh-app:// 自定义协议,Web Storage 可用性不确定; 写文件在哪个形态下都一样,而且用户能直接打开看/改。
  • 写盘失败(只读目录之类)时面板会如实说"没能写进配置文件",本次会话仍然生效 —— 不假装成功。
  • Esc 收起面板;点面板外面也收起(面板会盖住输入框右下角,用完就走)。
  • Alt+B 也能开合面板;不想要这个控件就设 background.controls: false。
  • 控件在模态之下(圆钮 z-index: 900,内核的 Modal 遮罩是 1000)—— 模态打开时圆钮点不到,这是故意的:模态就该盖住一切。

它不写死坐标也不抢焦点:圆钮挂在 document.body 上、和背景层同一层,不注入 DSH 的设置页 (那页是 React 管的,往里塞节点迟早出问题)。

4. 它是怎么接进去的(以及几个不许改的约束)

Host 半边 (index.js)
  ├─ ctx.on('webserver/index-inject', table => table.push(styleRow, scriptRow))   ← apply() 第一件事,同步
  └─ ctx.inject(['webServer'], …) → 三条 exact 路由
        /dsh-video-skin/boot.mp4        Range / ETag / 206 / 416 / HEAD
        /dsh-video-skin/background.mp4  同上
        /dsh-video-skin/config.json     no-store,允许改 JSON 刷新即生效

注入行只有两行,都在同一次 push 里:

  1. { kind: 'style', text: <skin.css> }
  2. { kind: 'script', placement: 'body', text: <bootstrap JSON> + <skin.js> }

三条硬约束(每条都是踩出来的,改之前先读 lib/injections.js 和 .recon/desktop-boot-recon.md):

  1. 绝不能用 kind:'script-src'。 桌面壳的解释器对 script-src 是 await loadScript(src), 失败即 reject,而那个 reject 会把 __DSH_BOOT_READY__ 打掉 → 整个应用起不来。 kind:'script' 是 createElement + textContent + append,没有 await,不可能"加载失败"。
  2. 订阅必须同步注册在 apply() 里。 对象级 inject 会把 apply 推迟到服务就绪之后, 而桌面壳的注入表在宿主启动时收集一次,晚一秒就永远进不了表。
  3. 覆盖层挂在 document.body 上,不放进 #root。 内核用 hydrateRoot 接管 #root 里 已有的启动页 DOM(判据是 :scope > [data-dsh-boot]),往里塞节点会造成 hydration 不匹配。

聊天背景的做法(见 runtime/skin.css):

  • 背景层是 position: fixed; z-index: -1 的 body 第一个孩子。-1 让它落在所有在流内容之下, 所以不需要给应用任何东西加 z-index,也不会挡住任何交互(含挂在 body 上的 portal)。
  • 表面半透明化改的是 --dsw-alias-bg-base / --dsw-alias-bg-layer-1..3 / --dsw-specific-sidebar-fill 这几个别名 token。内核主题把整套色板定义在 body / body[data-ds-dark-theme] 上,全应用的可视表面 都引用别名 —— 覆盖别名等于一次改到所有面板,不需要知道任何组件类名。自定义属性上的 !important 会压过主题里的普通声明(与选择器特异性无关)。
  • 表面不透明度是 JS 算好再写成独立变量(--dsvs-* 给暗主题、--dsvs-l-* 给亮主题), 不在 rgba() 里写 calc(var(...) + .1):自定义属性是 token 流,calc() 会被原样替换进颜色函数, 能不能解析取决于实现;算好再写既没有这层不确定性,值本身也可以直接被断言。
  • 亮/暗两套值不是化简失误:文字在两边颜色相反,而素材整体偏暗(实测 YAVG ≈ 0.25), 同样薄的白色表面会让深色文字掉到 3:1 以下。实测依据见 §3。
  • 刻意不动 --dsw-alias-bg-overlay、--dsw-menu-surface-fill、文档预览与遮罩类 token: 弹层和菜单要保持不透明,否则弹窗里的字会压在视频上。

5. 出问题了怎么查

现象 原因 / 处理
完全没有反应 注入表是开机快照 —— 完全退出并重开 DSH。第一次装完 / 改过 boot.* 之后都必须重启
有背景没有开机动画 只在页面加载时出现,切会话不会重放;确认 boot.enabled 没关,并且 assets/boot.mp4 在
开机动画没声音 桌面版应当一上来就有声。若是用浏览器打开 dsh web,Chromium 的默认策略会拦未静音的自动播放 —— 左下角会出现「开启声音」开关,点一下即可(点它不会跳过)。想彻底安静就设 boot.sound: false
左下角出现「开启声音」 正常:这是浏览器拦下自动播放时的降级路径。点它开声音,点画面跳过,两者互不影响
开机动画卡住进不去 不应该发生:点击/Esc/空格/回车 任何阶段都能进,超 holdMs 也会自动让开。若真卡住,加 ?dsvsdiag=1
字看不清 暗主题调高 background.surface 与 veil;亮主题调高 surfaceLight(0.75 以上基本回到原生观感)
画面太淡 / 看不清视频 调低 surface(暗主题可到 0.1,甚至 0)与 veil;亮主题相应调低 surfaceLight
黑屏没画面 素材没经过 faststart。node scripts/verify-all.mjs 会检查 moov 是否在 mdat 之前
想彻底关掉 uninstall.ps1,或把两个 enabled 都设成 false

诊断模式:地址后面加 ?dsvsdiag=1,底部提示行会变成实时读数 (phase / ready / done / t / dur / rs / err / why / log)。 出问题时截一张图就能说清楚卡在哪一步。

6. 自检

cd "D:\Projects\DSH工作台\插件制作\dsh-video-skin"
node scripts/check-syntax.mjs     # ESM 能求值 + 注入文本能解析
node --test "tests/**/*.test.mjs" # 81 个用例
node scripts/verify-all.mjs       # 上面两项 + 素材/打包/patch 契约(14 项)

测试里最有价值的两组:

  • tests/runtime.test.mjs —— 把 runtime/skin.js 放进桩 DOM + 虚拟时钟里真跑,断言 "剩余 1600ms 不淡、1200ms 立刻淡,并且淡出时长是 1200ms(真实剩余时间,不是配置的 1500)"、 "点击用 skipFadeMs"、"先试未静音、被拒才退回静音、超时窗口到点必须退回静音"、 "点声音开关不跳过而点画面才跳过"、"淡出时音量一起淡到 0"、 "时长未知 / 一帧不出 / 素材报错 / 内核不就绪 四条路径都有上界"。这些性质正则证明不了。
  • tests/host.test.mjs —— 用真的 apply(),把注册出来的路由挂到真的 http 服务器上打真请求: 200/206/416/304/HEAD、Cache-Control 的两种语义(整片 no-cache、切片必须 no-store)、 以及"注入行里不可能出现 </script"。

6.1 真浏览器端到端(推荐在改样式/素材之后跑)

桩 DOM 证明不了"CSS 到底生效没有""Chromium 到底能不能解码这段片子""音频轨到底有没有被解码" "点击有没有真的落到覆盖层上"。scripts/verify-browser.mjs 用真的 Chromium(headless)+ CDP 打开一个真的 DSH 页面:桌面等价环境 75 条 + 普通浏览器 65 条断言。

# 1) 造一个独立、可抛弃的 DSH_HOME + profile(只挂本插件)
node scripts\make-verify-profile.mjs
# 2) 起它(不碰正在用的那个 DSH)
$env:DSH_HOME='D:\Projects\DSH工作台\插件制作\dsh-video-skin\.verify-home'
& 'D:\AI\Agent\DSH官方\resources\runtime\cli\bin\dsh.cmd' --profile verify --no-open --port 19388
# 3) 用它打印出来的带令牌 URL 跑验证
node scripts\verify-browser.mjs --url "http://127.0.0.1:19388/?token=..." --shot-dir docs

两种模式都要跑,因为桌面版和浏览器是两条不同的自动播放策略:

模式 命令 断言什么
--autoplay allow(默认,等价桌面壳) --autoplay allow 素材未静音播放、不出现声音开关、webkitAudioDecodedByteCount > 0(音频轨真的被解码)
--autoplay block(普通浏览器默认策略) --autoplay block 自动退回静音继续播、左下角亮出声音开关、用真鼠标事件点它不会跳过、点后变成未静音

它断言的东西还包括:覆盖层铺满视口且层级压倒一切;点击视口中心真的触发跳过且用的是 skipFadeMs; 淡出后覆盖层整个从 DOM 移除;背景层是 body 第一个孩子且 z-index:-1; --dsw-alias-bg-* 真的解析成半透明(自定义属性是 token 流,getPropertyValue 拿到的是没解析的声明, 所以它把 token 用在真实属性上再读计算后的值);弹层 token 保持不透明; 背景视频真的解码出画面(readyState/videoWidth/时长);应用真的挂进了 #root。

§3.1 那个调节控件也有专门的端到端用例(E 段 23 条,传了 --dsh-home 才跑):

用例 断言什么
E1–E4 圆钮存在、面板默认收起、两个滑块、圆钮平时半透明
E5–E7 被内核模态(z-index:1000)盖住时不抢它的点击;移除模态层后圆钮可点 —— 用 elementFromPoint 实测,不是猜
E8–E10 用真鼠标点圆钮真的展开;面板给出实时对比度;读数里把暗/亮两个主题都写出来
E11–E14 拖到 0 时暗主题真透明、亮主题跟着走到派生的 0.4(这条正是"只写 surface 会让亮主题纹丝不动"那个 bug 的守卫);蒙版 60% 时亮色派生 42%
E15–E17 值真的落到 $DSH_HOME/dsh-video-skin.json,只有那四个旋钮,文件里别的内容原样保留
E18–E21 刷新之后值还在(写盘 → 运行期配置的闭环),两个主题都维持
E22–E23 「恢复默认」把值退回部署期默认,并把文件里那几项摘掉

顺手还会把视频帧画进 canvas 采样,按"视频 → 蒙版 → 表面"合成后和正文色算 WCAG 对比度, 给出一张"文字到底还能不能读"的实测表。本机实测(默认值:暗 surface 0.2 / veil 0.14,亮 surfaceLight 0.6 / veilLight 0.1):

主题 最低 5% 分位 中位
暗色 2.84 6.86 16.06
亮色 7.72 7.90 8.70

正文要求 4.5(WCAG AA):两边的 5% 分位都在 6.8 以上,也就是画面 95% 区域的对比度是要求的 1.5 倍, 而视频同时是明显可见的。暗色的"最低"2.84 对应画面里极少数的镜面高光像素 —— 见 §3 里那张扫描表的说明。 --sweep 会打印完整的(surface, surfaceLight, veil)对比度表,调参不必靠猜。

6.2 装机预演(改完接线、重启之前跑)

桌面版的 profile 由 Electron 应用独占,dsh --profile desktop --dump-config 会被明确拒绝, 所以"装完之后还能不能起来"在重启前没法用 CLI 回答。scripts/rehearse-profile.mjs 用内核自己的 loadProfileDirectory(就是启动路径上第一个会抛的那一步)离线组合一遍:

$env:ELECTRON_RUN_AS_NODE='1'
& 'D:\AI\Agent\DSH官方\DeepSeek Harness.exe' scripts\rehearse-profile.mjs desktop

它断言:本插件的 bundle 层解析得到、没被 skippedBundles 收走、patch 行 id/name 与包名一致、 Host 入口与四个素材都在盘上。退出码 0 才去重启。

7. 在干净 profile 上先试一遍

$dsh = 'D:\AI\Agent\DSH官方\resources\runtime\cli\bin\dsh.cmd'
& $dsh plugin --profile plugindev add "link:D:\Projects\DSH工作台\插件制作\dsh-video-skin"
& $dsh --profile plugindev web --port 0        # 打印带令牌的 URL,用它开一个独立实例看效果

不想动正在用的 DSH 时,这条路最安全。

8. 素材

素材 来源 处理 结果
assets/boot.mp4 videos/开机动画.mp4 1280×720 保持原分辨率,H.264 CRF 20,保留音轨(AAC 160k 立体声),+faststart 2.56 MiB(原 8.00 MiB)
assets/background.mp4 videos/聊天背景.mp4 缩放到 1600×914,H.264 CRF 30,去音轨,+faststart 10.15 MiB(原 226 MiB)
  • 开机动画保留音轨:用户要求开启声音。桌面版的自动播放策略允许未静音播放,所以一起就是有声的。
  • 聊天背景去掉音轨:用户要求"不要声音,有画面即可";没有音轨也就不需要在循环播放入口再考虑一次自动播放策略。

node scripts/verify-all.mjs 会把这两条期望钉死:开机必须有音轨、背景必须没有音轨、两条都必须 faststart。

想换成自己的片子:把 mp4 放进 assets/,改配置里的 file,然后重跑一次 node scripts/verify-all.mjs。

9. 许可

MIT。见 LICENSE。