Zum Hauptinhalt springen
S

dsh-see-now

saos-3624/dsh-see-now

Lässt DSH einen Live-Bildschirm oder Kamerastream beobachten: bewertet jedes Einzelbild nach dem Ausmaß der Bildänderung, behält nur relevante Frames und bündelt sie in einem Bild. Die KI sieht den Bildschirm live und erhält nur Frames, in denen sich das Bild geändert hat.

Installation

dsh plugin --profile web add github:saos-3624/dsh-see-now

README

See Now · 让 AI 实时看见你的屏幕

AI 不再"听你描述屏幕",而是自己看 —— 而且只传"画面变了"的那几帧。

一个给 DSH 用的实时视觉插件:把你的屏幕(或摄像头 / 视频文件)变成一串按变化量筛选过的画面,压进一张几帧拼成的图,交给模型。

静止的屏幕几乎不花钱;一镜到底的视频按"运动量"给帧;硬切幻灯片每切一次留一帧。


授权说明 / License

Apache License 2.0 —— 随便用。个人、公司内部、闭源产品、SaaS、二次分发都可以, 不需要问我要授权,也不收费。只要保留 LICENSE 和 NOTICE 这两份文件。

如果你觉得它替你省了时间,欢迎扫码打赏 —— 纯自愿,不影响上面任何一个字:

赞赏码(微信 / 支付宝)

提交 PR 前读一下 CONTRIBUTING.md —— 现在不需要签 CLA 了, 按 Apache-2.0 的规矩来就行。


看看它长什么样

调试窗(开发期用,不在发布包里):左边是抓到的每一帧,留下的带缩略图和格号,没留下的只剩一根分数条;右边是正在拼的那张图,按真实像素位置摆,新帧落格会亮一下,重排时格子会滑动。

调试窗

逐场景模式(scene:true):每换一次画面单独出一张满幅图,右下角「已出图」每条都标着 1 帧 100%。

逐场景模式

悬浮窗:AI 说的话在这里出现并被念出来(say 是唯一通道)。

悬浮窗


它解决什么问题

想让 AI 陪你看屏幕,最直觉的做法是"定时截全屏喂过去"。两个死结:

做法问题
每 2 秒截一张全屏一分钟 30 张图,全是重复画面,token 直接烧穿
每隔 30 秒截一张中间发生了什么全丢了,AI 只会说"看起来你在用电脑"

See Now 换了个思路:先判断这一帧值不值得看,再决定传不传。

  • 每个画面帧先打一个 0–255 的"变化分"(HSV 三通道加权差,在 64×36 网格上算,约 0.2ms)
  • 只有"画面突然换了一张"或"一直在动、攒够了量"才留下来
  • 留下来的几帧拼进同一张图,一张图的固定成本摊到 4~8 帧上

结果:同样一段 30 秒的操作,别人烧 30 张图,这里常常只要 2~3 张。


有多省

场景表现
静止屏幕(看文档、发呆)几乎不出图。20 秒给一张兜底帧,让你知道"确实没变"
滚网页、切窗口变化剧烈 → 留几帧 → 每 15 秒左右给你一张
一镜到底的视频没有切点,靠"积分"攒够一个采样点才出帧
硬切幻灯片(每张停 2 秒)每切一次留一帧,6 张照片 → 6 帧

成本口径:800×450 一张 ≈ 233 token(按宿主 DeepSeek-V4 的 patch 网格算:14px patch 向上对齐 → 42px 一格 → rows×(cols+1)+2)。 一张图里塞 4 帧还是 8 帧,价格一样 —— 所以"多留一帧"几乎免费,"漏掉一帧"才贵。

插件源码里 SIZES 的旧注释写着 218 token,那是早期按"像素 ÷ 1650"估的,偏低。以 233 为准。


快速开始

在 DSH 里用

已经发布到 npm,用 DSH 自带的 dsh plugin 装(它本质是 profile 维度的 pnpm 包装器 + 插件登记器,会在装前预检 engines/peer、装后自动把 dsh.bundle 写进 profile):

dsh plugin --profile web add dsh-see-now
  • web 是浏览器里用的那个 profile;桌面版换成 --profile desktop(那个 profile 需要先开过一次桌面端才会被初始化)。
  • 装完重启 DSH 生效(插件树在进程启动时组合)。
  • 重启之后不用新开会话:工具表是每一步重新组装的,旧会话下一轮就能看到 see_now (宿主还会插一条 tool-addition 告诉模型多了这个工具)。所以「工具表在会话开头就定死了」 不成立 —— 挂不上多半是没重启,或者装的是旧副本。
  • 卸载:dsh plugin --profile web remove dsh-see-now
  • 升级:dsh plugin --profile web update dsh-see-now

关掉悬浮窗之后想再打开:点 × 只是这一次不看,插件不会自己弹回来。手动叫:

npx dsh-see-window            # 开窗(已经开着就什么都不做)
npx dsh-see-window --status   # 只看它在不在
npx dsh-see-window --force    # 杀掉重开
npx dsh-see-window --close    # 关掉
从源码装(改代码时用)
git clone https://github.com/SAOS-3624/dsh-see-now.git
cd dsh-see-now
node scripts/install-dev.mjs      # 拷进本机 DSH 的 desktop profile

install-dev 是复制不是软链(Node 解析裸包名要走真实路径,软链会找不到宿主的 @deepseek-ai/dsh-tools)。装完同样要重启 DSH。

平台
平台屏幕引擎要额外做的
Windowsddagrab,不行退 gdigrab(自动探测)无
macOSavfoundation系统设置 → 隐私与安全性 → 屏幕录制,勾上跑 DSH 的 App
Linux—还没接(要补 x11grab / PipeWire)

ffmpeg 不用进 PATH:插件会按 DSH_FFMPEG_PATH / FFMPEG_PATH / DSH_FFMPEG_DIR / ~/.dsh/tools/ffmpeg/bin / ~/.dsh/bin 的顺序自己找(DSH 桌面版把 ffmpeg 装在第四个位置)。

然后让模型调:

see_now({ action: "watch" })          // 阻塞到你攒够一张图,然后把图交回来
see_now({ action: "watch", say: "…" }) // 同时说一句话(只有 say 会进悬浮窗、会被念出来)

第一次调用约 1 秒返回当前屏幕;之后每次返回"这段时间里发生的事"。

不进 DSH,当普通 Node 库用

包本身不依赖 DSH,capture / pipeline / png 都是独立导出的:

import { spawnFrames } from 'dsh-see-now/capture';
import { createPipeline } from 'dsh-see-now/pipeline';

const p = createPipeline({ width: 800, height: 450, fps: 12, queueDepth: 0 });
const run = spawnFrames({
  source: { kind: 'screen' },           // 或 { kind:'file', path:'x.mp4' }
  width: 800, height: 450, fps: 12,
  onFrame: (f) => p.push(f),
});

setInterval(() => {
  const sheet = p.take();               // 一张 PNG + 每帧的时间戳/档位/来源
  if (sheet) require('fs').writeFileSync(`sheet-${sheet.at}.png`, sheet.png);
}, 200);

声音

核心不内置任何 TTS 引擎,只 dynamic import 一个你指定的模块。配好之后, say 会进悬浮窗并且被念出来;普通回复永远只有字。

// ~/.dsh/voice.json
{ "adapter": "/绝对路径/examples/voice-say-mac.mjs" }

自定义模型:适配器自己的配置走 options,原样交给 attach(api, options) —— 换自己训的模型不用改适配器源码:

// ~/.dsh/voice.json
{ "adapter": "D:/my-voice/index.mjs", "options": { "model": "D:/my/models/xxx" } }

macOS 自带一个:examples/voice-say-mac.mjs,走系统的 say,音色和语速从 ~/.dsh/tts.json 读(和 dsh-watch-together 共用同一份):

// ~/.dsh/tts.json
{ "voice": "Tingting", "rate": 185 }

say -v '?' | grep zh_ 可以列出本机所有中文音色(Tingting / Meijia / Sinji / Reed / Rocko…)。

改完不用重启:每次调用工具前都会比对一次配置指纹(适配器路径 + tts.json 内容摘要), 变了就自动卸载重挂,没变就一次读文件的开销。所以改音色、换适配器,下一句就生效。

适配器要自己处理两件事:串行(两个 say 抢音频设备会像结巴)和 spoken 上报 (在出声的那一瞬发,不能等念完 —— 展示层是"字跟着声音走"的)。


听声音(视频里在说什么)

上一节是它说给你听,这一节是它听你在看的东西。

图只能说明画面,说明不了"他们在说什么"。开着这个之后,see_now 的清单里会多一段 台词,时间码和每帧那个 24.1s 是同一个坐标系 —— 模型能把一句话对到某一格画面上。

过去 24.1–45.6 秒的 4 个画面 · 798×462
  1. 24.1s · 400×225 · cut · 2 档
  ...
台词(这段时间里视频里在说的,机器转写,可能有错字/听岔):
  24.4s  评价一个镜头的好坏,一般来说有两个维度
  31.2s  第二个维度是艺术维度

要什么:一条 WASAPI 回环(抓的是"电脑正在放什么",不是麦克风)+ 一个转写服务。

  • 转写:DSH 桌面版自带 speechToText(SenseVoice),不用配任何东西; 命令行/其他宿主就在 ~/.dsh/asr.json 里指一个 OpenAI 兼容服务 (和 dsh-video-storyboard 共用同一份配置):
    { "baseUrl": "http://127.0.0.1:8000/v1", "key": "local-key", "model": "paraformer-zh-2024-03-09" }
    
  • 抓声音:pip install pyaudiowpatch numpy。不需要装虚拟声卡,也不需要开"立体声混音"。 装到哪个解释器都行,插件会自己找;找不到就把它写进 ~/.dsh/see-window.json:
    { "listenPython": "C:/Python313/python.exe" }
    

开销(i9-12900HX / Win11,实测):抓回环 0.3% 单核,转写 3 秒段 130–160ms。 跟画面采集(0.18–0.43 核)比是零头。

延迟("这句话说完" → "模型能读到它"):

环节耗时说明
静音判定450ms要等这么久才确认"说完了"。这是大头,也是唯一能明显缩短的一项
写盘 + 扫目录5ms + 平均 100mspollMs=200,Python 是写完 .tmp 再 rename,读不到半截
转写130–250ms3 秒段 130ms、10 秒段 400ms
合计约 0.7–0.8 秒从最后一个字到文字可用

这不影响实际使用:图本身是攒 15–40 秒才发一张,0.8 秒的转写延迟完全被淹没 —— 台词跟图走, 图覆盖"过去 30 秒",台词也是"过去 30 秒"。真正能感觉到的只有一处:一句话刚说完的那 0.7 秒内 问"它刚才说什么",还拿不到。

要更快就把 createListener({pollMs}) 调小(最低 100 左右,再低只是空转), 或者把 listen.py --silence 从 0.45 降到 0.3 —— 代价是句子更容易被切成两半。

没有或不想用:DSH_SEE_AUDIO=0 关掉;或者干脆不装那个 Python 包。 两种情况都只是没有字幕,看图一切照旧 —— status 里的「听声音」那一行会直说是 缺服务、缺包、还是这台机器抓不到回环:

听声音:在听(宿主 SenseVoice,累计 42 句,最近一句 3s 前)
听声音:没在听 —— 没有能用的 Python(要 pip install pyaudiowpatch numpy)

已知限制:

  • 只有 Windows。macOS 抓系统声音要装 BlackHole 之类的虚拟声卡(而且要手动配置多输出设备), 这一版没做 —— 在 mac 上「听声音」会直接报"抓不到回环",其余功能正常。
  • 抓的是整个系统的输出,不只是视频:QQ 提示音、BGM、游戏音效都会进台词。 视频里没人在说话时,转写多半是空的(服务端有 VAD),偶尔会对着音乐吐出几句幻觉歌词。
  • 它听得见自己:TTS 念台词时音箱会出声,那段会被回环收回去。所以落盘前查 quiet-until.json(和 mic.py 共用),念的时候整段丢弃 —— 否则 AI 会把自己的话 当成"视频里说的",然后接着聊下去。
  • 转写是机器识别,同音字会错("技术维" / "技术为")。模型被明确要求不要把它当原文引用。

参数

参数说明
actionwatch(默认,阻塞取图)/ peek / start / stop / status / window / region
show配 action:"window":true 把悬浮窗叫回来,false 藏起来,不传只报状态
region只看屏幕上一块:"x,y,w,h"(屏幕物理像素),或 "off" 取消。一般不用手填 —— 说"框这块"让它自己框,见下
say唯一能进悬浮窗、能被念出来的通道。普通回复不进气泡,但会被记进小窗的对话记录
qualitylow(800×450) / standard / high / ultra。改它会重启采集(~0.6s),并立刻返回当前这一帧
pace节奏:calm / normal(默认)/ dense。见下
scenetrue = 逐场景模式:一张图只装一个画面、铺满。见下
secondswatch 的阻塞上限(默认 300,封顶 860)。只是上限,攒够一张就提前返回
minFrames少于这么多帧的图就别给我了(省 token)
source / path / realtime采集源:屏幕 / 摄像头 / 回放一个视频文件

只看一块(region / action:"region")

只想让它盯住屏幕上的一个窗口(手机竖屏通话、播放器的画面、某个聊天窗口)时, 不用自己量像素:说一句"框这块",模型会调 see_now({action:"region"}), 屏幕变暗后拖一个框,松手就行。坐标存在 ~/.dsh/see-now/region.json, 之后每次 watch 默认只看这块,region:"off" 取消。

三条边界(插件会如实告诉模型,模型也该如实转述):

  • 抓的是屏幕上正在显示的像素:这块被别的窗口挡住,看到的就是挡住它的那个窗口;
  • 没接 OBS,抓不到后台窗口、最小化的窗口;
  • 只支持主屏(负坐标表达不了,gdigrab 也不接受)。

带上区域后出图规则也变了:按区域宽高比出图(竖屏就出竖屏)、每帧长边固定 = 档位长边、 一张默认 2 帧最多 4 帧。token 会比全屏贵(帧不再被缩小):low 档 2 帧约 345 token。

三档节奏(pace)

档换画面门槛最多多久叫醒一次适合
calm20 分30 秒日常挂着,只想知道"大场面变了没"
normal(默认)12 分15 秒滚网页、切窗口不会每下都留帧
dense7 分8 秒幻灯片、逐帧播报 —— 贵,别常开

觉得它太吵、太烧钱,就说一句「慢一点 / 省点钱」,模型会传 pace:"calm"。 觉得它漏了、迟钝,就说「不灵敏」,它会传 dense。

逐场景模式(scene)

打开后每换一次画面单独给你一张满幅图,并且每换一次就叫你一次 —— 用来做"每翻一页说一句这是什么"。

see_now({ scene: true, say: "好,接下来每换一个画面我就说一句" })

它是粘性的,一直生效到 scene:false 或停止。 单帧铺满靠的是 strategy: 'single'(档位固定压到 1),不然场景一密,格子会缩到 1/4、1/9,中间全是灰条。

自己的节奏:~/.dsh/see-now.json

有三件事是"每次会话都一样的偏好",不该让模型每次传参:多久自己收工、 默认用哪一档节奏。写进这个文件(改完重开 DSH 生效,不进 profile, dsh plugin update 不会覆盖):

{
  "maxAutonomousWakes": 24,
  "idleStopMin": 10,
  "defaultPace": "calm"
}
字段默认说明
maxAutonomousWakes8连续这么多次自动叫醒都没人理 → 收工。悬浮窗开着时,这条就是实际的最长陪看时长(normal 15 秒一次 ≈ 2 分钟)。1–200
idleStopMin3没开悬浮窗时,用户这么久没说话 → 收工。0 = 关掉这条
defaultPacenormal新会话/重开采集时的默认节奏:calm / normal / dense / game-economic。调用里显式传 pace 仍然优先

see_now({action:"status"}) 的「收工线」那一行会显示当前生效的值, 改过配置会带上文件路径 —— 否则"怎么陪一会儿就自己停了"很难查。

非法值会退回默认并在日志里留一条 warning,不会把插件弄挂。


它是怎么判断"变了"的

两条判据,缺一不可:

① 切点 —— 这一帧的分数跳得又高又孤立

score ≥ cutAbs(12)                 // 绝对地板:变化够不够大
score > 2.5 × max(1, 前后帧)        // 尖峰:是不是"一下子"
mc ≥ cutSpikeMcAbs(20)             // 尖峰也要有 mc 覆盖,防过渡帧先切
距上一刀 ≥ 0.4 秒                   // 不应期

// 补充出口(相机/机位场景)
score ≥ 6 && mc ≥ 60               // 运动补偿:整屏在动但邻域匹配不上 = 换镜头
score ≥ 80 && 跳 ≥ 12 && ≥ 1.5×邻居  // 强台阶:兜住 mc 关掉的配置
6 ≤ score < 12 && score > 3×邻居 && mc ≥ 8 && 包围盒 ≥ 20 && 填充率 ≥ 0.2  // 相似构图:压缩块,不是稀疏 UI 条

mc 是运动补偿后的"变了多少格"占比(0–255),搜索半径默认 4 格: 整屏平移 8 格时 radius=2 会误报 194,radius=4 只有 5;换镜头则两边都高。 B 站游戏片里 30.33s 的切机位只有 23.2 分,但 mc=121,靠这条能留下。

前景运动(手、勺子、遮挡物)也会让 mc 变高,所以低/中分切点再加一道背景模型: 最近 6 帧里一直没怎么动的格子算稳定背景;如果当前帧只改掉一部分稳定背景 (bg < cutBgAbs=100),绝对变化格数不超过 cutBgMaxChanged=600, 且变化区域没盖满高度(mcSpanY < 0.85), 就按前景运动处理,不切。真正换镜头会把稳定背景整片换掉,或至少盖满画面高度。 宽银幕的上下黑边不计入这个高度(contentHeightRatio 归一),否则 Sintel 这类 letterbox 片子的正常切点会被误挡。全黑帧仍按 0 跳过;暗场上下发黑、中间有亮内容时 改用 guardHeightRatio,不再因为边缘暗就跳过守卫。

高分候选(score ≥ 60)再等 0.5 秒做一次未来帧确认:用最近 24 帧的参考背景 看候选帧改掉的部分有没有回来。手/勺子离开后画面回到旧背景,就不切;真正换镜头 在新场景里稳定下来,仍然切。这项确认只作用于高分候选,低/中分仍走当前帧守卫。

前景停住超过 0.5 秒时,未来帧不会回到旧背景,但外圈通常还是旧背景: bgKeep 之外再看 bgBorderChanged,背景变化不到整片且外圈几乎没动时也判前景。 这条只在 bg < 220 且外圈 < 10 时生效,避免挡整屏换镜头的真切点。 外圈已经判成前景的候选不再等未来帧;真正的高分切点仍最多等 0.5 秒。

低/中分候选如果外圈也被改掉(border ≥ 60)、稳定背景很少(≤ 700)、 背景变化又不到整片(bg < 160),也等 0.5 秒做一次未来帧确认; 只有一部分旧背景回来(keep < 200)才判前景。这是在真实带货直播里 大手/产品盖住外圈时补的,静态外框的烹饪/开箱真剪不会被这条带走。

② 积分 —— 分不高但一直有,攒够才录一帧

need = 4分 × 12fps × 3秒 = 144      // 固定的绝对阈值,不跟着画面一起涨
每帧累加自己的分,累够就录、清零

为什么两个都要:尖峰是相对的,在低分区也会成立。实测缓慢放大(持续运动)有帧 s=13.6、前后邻居只有 1.1/1.3,尖峰判据完全放行 —— 没有绝对地板,它就会被切成一块块。 反过来只有地板也不行:一镜到底的视频每帧 4 分,永远不会"跳"。


调试窗(不在插件里)

npm run debug:live                                   # 看屏幕
npm run debug:live -- --source=file --file=x.mp4 --realtime=1
npm run debug:live -- --source=file --file=x.mp4 --scene    # 看逐场景模式

Windows 上可以直接双击 debug/调试窗.cmd,跟着菜单走。

窗口里有三样东西:帧流(每一帧都有一条,留下的带缩略图和格号)、正在拼的 sheet(按真实像素位置摆,新帧落格有动效)、两条曲线(每帧变化分 + 积分累积/阈值)。

代码在 debug/,不进 npm 包、不进插件,只是从外面驱动同一条流水线。


实测(Windows:i9-12900HX / RTX 4060 Laptop / 2560×1600)

采集开销(12fps → 800×450):

源首帧CPU
ddagrab(桌面复制 API)370ms0.28 核
gdigrab(GDI 抓屏)310ms0.45 核
OBS 虚拟摄像头575ms0.13 核

实测(macOS:M 系列 / Retina)

源首帧CPU
avfoundation 屏幕~700ms~0.16 核

首帧慢在 AVCaptureSession 冷启动(ffmpeg 进程本身 ~300ms),但稳态比 Windows 两种引擎都省。

像素格式是探针试出来的,不是写死的:屏幕输入不支持默认的 yuv420p,探针按 CPU 从省到费 (nv12 → uyvy422 → bgr0 → 设备默认)试到第一个真能出帧的,然后整个进程记住。

macOS 上有两个坑会伪装成"插件坏了",都写在 src/capture.js 里: ① -framerate 在 avfoundation 屏幕源上不压速(实测 30 帧只用 0.73 秒,≈40fps), 必须靠输出侧 -r 12 把速度压回来 —— 开发时少了这一条,管道被灌出过 240GB 的 raw 文件。 ② 没有「屏幕录制」权限时它不报错,exit 0、帧数正常,只是全黑。 探针会算平均像素,发现全黑就在 start 的返回里带一句提示,告诉你去系统设置里勾。

变化分刻度(0–255,同一套代码):

内容分数
静止桌面0.00(屏幕采集近似无损)
光标在静止画面上移动0.7
两张色调接近的照片切换16~18
滚网页 / 切窗口30~42
两张完全不同的照片切换46~63

所以 cutAbs: 12 这个默认值的位置是:照片切换抓得住,光标和微小抖动碰不到。


目录

src/
  capture.js    唯一 spawn ffmpeg 的地方,一根 rawvideo 管道
  binaries.js   找 ffmpeg(DSH 桌面版不进 PATH,所以不能写裸命令)
  cuts.js       逐帧变化分(64×36 HSV 网格)
  pipeline.js   唯一有状态的地方:帧 → 打分 → 切点/积分 → 留帧 → 档位 → pack → 出图
  tiers.js      档位(格子边长 1/k)与策略
  pack.js       DP 装箱:把 N 个不同大小的格子铺满画布
  sheet.js      合成 PNG(烧帧号、留缝)
  runner.js     采集管线 + 阻塞取图 + 节奏
  listen.js     听声音:扫 wav → 转写(宿主/本地 HTTP)→ 台词缓冲 → 挂到图上
  loop.js       常驻循环:有新图就唤醒模型
  index.js      工具注册与路由
examples/floating-chat/   悬浮窗(Tk)+ listen.py(抓回环、按静音断句)+ mic.py(麦克风)
debug/                    调试窗 + audio-check.mjs(手工验听声音,不在 npm 包里)
test/                     181 条测试

已知限制

  • 只认"画面变了",不认"语义变了"。同一句话的两个排版版本算变化,两段内容不同但长得很像的页面可能算没变。
  • 没有跨帧去重(除了 dupMin 那道粗筛)。它不保证给你的每一帧都"有新信息"。
  • 前景外圈守卫有边界:外圈也被前景盖住、或背景变化 bg ≥ 220 时挡不住; 反过来,真正切到"外圈固定、主体只在中部变"的近景,可能被这条误挡。
  • 参考背景会被长期前景污染:24 帧里前景占多数时模型会漂;外圈守卫还能挡一部分, 整屏前景仍然要等它离开。
  • 唤醒有节流:再密也不会超过 pace 里的间隔 —— 语音播报是串行的,更快只会排队。
  • 平台:Windows(ddagrab / gdigrab)和 macOS(avfoundation)都实测过; Linux 还没接(要补 x11grab 或 PipeWire,现在会直接报错,不假装能用)。
  • macOS 要多给一次权限:系统设置 → 隐私与安全性 → 屏幕录制,勾上跑 DSH 的那个 App。 没给的时候不会报错,只会全黑 —— 插件会提示,但权限本身只能你去点。
  • 听声音(台词)只有 Windows:靠 WASAPI 回环,mac 上要装 BlackHole 之类的虚拟声卡 再配多输出设备,这一版没做。mac 上「听声音」会直接报抓不到回环,画面功能照常。
  • 区域模式只能看到前台像素:没有 OBS 之类的采集源,被遮挡 / 最小化的窗口抓不到。 抓屏整体失败时(锁屏、UAC 安全桌面、远程桌面)窗口会退成一块纯色底板, 字还能读,但背后不是真实画面。

协议

Apache License 2.0 —— 商用、闭源、再分发、改完再卖都可以,保留 LICENSE 与 NOTICE 即可。

examples/floating-chat/ 来自同作者的另一个项目 dsh-watch-together,两边现在是同一个协议, 不再有"这个目录 Apache、其余非商业"的分裂。

愿意打赏就扫上面那个码 —— 不打赏一样能用全部功能,没有任何东西锁在付费后面。

Copyright © 2026 SAOS-3624

Ähnliche Plugins