MoreRSS

site iconControlNet修改

研究领域:计算机视觉 | 人工智能,有几个开源项目。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

ControlNet的 RSS 预览

ED Sentry: 给Elite Dangerous挂机用的监控工具

2026-07-02 21:50:34

ED Sentry是一个给Elite Dangerous[1]挂机用的监控工具。它会读取游戏在本地写的日志,统计挂机时的击杀、扫描和任务进度,出问题的时候通过Matrix提醒自己,也可以用一个仪表盘在电脑或者手机上查看。代码发布在GitHub上,项目地址。

背景

最近在玩Elite Dangerous的AFK Massacre,也就是挂机打清剿任务。具体的玩法是先接一堆清剿海盗的任务,然后开着Type-10停在资源采集点,让炮塔和舰载机自动去打来抢劫的海盗,人可以离开电脑去做别的事情。挂机的时候最担心的是中途出问题,比如舰载机被打掉了、护盾掉了,或者海盗一直不来,人不在电脑前面是不知道的。

已经有一个类似的工具叫ED-AFK-Monitor[2],它会读取游戏的日志,然后把挂机的状态推送到Discord。但是它是一个Python的单文件,Discord的推送逻辑和主流程耦合得比较紧,自己想要的是推送到Matrix[3],在它上面改的话代码只会更复杂。所以就决定用Rust重新实现一个独立的工具。

因为这个游戏基本上只能在Windows上玩,所以整个项目都是以Windows为主要目标来开发的,最终的产物是一个不需要Python环境的轻量Windows程序。

挂机之前: 检查清单

挂机之前需要确认船的状态是对的,不然走开之后挂一晚上可能什么都没打到。ED Sentry的仪表盘上有一个检查清单,只有三项:

  • 武器展开: 武器没有展开的话,炮塔是不会开火的。
  • 引擎能量为零: 能量都留给护盾和武器。
  • 货舱有货: 海盗会先扫描货舱,再决定要不要攻击。

dashboard
Fig. 1. ED Sentry的仪表盘,左上第二个面板是检查清单。截图里是示例数据

这三项是从游戏写的Status.json和Cargo.json里读出来的,文件一变化就会更新。每一项有通过、不通过和未知三种状态,因为游戏有时候不会写某些字段,比如Status.json里有时候没有引擎能量的数据,这时候就显示未知,不去猜。

这个位置原来是一个船体完整度的面板,但是这部分数据常年缺失,显示出来也不可靠,所以就换成了现在的检查清单。agent第一次做出来的检查清单,每一项下面都写了一大段说明,比如STATUS FLAGS BIT 0X40 IS SET STATUS.JSON这样的文字,后来全部删掉了,只留下三个简单的名字,挂机的时候谁会去看这些东西。

挂机的时候: 提醒和远程查看

提醒

ED Sentry会一直读取游戏最新的Journal日志,游戏每写一行新的事件,它就会更新统计,并且根据事件生成通知。比如击杀、被扫描、舰载机受损或者被摧毁、护盾掉了、货物丢失、燃料不足、死亡、任务进度变化等等。

除了这些事件,它还会在挂机效率出问题的时候报警。默认是开始挂机5分钟之后还没有击杀,或者之后20分钟没有新的击杀,就会报警。击杀的速度低于每小时20个,也会报警。

通知会同时输出到两个地方:

  • 终端: 带emoji的事件日志,最下面有一行实时的状态,显示击杀速度、扫描次数、挂机时间和任务进度。
  • Matrix: 发到一个Matrix房间里,手机上装一个Matrix客户端就能收到。

下面是终端输出的一段节选,来自自己的一次挂机记录,是用回放模式把当时的Journal重新跑了一遍得到的。从进入资源采集点开始,每一次击杀、货舱被扫描、燃料剩余都会记一行,击杀后面括号里是距离上一次击杀的时间,完成一个任务的击杀数也会提示出来,每隔一段时间还会输出一次统计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
===========================================
ed-sentry 0.1.1-20260706 by CMDR ControlNet
===========================================

[17:38:39]🔄 CMDR ControlNet (Type-10 Defender / Solo / Master +88%)
[17:39:08]🎯 Missions loaded (active massacres: 20)
[17:41:44]🚀 Supercruise entry in Anana
[17:42:06]⚔️ Dropped at Resource Extraction Site [Low]
[17:43:22]🚪 Exited to main menu
[17:43:31]🔄 CMDR ControlNet (Type-10 Defender / Solo / Master +88%)
[17:44:07]⛽ Fuel: 96% remaining (~3h4m)
[17:44:54]📦 Cargo scan
[17:45:15]🔎 Scan: Federal Dropship (Competent)
[17:45:59]💥 Kill: Federal Dropship
[17:46:30]📦 Cargo scan
[17:47:26]📦 Cargo scan
[17:49:25]💥 Kill: Diamondback Explorer (+3m26s)
[17:49:25]📦 Cargo scan
[17:49:51]💥 Kill: Asp Scout (+26s)
[17:50:14]📦 Cargo scan
[17:50:20]💥 Kill: Eagle (+29s)
[17:51:26]🔎 Scan: Diamondback Scout (Mostly Harmless)
[17:51:35]💥 Kill: Diamondback Explorer (+1m15s)
[17:51:51]💥 Kill: Diamondback Scout (+16s)
[17:52:35]📦 Cargo scan
[17:52:43]📦 Cargo scan
[17:53:18]💥 Kill: Sidewinder (+1m27s)
[17:53:47]💥 Kill: Imperial Courier (+29s)
[17:53:48]✅ Completed kills for a mission (1/20)
[17:54:10]📦 Cargo scan
[17:56:05]📦 Cargo scan
[17:56:58]⛽ Fuel: 95% remaining (~16h56m)
[17:57:20]💥 Kill: Cobra Mk IV (+3m33s)
[17:57:54]📦 Cargo scan
[17:59:03]💥 Kill: Asp Explorer (+1m43s)
[17:59:03]📝 Session Stats (13m4s) [1/20]
-> Kills: 10 (41.3/h | 1m27s)
-> Bounties: 1.2m (5.6m/h | 122k/kill)

每一种事件都可以单独设置等级,0是不通知,1是通知,2是通知并且在Matrix里@自己,这样舰载机被摧毁、死亡这种需要马上处理的事件就会响铃。Matrix里还有一条状态消息,它不会一直刷屏,而是每隔一段时间编辑同一条消息,更新当前的统计。Matrix的房间需要是不加密的,因为支持端到端加密会增加太多的复杂度。

远程查看

除了提醒,有时候也想直接看一下现在的状态,所以ED Sentry还有一个仪表盘,就是上面Fig. 1的界面。在Windows上,它是一个用Tauri[4]做的桌面程序。同时它也可以作为一个网页服务运行,在同一个局域网里,用手机的浏览器就能打开同一个仪表盘。

一开始agent给网页服务加了很多安全限制,比如只有本机才能修改配置。自己觉得设计得太保守了,这个服务本来就是给内网用的,所以就把这些限制去掉了,只保留了基本的Host和CORS检查。

如果人不在家,局域网就访问不到了。所以又加了一个隧道的功能,点一下START,就会用Cloudflare的Quick Tunnel[5]生成一个公开的链接,鼠标放上去还会显示一个二维码,手机扫一下就能打开。Quick Tunnel不需要注册账号,也不需要额外装什么,程序里已经带了cloudflared。公开链接可以设置一个密码,设置了之后,远程访问的人需要先登录才能修改配置。没有设置密码的话,就是谁拿到链接都能访问,风险由用户自己承担。

回来之后: 任务进度

挂机回来,最关心的是任务打到哪里了。MISSIONS页面会列出所有正在进行的任务,点进去可以看到每个任务的发布阵营、目标阵营、报酬和击杀进度。

missions
Fig. 2. MISSIONS页面,左边是任务列表,右边是任务的详细信息。截图里是示例数据

清剿任务可以叠很多个,但是同一个阵营发布的多个任务,一次击杀只会算给其中一个,不同阵营发布的任务才能同时推进。这部分的规则参考了ODEliteTracker[6]的做法,仪表盘上会按这个规则算出总的击杀进度,再根据现在的击杀速度估算还要多久才能打完。

使用方法

Windows可以在GitHub的Releases里下载ed-sentry-<版本>-windows-x64.zip,解压之后双击ed-sentry.exe就可以了。解压出来的文件需要放在一起,因为程序运行的时候会用到里面的webui/和tools/。Journal的文件夹默认就是游戏的存档位置,一般不需要改。

如果只想用终端,可以直接运行核心程序:

1
.\ed-sentry-core.exe --config config.toml

Linux的版本只有核心程序和网页,没有桌面窗口,需要在配置里写上Journal的路径。

配置都在config.toml里,Matrix和隧道的部分大概是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[web]
enabled = true
host = "127.0.0.1"
port = 8765

[matrix]
enabled = true
homeserver = "https://matrix.example.org"
room_id = "#alerts:example.org"
access_token = "<token>"

[tunnel]
provider = "cloudflare_quick"
auto_start = false
config_password = ""

config.toml里有Matrix的token和隧道的密码,不要把自己的配置文件分享出去。

实现

整体架构

architecturearchitecture

Fig. 3. ED Sentry的整体架构

如上图所示,ED Sentry的核心是一个Rust写的程序ed-sentry-core。它找到最新的Journal日志之后,用文件监听加上轮询的方式,只读取新追加的行。每一种Journal事件都会解析成具体的类型,再更新战斗统计、任务追踪和检查清单这几部分的状态。监控的部分根据这些状态生成通知和报警,最后按每种事件的等级分发到终端和Matrix。

仪表盘的数据走的是另一条路。核心程序会把当前的状态做成快照,桌面版通过Tauri的事件推给窗口,网页版通过Axum[7]的HTTP API和WebSocket推给浏览器,两边用的是同一套React界面。桌面版的ed-sentry.exe只是一个很小的启动器,它会去启动同一个文件夹里的ed-sentry-core.exe --gui,这样用户解压之后一眼就能找到应该点哪个程序。

从命令行到仪表盘

这个项目一开始只打算做命令行,计划里写的是不做网页界面。后来想到可以有四种使用方式,桌面版、网页、Matrix机器人和命令行,前两种可以共用同一套网页界面,于是就开始做仪表盘。

界面的做法和之前的Media Poster Space差不多。GPT的模型做界面设计不太行,所以先用Gemini[8]的网页版设计了一版自己喜欢的界面,保存成一个React的文件放进仓库,再让agent照着这个设计重做前端,并且不允许它修改这个设计文件。

design
Fig. 4. 用Gemini设计的界面,里面是设计时用的示例数据

对比上面Fig. 1可以看到,最终的界面基本上是照着这个设计做的,只是左上第二个面板从船体完整度换成了检查清单。

以Windows为主

既然主要是给Windows用的,Windows上的问题就特别多。Windows的版本是在Linux上交叉编译出来的,中间踩了不少坑:

  • Matrix的库默认会带上端到端加密和SQLite,SQLite在交叉编译Windows的时候会出错,所以关掉了它的默认功能。
  • Tauri默认开启的一个功能,会让程序在比较旧的Windows上直接打不开,连main函数都进不去。
  • 桌面窗口启动的时候会先连接Matrix,如果Matrix连接超时,窗口就会卡住10秒。后来把连接放到了后台,窗口先显示出来。
  • 隧道启动之后,cloudflared还会一直输出日志,如果程序不继续读取这些输出,管道满了之后cloudflared就会卡死,隧道也就断了。

在发布第一个版本之前,CI在Windows上又失败了很多次,前后修了20个左右的提交,大部分都是只有Windows才有的问题,比如配置文件里路径的反斜杠被当成了转义字符,测试里用来模拟cloudflared的脚本在Windows上跑不起来。发布的流程也要求agent真实地验收,需要真的把代码推到GitHub上、触发CI、看到发布出来的结果,并且README里的下载链接能下载到对应的文件,才算是完成。

和agent一起开发

这个项目的代码基本上都是agent写的,用的是OpenCode加上oh-my-opencode,以及Codex,模型都是GPT。整个过程中印象比较深的一件事,是agent会写出看起来能用、实际上什么都没做的功能。最早的时候发现watch模式其实什么都没做,命令行参数和配置都能解析,但是根本没有接到实际的逻辑上。于是就让agent检查一遍还有没有类似的空壳功能,结果又找出了好几个。之后就定了一个规则,每一个配置项要么真的实现,要么在文档里写明是以后再做的。

总结

本文介绍了ED Sentry,一个给Elite Dangerous挂机用的监控工具。它读取游戏的本地日志,挂机之前用检查清单确认船的状态,挂机的时候通过终端和Matrix提醒,也可以通过局域网或者Cloudflare的隧道在手机上查看仪表盘,回来之后再看任务的进度。整个项目以Windows为主,大部分的时间其实都花在了Windows的打包和CI上。

参考文献

  • [1] "Elite Dangerous", Frontier Developments, 2026. https://www.elitedangerous.com/
  • [2] "PsiPab/ED-AFK-Monitor", GitHub, 2026. https://github.com/PsiPab/ED-AFK-Monitor
  • [3] "Matrix", Matrix.org Foundation, 2026. https://matrix.org/
  • [4] "tauri-apps/tauri", GitHub, 2026. https://github.com/tauri-apps/tauri
  • [5] "Quick Tunnels", Cloudflare, 2026. https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/
  • [6] "WarmedxMints/ODEliteTracker", GitHub, 2026. https://github.com/WarmedxMints/ODEliteTracker
  • [7] "tokio-rs/axum", GitHub, 2026. https://github.com/tokio-rs/axum
  • [8] "Gemini", Google, 2026. https://gemini.google.com/

Media Poster Space: 给Jellyfin和Emby用的海报墙屏保

2026-03-16 10:47:33

Media Poster Space(MPS)是一个海报墙,连上自己的Jellyfin或者Emby服务器之后,会把媒体库里的海报排成一面一直在滚动的墙,用起来就像一个屏保。它有网页版和桌面版两种,网页版可以直接打开mps.controlnet.space使用,代码发布在GitHub上,项目地址。

背景

之前在《比之前更进一步的终极看番工具栈》里提到过,原来用文件夹看番的时候,没有类似于海报墙的东西。后来换成了Jellyfin[1],虽然有了海报墙,但是Jellyfin原版的海报墙实在是很丑。

Jellyfin也有不少第三方的前端和CSS主题,可以把海报墙做得好看一些,但是用下来感觉体验也就那样,还是没法脱离Jellyfin本身的那一套设计。而且有些主题对OLED屏幕不太友好,固定的元素一直显示在同一个位置,容易产生烧屏。

所以就想自己做一个可以一直运动的海报墙,海报一直在动,OLED也就不会烧屏了。背景也希望尽量黑一点,这样海报看起来会比较好看。

功能

wall
Fig. 1. 海报墙的效果

目前主要有以下功能:

  • 海报墙: 海报排成倾斜的多行,每一行都在横向滚动,左下角有一个大的时钟。剧集只显示整部剧的海报,不会每一集都显示一张。
  • 防烧屏: 海报一直在移动,时钟也会定期微微移动位置,并且有呼吸的效果,不会有元素长时间停在同一个地方。
  • 媒体服务器: 支持Jellyfin和Emby[2],可以选择要显示哪些媒体库。如果选错了服务器类型,比如用Jellyfin的方式去连Emby的服务器,会直接提示出来。
  • 网页版: 纯静态的网页,没有后端,浏览器直接连接自己的媒体服务器。可以记住服务器地址和用户名,支持全屏。
  • 桌面版: 用Tauri[3]做的,有Windows,Linux和macOS三个版本。密码保存在系统的密钥环里,还可以选择在哪个显示器上显示,也可以设置开机自动启动。
  • 离线启动: 网络断开的时候,也可以用缓存的海报先显示出来,token失效或者网络断开之后也会提示重新连接。
  • 纯鼠标操作: 没有任何快捷键,底部的控制栏在空闲一段时间之后会自动隐藏,只有动鼠标才会出现。

connect
Fig. 2. 连接媒体服务器的页面,Emby和Plex收在右边,点击之后展开

使用方法

网页版直接打开mps.controlnet.space,选择服务器类型,填入服务器地址、用户名和密码,再选择要显示的媒体库,就可以进入海报墙了。

因为网页版没有后端,是浏览器直接拿着token去访问媒体服务器,所以有两点需要注意:

  • 服务器需要能通过HTTPS访问。网页版本身是HTTPS的,如果服务器只有HTTP的地址,浏览器会把请求当成混合内容拦截掉。
  • 服务器需要允许跨域访问。Jellyfin默认就是允许的,如果之前在设置里限制过跨域的来源,需要把https://mps.controlnet.space加进去。

之所以不在后端做一个代理,是因为这样的话,用户的登录信息就要经过别人的服务器,反而更不安全。让浏览器直接连接自己的服务器,是更合理的做法。

桌面版可以在GitHub的Releases里下载。

如果想自己在本地运行:

1
2
3
pnpm -w install
pnpm --filter @mps/web dev # 网页版
pnpm --filter @mps/desktop tauri:dev # 桌面版

界面设计

这个项目的代码基本上都是agent写的,主要用的是OpenCode加上oh-my-opencode,模型是GPT。但是GPT的模型做界面设计的能力非常弱,让它直接设计海报墙,完全做不出一个还能看的东西。第一版做出来之后,自己的评价是「丑爆了」。

所以界面的部分换了一种做法,流程如下图所示。先在ChatGPT的网页版里做一些brainstorming,把对海报墙的想法整理清楚,再把这些想法交给Google的Gemini[4]来画设计稿,其中有一部分前端是在Antigravity[5]里做的。这一步改了很多次,做出了好几个不同风格的海报墙。然后从这些设计里选一个,把设计页面保存成HTML文件,放进仓库的designs/文件夹,最后在OpenCode里让GPT参考这些设计,完成剩下的开发。AGENTS.md里也写明了,designs/里的文件只是给agent参考的,agent不能修改。

design-workflowdesign-workflow

Fig. 3. 界面设计的流程,每一步之间都是自己手动传递和把关

design
Fig. 4. 最后选中的设计稿Parallax Horizon,里面的图片是设计时用的示例图

现在的海报墙就是从上图这个设计来的。把它交给GPT之后,第一次做出来的效果和设计稿完全不像,只好让它完全重写。后面连接页面的设计也是一样,先在Antigravity里做好设计稿,再告诉agent用这个设计稿作为连接页面的设计。

9月的时候又重做了一次连接页面,这次用的是Claude Design,同样是先出设计稿,再照着实现。三个平台用三种不同的颜色、登录页面用暗色、保留各个平台的logo、切换平台的时候加一个动画,这些都是在设计的时候定下来的。

这相当于是一个弥补GPT设计弱项的工作流: 设计交给擅长设计的模型,开发交给GPT,中间用保存下来的设计页面作为两边的桥梁。

设计稿定下的是整体的样子,还有几处是在和agent一起开发的时候自己决定的。一开始只打算做桌面版,但是既然界面本身就是网页前端,完全可以再出一个网页版,所以就要求两个版本同样的质量、同一天发布。OLED的保护一开始就定成了强保护,连海报的边缘也要能有一定的偏移,不然整齐的网格本身也是一种会烧屏的图案。点击海报打开详情的功能是自己决定删掉的,海报墙是拿来看的,没有必要去点它。结果负责最后检查的agent认为这个改动超出了原来的需求,把整个计划驳回了,最后还是按自己的意思保留了这个改动。

另外,agent有时候会伪造界面测试的证据。有一次检查发现,桌面版测试的截图其实是一张1x1的PNG,录屏是一个改了.webm后缀的文本文件,后来改成了用Playwright真实地截图和录屏。

实现

整体架构

architecturearchitecture

Fig. 5. Media Poster Space的整体架构

如上图所示,整个项目是一个用pnpm和Turborepo[6]管理的TypeScript monorepo。一开始网页版和桌面版是分开实现的,一边改了另一边也要跟着改,很难统一。所以后来把界面和大部分的逻辑都移到了共享的@mps/core里,网页版和桌面版只负责和平台相关的部分,比如桌面版的密码保存和显示器选择。界面没有用前端框架,直接操作DOM。

和媒体服务器的交互都在MediaProvider这个接口后面。Jellyfin的客户端直接用fetch调用HTTP API,Emby和Jellyfin的API本来就是同一个祖先,所以Emby的客户端只是继承了Jellyfin的客户端,改了API的路径和认证的方式。Emby的支持是自己写的,写完之后再让agent检查了一遍。

让海报墙一直动下去

海报墙一开始有一个很明显的问题: 每过一秒左右,整个海报墙就会强制刷新一次。agent给了三个修复方案,但是自己觉得整个加载新海报的策略就不对,海报墙应该是一个无限的动画,不应该隔一段时间就整个重新加载。

所以就改成了维护一个海报的队列,队列里的海报少于10张的时候,就向服务器请求新的海报,补充到40张。查下来,每秒刷新的原因是诊断模块每秒采样一次的时候会调用render(),而render()会把整个容器清空再重新生成。

改完之后还有两个问题,一个是海报会重复出现,另一个是空闲8秒左右之后偶尔会整屏刷新。原因也类似,一是请求新海报的时候一直在请求第一页,二是空闲8秒自动隐藏控制栏的时候,又调用了一次render()。

最后一个问题是新海报替换旧海报的时机。自己的想法是,只有从边缘进来的海报才需要换成新的海报,已经进入屏幕的海报是绝对不能刷新的。所以现在只有每一行入口那一侧、完全在屏幕外的格子(每行最多2个)才会被换成新的海报,暂时放不下的新海报先排队等着。这样在屏幕上看到的海报,从进来到出去都不会变。

后续计划

目前支持的是Jellyfin和Emby,连接页面上已经留了Plex[7]的入口,后续会把Plex的支持作为下一个开发的目标。

总结

本文介绍了Media Poster Space,一个给Jellyfin和Emby用的海报墙屏保。它有网页版和桌面版,海报一直在滚动,不会让OLED屏幕烧屏。开发的时候,界面先用Gemini和Antigravity设计,再让GPT照着设计稿实现,算是弥补了GPT在界面设计上的弱项。

参考文献

  • [1] "jellyfin/jellyfin", GitHub, 2026. https://github.com/jellyfin/jellyfin
  • [2] "Emby", Emby, 2026. https://emby.media/
  • [3] "tauri-apps/tauri", GitHub, 2026. https://github.com/tauri-apps/tauri
  • [4] "Gemini", Google, 2026. https://gemini.google.com/
  • [5] "Google Antigravity", Google, 2026. https://antigravity.google/
  • [6] "vercel/turborepo", GitHub, 2026. https://github.com/vercel/turborepo
  • [7] "Plex", Plex, 2026. https://www.plex.tv/

videnoa: 用节点编排的AI视频超分和插帧工具

2026-02-25 06:15:22

videnoa是自己做的一个AI视频增强工具,可以对视频进行超分辨率和插帧。处理的流程用节点图来描述,可以在网页上像搭积木一样把节点连起来,然后交给GPU去跑。代码发布在GitHub上,项目地址。

功能

editor
Fig. 1. Worker网页上的节点编辑器,图中是自己平时用的3倍插帧 + 2倍超分的预设

目前主要有以下功能:

  • 超分辨率: 支持Real-ESRGAN[1],RealCUGAN[2]这一类的ONNX模型,比如动画用的AnimeJaNai[3],大的模型可以分块处理。
  • 插帧: 使用RIFE[4] v4.26,可以插2倍、3倍或者更多。
  • 推理: 通过ONNX Runtime[5]调用CUDA或者TensorRT[6],超分和插帧都可以开多个并行的推理通道。
  • 节点编辑器: 网页上用React Flow[7]做的节点编辑器,除了视频输入输出、超分、插帧和缩放,还有Jellyfin、HTTP请求、下载、字符串和路径处理等节点。也可以先预览单帧的效果再提交。
  • 多种入口: 同一个引擎可以通过命令行videnoa run,网页服务和Tauri[8]的桌面版使用。
  • 输出: 用FFmpeg[9]编码视频,支持x264,x265和NVENC。MKV输出会用mkvmerge[10]把原来的音频、字幕、章节和附件合进去。处理失败或者取消的时候不会留下不完整的文件。
  • Controller: 一个不需要GPU的调度服务,可以跑在NAS上,把视频分发给一台或者多台GPU机器(Worker)处理,再把结果放回原视频的旁边。任务状态都保存在SQLite里,中途崩溃了也可以继续。
  • 连接方式: Controller和Worker之间可以直接用HTTP(S),比如通过Tailscale,也可以用iroh点对点直连。

自己平时用的预设,是先用RIFE插3倍帧,再用AnimeJaNai V3做2倍超分,最后用x265编码。

jobs
Fig. 2. Worker的任务列表,现在一集番剧大约需要1小时45分钟

使用方法

Worker需要NVIDIA的GPU,最简单的方法是用Docker:

1
2
3
4
5
6
mkdir -p ./models ./trt_cache ./data
docker run -d --name videnoa --gpus all -p 3000:3000 \
-v "$PWD/models:/app/models" \
-v "$PWD/trt_cache:/app/trt_cache" \
-v "$PWD/data:/app/data" \
controlnet/videnoa:latest

然后打开http://<ip>:3000就可以用了。模型可以在GitHub Releases的misc里下载models.zip,解压到./models。第一次用TensorRT跑一个模型的时候,会先构建引擎,缓存到./trt_cache里,所以第一次会比较慢。

不想开网页的话,也可以直接用命令行跑一个预设:

1
./videnoa run presets/interpolation-3x-anime-2x.json --input input.mkv --output output.mkv

Controller可以直接在NAS上用Docker跑:

1
2
3
4
5
6
7
mkdir -p ./controller-data
docker run -d --name videnoa-controller \
--user "$(id -u):$(id -g)" \
-p 3001:3001 \
-v "$PWD/controller-data:/workspace/data" \
-v "$HOME/Videos:/media" \
controlnet/videnoa-controller:latest

打开http://<ip>:3001,设置管理员密码,再把Worker添加进去。ANI-RSS这一类的工具,在下载完成之后调用Controller的POST /api/tasks就可以提交任务了。

Linux和Windows也有打包好的版本,可以在GitHub Releases里下载。如果要用iroh连接,需要先给Worker设置密码,在设置里打开iroh,再把Endpoint ID填到Controller里。

背景

自己的NAS上用ANI-RSS[11]订阅和下载新番,再用Jellyfin[12]来看。下载下来的番剧一般是1080p 24帧的,所以就想让新番下载完之后,可以自动地超分和插帧,再放回媒体库里。

做这个东西之前也考虑过用现成的工具,但是最后还是决定自己写,主要有两个原因。

第一个是自动化。这个系统要和自己的下载、媒体库对接起来,处理的流程也会经常调整。用节点的方式来描述流程,自由度更高,本身也有一定的编程能力,比如可以拼接路径、发HTTP请求、从Jellyfin获取视频,更符合自己对自动化脚本的需求。如果用不可视化的脚本来写,当然也可以实现,但是后续维护起来就没有那么容易了。

第二个是扩展。GPU计算的部分(Worker)是自己实现的,后续就可以往里面加任何想要的东西。比如后来加的Controller,可以在NAS上统一调度多台GPU机器,还可以用iroh[13]在不同的内网之间直连,这些东西做起来都挺有意思的。用现成的工具的话,这些就做不到了。而且现成的工具也没有告诉你,超分和插帧到底应该用什么样的技术栈来做。

当然,用现成的工具也有好处,就是不用自己去踩很多别人已经踩过的坑。比如后面会讲到的输出的MKV文件丢失音频的问题,就是自己最近遇到、然后专门花时间去修的。但是综合考虑,有一个自己能完全掌控的系统,还是要好很多。

名字videnoa是Video Enhancement Node-Orchestrated Automation的缩写,也就是用节点编排的视频增强自动化。

整体架构

architecturearchitecture

Fig. 3. videnoa的整体架构

如上图所示,左边是NAS上的部分。ANI-RSS下载完新番之后,通过hook调用POST /api/tasks把任务提交给Controller。Controller挑一台空闲的Worker,把源视频上传过去,等处理完再把结果取回来,放到原视频的旁边,Jellyfin就会把它识别成同一集的另一个版本。Controller和Worker之间用HTTP(S)或者iroh都可以,这两种方式自己平时都会用。

右边是Worker。网页、桌面版和命令行用的是同一个引擎,节点图会先被编译成一条流式的管线: FFmpeg解码出原始的帧,经过插帧和超分之后,再交给FFmpeg编码。帧在各个节点之间通过有界的队列传递,不会写到硬盘上。最后如果输出是MKV,再用mkvmerge把原视频的音频、字幕等合回去。

后端是一个Rust的workspace,分成核心引擎、命令行、桌面版、Controller、iroh的传输层和Worker与Controller之间的协议几个crate。前端是React,Worker和Controller各有一个,都直接打包进Rust的二进制文件里。

性能优化

对这个系统来说,性能是最重要的。所以从一开始就定了一条规则: 每一次优化都要先做实验,和baseline对比,只有性能真的提升了才保留。实际做下来,大部分的优化在测试之后都没有保留下来,比如把FP16的输入直接借给ONNX Runtime,或者换成half::f16的数组,测出来反而更慢,最后都撤回了。

还有一点是数字要看清楚是在什么条件下测的。v0.1.1的时候,TensorRT在预热之后的稳定FPS提高了48%,但是算上启动和构建引擎这些时间,端到端只快了15.7%。

后来有用户反馈,在RTX 5080上跑的时候GPU利用率很低。查下来发现RIFE的ONNX模型把batch写死了,没法用更大的batch,多个线程共用一个session又会被锁串行化。最后的做法是开多个独立的TensorRT session,再用一个按顺序重排的缓冲区把帧排回原来的顺序。这样插帧快了大约1.55倍,后来又推广到了超分,也就是现在节点上的num_workers参数。再后面又加了按任务复用的帧缓冲池,超分 + 插帧的时间又少了38%。

Controller

Controller看起来只是把文件传过去,再传回来,但是大部分的工作其实都是在处理各种失败的情况。比如上传到一半断了、Worker处理完了但是回复丢了、下载回来的文件不完整、发布的时候目标已经有文件了等等。所以Controller里所有的任务状态都只以SQLite为准,提交任务是幂等的,失败的时候只重试失败的那一步,不会把已经跑完的AI处理再跑一遍,发布的时候也不会覆盖已有的文件。

这里也遇到过一个测试掩盖了问题的情况。在最后检查的时候,发现添加进去的Worker永远不会变成可用的状态,因为生产代码里根本没有定期检查Worker状态的循环。之前的测试都是直接把Worker的状态写成在线,所以一直没有发现。

踩过的坑

做视频处理,很多问题不会报错,只是结果不对。下面是遇到的一些比较典型的坑:

  • TensorRT引擎缓存: 一开始所有模型共用一个缓存目录,结果1080p的RIFE引擎被拿去处理4K的输入。后来缓存改成按模型、输入尺寸和TensorRT、ONNX Runtime的版本来区分。
  • 内存: Worker在空闲的时候占了16.9 GiB的内存,但实际在用的只有322 MiB,问题出在glibc的内存碎片。调用malloc_trim之后降到了1 GiB左右,后来又加了帧缓冲池,减少反复申请内存。
  • 退出时崩溃: 命令行跑完退出的时候,有时会报corrupted double-linked list。原因是Rust这边的ort库在ONNX Runtime自己的C++静态析构函数执行完之后,才去释放全局的环境,最后的做法是让这个环境一直保留到进程结束。
  • 颜色: RGB转YUV的时候用的是BT.601的矩阵,但是标记的却是BT.709,结果颜色会偏,比如纯绿色(0,200,0)播放出来是(0,167,0)。
  • 亮度: FP32的RealESRGAN模型需要0到1的输入,但是实际传进去的是0到255,输出的画面亮度只有原来的一半。
  • 音频: 有一次AI版本的视频播放时没有声音。原因是FFmpeg 7以后,在复制音频、而视频是从管道慢慢送进来的时候,会把音频全部排在视频的前面。最后改成FFmpeg只编码视频,再用mkvmerge把音频、字幕等合进去。

另外,第一个外部用户在Windows上用RTX 5090把720p的番剧处理到4K,一次就报了三个问题,包括Windows的路径、节点图的编译和Windows版FFmpeg没有x264和x265的编码器。这些都是自己的环境里不会遇到的。

AI开发

和oma-dotfile一样,这个项目的代码基本上也是agent写的。

一开始用的是OpenCode加上oh-my-opencode。最开始先和agent讨论整个系统怎么做,里面几个大的方向是自己定下来的:

  • 后端用Rust而不是Python,性能更好,Docker镜像也不会太大。
  • 只用ONNX模型,不考虑从PyTorch转换。
  • 把处理流程做成类似ComfyUI那样的低代码节点,比如Resize节点、超分节点、插帧节点。
  • 视频流以外的部分,比如音频、字幕,全部原样保留。

方向定下来之后,让规划的agent写成计划,再交给执行的agent一步一步做。agent有时候也会做得太多,比如一开始它加了批量处理和监控文件夹的功能,自己觉得是过度设计,就删掉了,Jellyfin做成一个节点,批量处理直接用bash循环就行。

大部分的代码是用GPT的模型写的,但是GPT写前端的能力比较弱,所以界面没有交给它来设计。第一版的界面是项目刚开始的时候用Claude Opus写的,Opus的前端能力也很强。后来重做Controller和Worker的界面时,是先用Claude Design出设计稿,再照着设计稿来实现,Controller的前端也有一部分是自己改的。

Controller也是先把需求写清楚: 部署在NAS上,和ANI-RSS、Jellyfin放在一起,通过HTTP(S)连接Worker,不需要SSH和网络挂载,也不做监控文件夹。然后让agent写成一个比较长的计划,按照TDD一个任务一个任务地实现,最后再有一轮独立的检查,前面说的Worker永远不可用的问题就是在这一轮被发现的。

后来主要用的是Claude Code。和oma-dotfile一样,会让Claude Code调用Codex,用GPT-6 Astra只读地检查整个项目,找出来的问题再由Claude对照代码确认之后修复。比如音频那个问题,第一次的修复就是在Codex的检查里发现还有遗漏,最后撤回重做的。后来Codex的额度快用完了,就改成用Claude自己的subagent来做设计的检查。

自己在这里面主要是决定要做什么,然后在agent改错的时候把它拉回来。比如:

  • agent给Worker的任务历史加了数量上限,自动删除旧的记录。但是Controller要靠这些记录来查询任务状态,所以撤回了。
  • agent打开了Dependabot,一下子开了好几个PR,自己把这些PR都关了,以后也不再开。
  • agent把桌面版的数据目录换到了系统的应用数据文件夹,这样老用户的设置和工作流都会找不到,所以也撤回了。

现在的问题

现在用下来,还有两个比较大的问题,也是后面打算改进的地方。

  • 速度: 现在还是太慢了,完全做不到实时。一集番剧做3倍插帧 + 2倍超分,也要将近两个小时。后续可能会考虑用DLSS这一类的技术来提高效率。
  • Controller不够智能: 现在所有的任务都是由ANI-RSS的hook触发的,而ANI-RSS没法按条件决定哪些要处理、哪些不要。所以后续希望在Controller里加一个过滤的功能,比如本来就是4K的番剧就不用再超分了,本来就是60帧以上的也不用再插帧了。

总结

本文介绍了videnoa,一个用节点编排的AI视频超分和插帧工具。它用Rust写成,通过ONNX Runtime和TensorRT在GPU上推理,再加上一个跑在NAS上的Controller,把新番的下载、AI增强和媒体库串成了一个自动化的流程。

参考文献

  • [1] X. Wang, L. Xie, C. Dong, and Y. Shan, "Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data," in ICCVW, 2021. https://github.com/xinntao/Real-ESRGAN
  • [2] "bilibili/ailab (Real-CUGAN)", GitHub, 2026. https://github.com/bilibili/ailab
  • [3] "the-database/mpv-AnimeJaNai", GitHub, 2026. https://github.com/the-database/mpv-AnimeJaNai
  • [4] Z. Huang, T. Zhang, W. Heng, B. Shi, and S. Zhou, "Real-Time Intermediate Flow Estimation for Video Frame Interpolation," in ECCV, 2022. https://github.com/hzwer/Practical-RIFE
  • [5] "microsoft/onnxruntime", GitHub, 2026. https://github.com/microsoft/onnxruntime
  • [6] "NVIDIA/TensorRT", GitHub, 2026. https://github.com/NVIDIA/TensorRT
  • [7] "xyflow/xyflow (React Flow)", GitHub, 2026. https://github.com/xyflow/xyflow
  • [8] "tauri-apps/tauri", GitHub, 2026. https://github.com/tauri-apps/tauri
  • [9] "FFmpeg", FFmpeg, 2026. https://ffmpeg.org/
  • [10] "MKVToolNix", MKVToolNix, 2026. https://mkvtoolnix.download/
  • [11] "wushuo894/ani-rss", GitHub, 2026. https://github.com/wushuo894/ani-rss
  • [12] "jellyfin/jellyfin", GitHub, 2026. https://github.com/jellyfin/jellyfin
  • [13] "n0-computer/iroh", GitHub, 2026. https://github.com/n0-computer/iroh

oma-dotfile: 在多台机器上同步各种coding agent配置的dotfile工具

2026-01-28 08:14:00

oma-dotfile是自己用来同步各种coding agent配置的dotfile仓库,只需要一行命令,就可以把同一套规则、模型、skill和通知插件装到OpenCode,oh-my-pi,Codex和Claude Code等agent里。代码发布在GitHub上,项目地址。

背景

为了让agent用起来更适合自己,肯定会对agent的各种东西做一些自定义的改造。比如说,想用自己选的模型,而且有一个自己部署的Gotify[6]通知服务器,希望agent每次执行完之后都可以发一条通知到手机上。

有了这些自定义的东西之后,再加上自己是在多台机器上同时使用agent的,就会发现要把各种agent的配置文件从一台机器抄到另外一台机器,其实非常麻烦。所以就做了这样一个dotfile的工具,可以在新的机器上快速地拉取相关的配置。这样的话,一台机器上有什么更新,其他机器上直接拉一下就可以用了。

一开始只有OpenCode[1]加上oh-my-opencode[2],所以名字叫omo-dotfile。后来又陆续加上了oh-my-pi[3],Codex[4]和Claude Code[5],就改名成了oma-dotfile。

功能

banner
Fig. 1. 安装脚本启动时的banner

目前主要有以下功能:

  • 一行命令安装: 在Linux,macOS和Windows上都可以用,只需要Python 3.10以上和Git,不需要安装任何第三方的Python包。
  • 多个agent: 支持OpenCode + oh-my-openagent(原oh-my-opencode),OMO Native(oh-my-openagent独立的命令行版本),oh-my-pi(OMP),Codex和Claude Code。默认只给本机上安装了的agent装配置。
  • 全局规则: 同一份规则文件_AGENTS.md会装到每个agent里。OpenCode,Codex,OMP和OMO Native装成各自的AGENTS.md,Claude Code则装成~/.claude/rules/oma-dotfile.md。
  • 模型: 默认通过一个OpenAI兼容的第三方网关调用模型,加上--oauth参数之后,可以换成ChatGPT的官方登录。
  • Skills: 9个共用的skill,比如github-cli,secret-guard,skill-creator等。
  • 通知: 每个agent都有一个Gotify的通知插件,标题是<Agent> :: <项目>@<主机名>,这样一眼就能看出是哪台机器上的哪个项目完成了。也可以配置一个大模型,把这一轮的回复总结成一句话再发出来。
  • 统计: 配置WakaTime[7]的插件统计写代码的时间,以及Tokscale[8]的模型别名,统计各个模型的token用量。

使用方法

在Linux或者macOS上:

1
curl -fsSL https://raw.githubusercontent.com/ControlNet/oma-dotfile/master/pull.py | python3

在Windows的PowerShell里:

1
(Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/ControlNet/oma-dotfile/master/pull.py' -UseBasicParsing).Content | python

如果要给所有支持的agent都装上配置,可以加上--all参数。被修改的配置文件会先备份成*.bak-<时间>,不需要备份的话可以设置NO_BACKUP=1。

如果想用ChatGPT的官方登录,而不是第三方网关,可以加上--oauth:

1
curl -fsSL https://raw.githubusercontent.com/ControlNet/oma-dotfile/master/pull.py | python3 - --oauth

然后在各个agent里分别登录,比如codex login,或者在OMP里用/login openai-codex。之后不加--oauth再运行一次,就会切换回网关。

所有的密钥都是通过环境变量读取的,主要有以下几个:

  • CODEX_BASE_URL,CODEX_API_TOKEN: 网关的地址和token
  • GOTIFY_URL,GOTIFY_TOKEN_FOR_OPENCODE: Gotify的地址和token,其他agent可以用GOTIFY_TOKEN_FOR_CODEX等单独设置
  • GOTIFY_NOTIFY_SUMMARIZER_MODEL,GOTIFY_NOTIFY_SUMMARIZER_ENDPOINT,GOTIFY_NOTIFY_SUMMARIZER_API_KEY: 用来总结通知的模型
  • WAKATIME_API_KEY,WAKATIME_API_URL: WakaTime的key和地址,会写到~/.wakatime.cfg里

这个仓库是自己的配置,规则和模型都是按照自己的习惯来的。如果要用的话,建议先fork一份,改成自己的配置之后再用。

实现

整体架构

architecturearchitecture

Fig. 2. oma-dotfile的整体架构

如上图所示,仓库里放的是所有agent的配置,pull.py运行的时候,先把整个仓库浅克隆到一个临时目录,然后检测本机装了哪些agent,再把对应的配置分别写到各个agent的目录里。规则和skill是所有agent共用的,模型配置和通知插件则是每个agent各有一份,因为每个agent的配置格式和插件的API都不一样。密钥不会写进仓库,只在安装的时候从环境变量里读取,或者让agent自己在运行时读取环境变量。自己的这些密钥是用Infisical[10]统一管理的,再同步到每台机器的环境变量里。

安装脚本

最开始的安装脚本是pull.sh和pull.ps1,里面把要下载的文件一个一个写死了。后来加了插件和skill的文件夹之后,每加一个文件都要改两个脚本,所以就换成了一个只用Python标准库的pull.py,直接git clone --depth 1整个仓库,然后再复制过去。

只用标准库这一点后来也遇到了问题。Codex的配置是TOML,读TOML用的tomllib要Python 3.11才有,而Ubuntu 22.04自带的是Python 3.10,在那些机器上一运行就报错了。因为安装脚本是直接curl下来用系统的Python运行的,没法先装tomli,最后只能写了一个简单的TOML解析作为后备。

另外有两个地方一开始也没有考虑清楚:

  • 检测agent: 一开始是看配置目录是否存在,但是只要在这台机器上运行过一次安装脚本,这些目录就都被创建出来了,所以没法用来判断。后来改成了检查可执行文件,包括~/.bun/bin,~/.local/bin和nvm这些不一定在PATH里的位置。
  • 哪些文件归安装脚本管: 比如OpenCode的插件和skill文件夹里,除了这个仓库装的,也可能有自己另外装的。所以只替换这个仓库管理的那几项,其他的保持不变。像OMO Native的settings.json这种运行时会被修改的文件,则是合并而不是覆盖。

规则

_AGENTS.md里放的是自己对agent的一些要求,比如:

  • 不允许在代码、日志和文档里写入任何密钥
  • 不允许使用git clean
  • 删除、覆盖、迁移和force push之前要先提醒
  • Python项目要用pixi或者conda管理环境,不能装到系统的Python里
  • 不允许用任何dummy的占位符来代替真实的实现
  • 有价值的知识要保存到.omo/knowledges/*.md里

同一份规则要给5个agent用,规则里就只能写所有agent都能理解的东西。之前有两条规则是按oh-my-opencode的写法写的,比如用load_skills=["skill-name"]去委派任务,这是oh-my-opencode特有的写法,在别的agent里是没有意义的,后来就删掉了。

还有一个问题是,不少agent默认会去读其他agent的配置。比如OMO Native默认会读取~/.claude/rules,OpenCode也会兼容Claude Code的配置,这样同一份规则可能会被注入好几次。所以安装脚本会把OMO里这些兼容的功能关掉,OpenCode也建议设置OPENCODE_DISABLE_CLAUDE_CODE=1,让每个agent只读自己目录里的那一份。装到Claude Code的时候,也是放到rules/里,不去覆盖CLAUDE.md,这样不会影响自己原有的设置。

模型的高低搭配

用哪个模型和当时的性价比、订阅都有关系,一直在变。比如2月份的时候GPT的性价比更高,而且有Codex的订阅,后来从GPT-5.x一直换到GPT-6.1,现在也会用Opus 5.5。

这里说一下不同agent之间的高低搭配。以oh-my-openagent为例,现在的配置是这样的:

agent 作用 模型 推理强度
Sisyphus / Hephaestus 主agent,负责规划和实现 GPT-6.1 Sol high
Oracle 咨询和审查 GPT-6.1 Sol xhigh
Momus 审查计划 GPT-6.1 Sol max
Explore 搜索代码库 GPT-6 Luna xhigh
Librarian 查文档和资料 GPT-6 Luna xhigh

搜索代码和查文档这种量大但是不太难的工作,交给便宜的小模型,主agent用主力的模型,而负责审查和最后把关的agent则用最强的模型和最高的推理强度。如果全部都用最强的模型,价格就太高了。关于各家agent和模型怎么选,之前在一次分享里也讲过,slides放在这里。

通知

通知插件是按照各个agent自己的语言来写的,OpenCode和OMP是JavaScript,Codex和Claude Code是Python。

通知这部分是维护得最多的地方,在全部206个commit里面,大约有四分之一都和通知有关。问题出在agent发出的完成事件并不等于任务做完了。每个agent都会在一些自己看不到的地方触发完成的事件,比如:

  • Codex在后台自动生成会话标题和对话回顾
  • Codex自动审批的那一轮
  • /goal模式下自动继续的中间轮次
  • 子agent的结束
  • Claude Code的Stop事件

这些都会发出一条通知,结果手机上一下子收到好几条没有意义的消息。所以只能一个一个地找出来,然后过滤掉。

项目记忆

中间还试过Beads[9],想用一个项目级的数据库来管理项目的issue和记忆,加进来又删掉,前后折腾了两次。后来发现没有必要,如果是私人项目,issue完全可以直接用GitHub Issues来管理,而文档和知识的积累,直接放在项目的.omo/knowledges文件夹里,写成Markdown文件就可以了。这个仓库也是这样,agent在里面积累了几十篇笔记,记录了每次调查的结论,后面再改同一个地方的时候就不用重新调查一遍。

AI开发

和MC Matrix Bridge一样,这个项目的代码基本上也是agent写的。这个仓库本身就是agent的配置,所以开发它的agent,用的也是这个仓库装上去的规则、skill和通知。

有的规则是agent出了问题之后才加上的。比如2月的时候,在另一个项目里,一个子agent为了把工作区恢复到干净的状态,直接运行了git clean -fdx,把所有没有被git追踪的文件都删掉了。发现之后,马上就在_AGENTS.md里加上了不允许使用git clean这一条。

10月的时候,还试了一下让一个agent去检查另一个agent写的代码。Claude Code会调用Codex,用GPT-6 Astra以只读的方式检查整个项目。找出来的问题由Claude Code对照代码确认之后,先写测试再修复,一个问题一个commit。这样找到了几个bug,比如Windows的路径写进TOML的时候没有转义,导致Codex的配置文件坏掉,还有按照修改时间清理备份,可能会把刚刚做好的备份删掉。

自己在这里面负责的是决定要做什么,然后在agent做多了或者做错了的时候把它拉回来。比如--oauth具体要怎么切换、切回网关的时候要不要自动恢复,都是自己定下来的。改README的时候,agent经常会加上很多不必要的说明,让它精简之后又删得太多了,最后是让它参考这个仓库早期README的样子来写的。

现在的问题

目前还有两个问题,可以作为后续改进的方向。

  • 依赖GitHub Raw: 现在安装脚本是从raw.githubusercontent.com下载的,配置也是直接从GitHub克隆的,并没有用自己的域名。以后可以换成自己的域名。
  • 依赖Python: 现在运行还需要机器上有Python。后续可以考虑换成Rust之类的系统编程语言,编译成一个二进制文件,下载下来就可以直接运行。

总结

本文介绍了oma-dotfile,一个在多台机器上同步各种coding agent配置的dotfile工具。它用一个只依赖Python标准库的脚本,把同一套规则、skill和通知插件装到5个不同的agent里。现在日常的主力是Claude Code,所以最近也把Claude Code的插件和规则的安装加了进来。

参考文献

  • [1] "anomalyco/opencode", GitHub, 2026. https://github.com/anomalyco/opencode
  • [2] "code-yeongyu/oh-my-openagent (原oh-my-opencode)", GitHub, 2026. https://github.com/code-yeongyu/oh-my-openagent
  • [3] "can1357/oh-my-pi", GitHub, 2026. https://github.com/can1357/oh-my-pi
  • [4] "openai/codex", GitHub, 2026. https://github.com/openai/codex
  • [5] "anthropics/claude-code", GitHub, 2026. https://github.com/anthropics/claude-code
  • [6] "gotify/server", GitHub, 2026. https://github.com/gotify/server
  • [7] "WakaTime", WakaTime, 2026. https://wakatime.com/
  • [8] "junhoyeo/tokscale", GitHub, 2026. https://github.com/junhoyeo/tokscale
  • [9] "gastownhall/beads", GitHub, 2026. https://github.com/gastownhall/beads
  • [10] "Infisical/infisical", GitHub, 2026. https://github.com/Infisical/infisical

MC Matrix Bridge: 连接Minecraft服务器和Matrix房间的Forge/NeoForge模组

2026-01-10 05:23:00

MC Matrix Bridge是一个Forge/NeoForge的纯服务端mod,可以把Minecraft服务器里的聊天和一个Matrix房间双向连接起来。代码发布在GitHub上,项目地址,也可以在Modrinth和CurseForge上下载。

背景

之前和朋友联机玩GTL(GregTech Leisure)整合包,版本是Forge 1.20.1。自己平时聊天用的是Matrix,所以希望能把Matrix和游戏内服务器的聊天对接起来。这样的话,有的时候自己没有在游戏里,也可以通过Matrix和游戏里的玩家进行交流。

然后去找了一下现有的mod:

  • Matrix Bridge[1]: 支持高版本,但是只有Fabric
  • Matrix Bridge(Forge)[2]: Forge版本的只支持1.7.10,而且已经好几年没有更新了
  • MatterLink[3]: 支持到1.15.2,并且需要另外部署一个Matterbridge作为中转

所以Forge这边只有非常老的版本,高版本只有Fabric实现了。既然如此,那就自己在Forge上也实现一个。一开始只考虑了1.20.1,但是后来发现之后的新版本基本上都是基于NeoForge的,所以又把NeoForge也做了。

功能

example
Fig. 1. 游戏内的效果,第一行是从Minecraft发出去的消息,第二行是从Matrix发进来的消息

一开始想做的功能就是把聊天的部分搞定,后续又加上了一些和服务器交互的部分。目前主要有以下功能:

  • 双向聊天: 游戏内玩家的聊天会转发到Matrix房间,Matrix房间里的文字消息也会转发到游戏里,并且带上[MC]和[Matrix]的前缀。
  • 纯服务端: 只需要服主在服务器上安装,玩家用原版客户端就可以直接进服,不需要额外下载安装。
  • 连接提示: 玩家进服的时候会提示Matrix房间已连接,这个提示会根据每个玩家的客户端语言显示,目前支持英文和中文。
  • 服务器事件: 可以选择把玩家进出服务器、服务器启动和关闭的消息发到Matrix,默认是关闭的。
  • Matrix指令: 在Matrix里发送!mc list可以查看服务器目前在线的玩家,!mc help可以查看帮助。
  • Event Taps: 在运行时订阅任意一个Forge/NeoForge的事件,然后把事件转发到Matrix,可以用来调试和监控服务器。

各个版本的更新如下:

版本 日期 内容
v0.1.0 2026-01-09 双向聊天,支持Forge 1.18到1.21
v0.1.1 2026-01-10 连接提示的多语言支持
v0.1.2 2026-01-12 Matrix指令,支持NeoForge 1.21
v0.1.3 2026-01-14 更稳定的客户端语言检测
v0.1.4 2026-05-31 Event Taps,支持Minecraft 26.1
v0.1.5 2026-09-21 支持Minecraft 26.2,以及一系列兼容性和稳定性的修复

使用方法

安装

首先需要一个Forge或者NeoForge的服务器,以及一个没有开启端到端加密的Matrix房间。然后根据服务器的加载器和Minecraft版本选择对应的jar,目前一共有9个:

  • Forge 1.18.x / 1.19.x / 1.20.x: Java 17
  • Forge 1.21.x,NeoForge 1.21.x: Java 21
  • Forge 26.1.x / 26.2.x,NeoForge 26.1.x / 26.2.x: Java 25

把jar放到服务器的mods/文件夹里,然后启动一次服务器,就会生成配置文件config/minecraftmatrixbridge.toml。

配置

配置文件里必须要填的是以下三项:

  • matrix.homeserver: Matrix服务器的地址,比如https://matrix.example.com
  • matrix.roomId: 房间的id!abcdef:example.com,或者别名#myroom:example.com
  • matrix.accessToken: 机器人账号的access token,也可以用环境变量MATRIX_ACCESS_TOKEN

其他的选项都有默认值,比如bridge.enableJoinLeaveToMatrix控制是否转发玩家进出服务器的消息,bridge.matrixBotPrefix是Matrix指令的前缀(默认是!mc),完整的列表可以看GitHub上的README。如果机器人账号只是被邀请进了房间,还没有加入,启动的时候也会自动接受邀请。

指令

游戏内的指令需要OP权限:

  • /matrix status: 查看桥接的状态
  • /matrix reload: 重新加载配置
  • /matrix test: 发一条测试消息到Matrix

Event Taps

Event Taps可以在游戏内用/matrix event,或者在Matrix里用!mc event来控制。在Matrix里使用的话,要求发送者在房间里的权限等级至少是50(也就是moderator)。

1
2
3
4
5
6
7
8
9
# 监听一次聊天事件
/matrix event on ServerChatEvent
# 监听10分钟的玩家登录事件
/matrix event on PlayerLoggedInEvent "" 10m
# 一直监听包含diamond的方块事件
/matrix event on BlockEvent diamond permanent
# 在Matrix里查看和关闭
!mc event list
!mc event off ServerChatEvent

为了防止刷屏,同时最多只能开10个,转发的间隔默认至少是1秒,每条消息也会截断到2048个字符以内。

实现

整体架构

architecturearchitecture

Fig. 2. MC Matrix Bridge的整体架构

如上图所示,整个mod分成两层。外面一层是版本兼容层,每个Minecraft版本一个,负责监听游戏事件、注册指令和向玩家广播消息。里面一层是core,包含了和Minecraft版本无关的全部逻辑。游戏里的聊天会先进入发送队列,再由单独的发送线程交给MatrixClient发到Matrix。Matrix那边的消息则通过sync长轮询拿回来,去掉重复的和机器人自己发的消息之后,再回到服务器主线程广播给玩家。Event Taps监听到的事件也会进入同一个发送队列,sync的进度则保存在世界存档里。

多版本

一般来说,Minecraft的mod都是基于单一的一个版本来开发的。但是这个mod涉及的Minecraft API比较少,更多的还是和Matrix进行交互,所以和Minecraft版本的依赖性并不是很强。大部分代码都可以复用,只需要为每个版本写一个很小的兼容层,就可以交叉编译到多个Minecraft版本。

对于这种小型的mod来说,个人认为这一点是非常重要的。这样就可以让它支持尽可能多的版本,成为一个基础设施。所以整个仓库是这样组织的:

1
2
3
4
5
6
7
8
9
10
11
12
13
├── core               # 纯Java,不依赖Minecraft,包括Matrix客户端、消息队列、去重、Event Taps等
├── forge-shared # 多个Forge版本共用的代码
├── neoforge-shared # 多个NeoForge版本共用的代码
├── forge-1.18
├── forge-1.19
├── forge-1.20
├── forge-1.21
├── forge-26.1
├── forge-26.2
├── neoforge-1.21
├── neoforge-26.1
├── neoforge-26.2
└── scripts # 打包和测试的脚本

每个forge-*和neoforge-*都只是一层很薄的兼容层,负责监听Minecraft的事件和注册指令,然后调用core里的逻辑。

这里有一个比较麻烦的地方是Gradle。老版本的Forge需要Gradle 8,而26.x的Forge和NeoForge需要Gradle 9,两个大版本是不兼容的。所以仓库里放了两套Gradle wrapper,通过-Pomx_modern_26这个参数决定这一次构建包含哪一组版本。

另外,同一个大版本里面的小版本也会有API的变化,比如1.20.6改了映射,Forge 1.21换了新的EventBus,NeoForge 1.21.11改了权限相关的API。为了让同一个jar能在所有的小版本上运行,调用这些API的地方都改成了用反射,其他地方还是直接调用。如果权限检查出了问题,就按照没有权限处理。

Matrix客户端

这里没有用Matrix的SDK,直接用了JDK自带的HttpClient,加上Minecraft本身就带有的Gson,调用了大概8个Matrix Client-Server API[4]的接口。因为只需要连接一个没有加密的房间,用到的接口很少,自己写反而更简单,jar里面也不用打包额外的依赖。

网络请求都在单独的线程里进行,游戏内的聊天事件只是把消息放进一个队列,所以不会卡住服务器的主线程。接收消息则是通过sync接口长轮询,sync的进度保存在存档的<world>/data/minecraftmatrixbridge/state.json里,这样服务器重启之后,也不会把之前的消息重新刷一遍。

AI开发

这个项目基本上是纯AI开发的。从2026年1月开始,自己就已经不怎么看代码的具体内容了,而之前的项目还是会尽可能去看代码是怎么组织的。

工具和模型

开发的工具和模型在不同阶段也不太一样。一开始Cursor和Codex都拿来尝试过,Cursor里面尽量用的是Claude的模型,Codex则是因为有ChatGPT的订阅,就顺便拿来用了。从Event Taps开始,主力换成了OpenCode加上oh-my-opencode[6]。大概是这样的:

阶段 工具 主要模型
2026-01 第一版(v0.1.0到v0.1.3) Cursor,Codex Claude Opus 4.5
2026-01 Event Taps OpenCode + oh-my-opencode 主agent用Claude Opus 4.5,探索和咨询的子agent用GPT-5.2
2026-04 适配26.x的初版 Codex + oh-my-codex,OpenCode + oh-my-opencode GPT-5.4
2026-05 适配26.x(v0.1.4) OpenCode + oh-my-opencode GPT-5.5,探索用GPT-5.4 mini
2026-09 兼容性和发布(v0.1.5) Codex,Claude Code 前期GPT-6 Astra,后期GPT-5.6 Sol

分工

自己负责的是设计整个mod的框架,包括如何跨版本、仓库的文件组织和交叉编译的思路。具体的做法是先和agent讨论各种想法,讨论出结果之后,再让agent把结果整理到AGENTS.md里,里面包括项目要做什么、必须满足的要求(纯服务端、尽量少的依赖、不能阻塞主线程等)和不做的东西(端到端加密、多房间等)。这个文件就是在一次次讨论中慢慢积累起来的,一直没有提交到仓库里。之后Gradle的细节和代码的编写就全部交给agent来做了,做新功能的时候也是一样,先讨论,把计划写进AGENTS.md,再开始实现。

oh-my-opencode是OpenCode上的一个多agent编排层,一个主agent负责写代码,其他的活分给不同的子agent:

  • Sisyphus / Hephaestus: 主agent,负责规划和写代码,1月用的是Sisyphus,4月和5月用的是Hephaestus
  • explore: 在代码库里搜索代码
  • librarian: 查Forge和NeoForge的文档
  • oracle: 做可行性分析,最后检查任务有没有完成

在提示词里加上ultrawork,主agent就会自己并行地开几个子agent去调研,再把结果汇总起来。

开发过程

做Event Taps的时候,先让它基于AGENTS.md里之前讨论的结果做一个更详细的计划,分析一下可行性。它会开几个子agent去查代码和文档,然后和自己一起把计划定下来,写回AGENTS.md,再开始写代码。中间需要自己来定的是设计上的选择。事件的索引放在运行时扫描,Matrix端的权限直接用房间里的权限等级,不另外做管理员白名单。另外还专门问了一下启动时扫描事件会不会卡住主线程。写完之后,agent会自己跑构建和单元测试,再把各个版本的服务器真的跑起来看看。

4月份适配26.x的时候,先用Codex加上oh-my-codex写了第一版,再让OpenCode里的Hephaestus去审查这份代码,把审查出来的问题都修掉。修的时候用的是ULTRAWORK Loop,oracle检查通过了才会停下来,中间被oracle打回了6次。

5月份发布v0.1.4,用的是Hephaestus加上GPT-5.5,还开了几个只读的agent专门做审查。agent写出来的东西也不是每次都对,这个阶段自己纠正了好几次:

  • NeoForge的代码还是有很多重复,让它把重复的部分抽到neoforge-shared里
  • 自己手动跑了一次Gradle构建,发现了报错
  • 把失败的CI链接贴给它,让它一直修到GitHub Actions通过

9月份做v0.1.5用的是Codex,前期是GPT-6 Astra,后期是GPT-5.6 Sol,也用Claude Code处理了一部分。这次做的是让同一个jar兼容各个小版本,并且把真实Synapse的端到端测试加进了CI。后面提到的CurseForge版本标签的坑也是这个时候遇到的。

测试

测试方面,也是尽可能让agent把自动化测试写好,包括单元测试、用模拟的Matrix服务器启动真实的Minecraft服务器进行测试,以及后来在CI里用真实的Synapse[5]服务器,对45个Minecraft版本和加载器的组合做端到端的测试。但是每次发布新版本之前,自己都还会用真实的Matrix服务器和真实的Minecraft服务器人工测试一次,确保它能跑。

一些坑

客户端语言

连接提示需要根据玩家的客户端语言显示。由于这是一个纯服务端的mod,原版客户端里面并没有这个mod的语言文件,所以文本只能在服务端根据玩家的语言翻译好再发过去。

问题是,玩家刚登录的时候,服务器其实还不知道玩家的语言,这个时候读到的都是默认的en_us,客户端的设置要过一会儿才会发过来。一开始的做法是固定等20个tick再发提示,但是不稳定。后来改成每个tick检查一次,如果客户端的设置看起来已经收到了(视距不是默认的2,或者语言不是en_us)就发送,最多等5秒。

CurseForge的版本标签

Minecraft从26.1开始改成了按年份的版本号,CurseForge那边的版本标签也和之前不太一样。发布v0.1.5的时候,发布流程在查找Minecraft 26.2这个标签的时候,先是一个都找不到,修好之后又找到了两个,一共失败了4次,对应修了4个commit。

好在发布流程是先校验所有平台的版本标签,然后才开始上传的,所以失败的时候也不会出现只发布了一半的情况。

除此之外还有一些小问题,比如Forge用的NightConfig的文件监听线程会让服务器在stop之后关不掉,以及超时重试的时候没有复用Matrix的transaction id,导致同一条消息发了两遍,这些都在v0.1.5里修好了。

以后的计划

  • 端到端加密: 目前还没有实现。Matrix的端到端加密实现起来过于复杂,还需要考虑设备验证之类的东西,不管是软件依赖方面还是交互方面,用起来都很不舒服。如果以后能找到一个不错的UX,可以比较方便地完成验证,再考虑去做。
  • 跟进新版本: 这个mod需要一直跟进Minecraft的版本,尽可能让它在所有的版本里都能跑。现在已经做了不少版本兼容性的改进,但是后续26.2的新小版本、26.3以及之后的版本,还需要慢慢跟进。

总结

本文介绍了MC Matrix Bridge,一个把Minecraft服务器和Matrix房间连接起来的Forge/NeoForge纯服务端mod。它的核心逻辑和Minecraft版本无关,通过很薄的兼容层支持了从1.18到26.2的Forge和NeoForge。这也是自己第一个基本上纯靠AI开发的项目,自己负责架构和人工测试,代码交给agent来写。

参考文献

  • [1] "Matrix Bridge", Modrinth, 2026. https://modrinth.com/mod/matrix-bridge
  • [2] "Matrix Bridge", CurseForge, 2022. https://www.curseforge.com/minecraft/mc-mods/matrix-minecraft-bridge
  • [3] "MatterLink", CurseForge, 2020. https://www.curseforge.com/minecraft/mc-mods/matterlink
  • [4] "Client-Server API", Matrix Specification, 2026. https://spec.matrix.org/latest/client-server-api/
  • [5] "element-hq/synapse", GitHub, 2026. https://github.com/element-hq/synapse
  • [6] "code-yeongyu/oh-my-openagent (原oh-my-opencode)", GitHub, 2026. https://github.com/code-yeongyu/oh-my-openagent

bili-parser: B站视频信息解析和AI字幕工具

2025-05-19 21:56:00

bili-parser是一个用来解析B站视频信息的网页工具,输入B站链接之后,可以得到视频的封面、标题、播放数据和简介,并且一键复制成方便分享的格式。后来又加上了基于Whisper的AI字幕,以及基于字幕的AI摘要和对话。代码发布在GitHub上,项目地址,在线版本部署在Vercel上。

背景

之前在QQ群里看到有一个bot,别人在群里发了B站链接之后,它会自动回复这个视频的信息和总结,个人觉得这个功能挺好的。

但是如果是自己手动把B站链接转发给别人,就会有一个问题。对方只能看到一个链接,并不知道这个视频里面包含什么内容,得点进去才能看,所以吸引力就不是很强。如果转发的时候能带上封面、标题、播放数和简介,吸引力就会强很多。

所以就先做了这个工具,把视频的metadata整理成一段可以直接复制的内容,然后再慢慢往上加功能。

功能

home
Fig. 1. bili-parser的首页

目前主要有以下功能:

  • 视频信息: 输入可以是完整的B站链接、带有其他文字的分享文本、BV号,或者b23.tv的短链和短链代码。解析之后会显示封面、标题、UP主和粉丝数、播放/弹幕/点赞/投币/收藏/分享数据、简介,以及当前在线观看的人数。
  • 在线播放: 页面里嵌入了B站的播放器,可以直接看视频。
  • 一键复制: 把上面的信息复制到剪贴板,同时包含纯文本和带封面图的富文本,粘贴到支持富文本的地方就会带上封面。
  • AI字幕: 通过自己部署的Whisper服务生成带时间戳的字幕。
  • AI摘要和AI对话: 基于视频信息和字幕,用大模型生成摘要,或者针对视频内容进行对话。

每个视频也有自己的页面,比如 https://bili-parser.vercel.app/BV1xx411c7mD,可以直接把这个地址发给别人。

video
Fig. 2. 解析B站最早的视频字幕君交流场所(BV1xx411c7mD)的结果

复制出来的纯文本是这样的,粘贴到聊天软件里面基本上一眼就能看到这个视频是什么。

1
2
3
4
5
6
7
8
标题: 字幕君交流场所
UP主: 碧诗 粉丝: 143.6万
👀播放: 559.4万 💬弹幕: 12.9万
👍点赞: 27.8万 💰投币: 4.1万
📁收藏: 12.8万 🔗分享: 2.9万
📝简介: www
🏄‍♂️ 总共 21 人在观看,21 人在网页端观看
https://www.bilibili.com/video/BV1xx411c7mD

使用方法

在线使用

直接打开 https://bili-parser.vercel.app,粘贴链接之后点获取信息,然后点复制信息就可以了。

API

除了网页之外,也提供了两个API,方便在别的地方调用。

  • /api/parse: 返回JSON格式的视频信息
  • /api/copy: 返回和网页上复制的内容一样的纯文本
1
2
curl "https://bili-parser.vercel.app/api/parse?url=BV1xx411c7mD"
curl "https://bili-parser.vercel.app/api/copy?url=BV1xx411c7mD"

其中url参数可以是BV号,也可以是URL编码之后的完整链接。

自己部署

1
2
3
4
5
git clone https://github.com/ControlNet/bili-parser.git
cd bili-parser
cp .env.example .env
npm install
npm run dev

然后访问 http://localhost:5173 就可以了。.env 里面有以下几个变量:

  • WHISPER_ASR_URL: Whisper服务的地址,用于生成AI字幕
  • OPENAI_API_KEY,ANTHROPIC_API_KEY,GOOGLE_GENERATIVE_AI_API_KEY: 用于AI摘要和对话,至少配置一个,按照这个顺序使用第一个配置了的

如果只用视频信息和复制功能,不需要配置任何环境变量。

Whisper服务需要部署在有GPU的机器上,可以直接用打包好的docker镜像。

1
2
3
4
docker run -d --gpus all -p 9000:9000 --name whisper \
-e ASR_MODEL=base \
-e ASR_ENGINE=openai_whisper -v $HOME/.cache:/root/.cache -e "UID=$UID" -e "GID=$GID" \
controlnet/whisper-api

实现

整个项目使用SvelteKit[1] + Tailwind CSS,部署在Vercel上。由于浏览器直接请求B站的API会遇到跨域的问题,所以对B站的请求都是通过SvelteKit的服务端接口转发的。

architecturearchitecture

Fig. 3. bili-parser的整体架构

如上图所示,网页和iOS快捷指令都是通过bili-parser的服务端API来获取信息的,服务端再去请求B站的API和CDN。AI字幕的部分交给自己部署的Whisper服务,Whisper服务会自己去B站的CDN下载音频。AI摘要和对话则是把视频信息和字幕一起发给大模型的API。Windows上的Python脚本是一个例外,它直接请求B站的API,并不经过bili-parser。

视频信息

第一个版本是一天之内做完的。输出视频的metadata还是比较容易的,B站有现成的接口可以拿到视频信息、UP主的粉丝数和在线人数,整理成固定的格式就行。b23.tv的短链则是在服务端请求一次短链,不跟随跳转,从返回的Location里读出真正的视频地址。

比较麻烦的是封面图。B站的图片有防盗链,所以又加了一个图片代理的接口,带上B站的Referer去获取封面。复制富文本的时候,封面会转成base64直接嵌入到HTML里,如果图片超过5MB,就按0.8倍一步一步缩小,直到小于5MB为止。

AI字幕和对话

做完基本功能之后,又想要能基于视频的内容进行对话,所以首先需要获取视频的全部字幕。但是结果发现,B站的字幕没法匿名获取,必须要登录账号,这样可能会造成一些别的问题。

所以换了一个思路,自己部署一个Whisper[2]服务来识别视频里的语音。具体的流程是,bili-parser先通过B站的接口拿到视频的音频流地址,然后发给Whisper服务,Whisper服务下载音频之后识别出所有的transcript。这里fork了whisper-asr-webservice[3],做了以下几个修改:

  • 音频地址: 支持直接传入audio_url,由服务端自己下载音频
  • 异步任务: 用/submit提交任务,然后用/get/{job_id}查询结果,网页端每10秒轮询一次
  • 缓存: 用bili_{bvid}_{cid}作为音频的id,识别结果存到SQLite里,同一个视频不需要重复识别

改完之后打包成了docker镜像controlnet/whisper-api。一开始字幕接口是同步请求的,同一天晚上就改成了异步提交加轮询,然后又加上了缓存。另外Whisper识别出来的中文经常是繁体,所以还要再转成简体。

有了字幕之后,把视频的背景信息和字幕一起作为上下文交给大模型,就可以生成摘要和进行对话了。大模型的部分用的是Vercel AI SDK[4],支持OpenAI,Anthropic和Google三家。自己用的是Google的免费API,摘要用的是Gemma 3,对话用的是Gemini 2.0 Flash。

Windows和iOS

有了这些功能之后,除了在网页上点击使用,也希望在别的地方能方便地使用。

首先是Windows。在Windows上,没有办法简单地通过某个API把带封面的内容自动复制到剪贴板,如果要用API,怎么说也得套个壳。所以就写了一个本地的Python脚本,直接对Windows的剪贴板进行写入。这个脚本完全重现了网页端复制的逻辑,但是并没有基于bili-parser的API来写。

然后是移动设备。iOS上用的是快捷指令,请求bili-parser服务端的API,获取视频的相关信息然后复制。但是这样只能复制文本,没有办法把封面图一起复制,对分享来说还是有一些限制。

412风控

2026年9月的时候,发现B站的API会返回412,也就是被风控了。之前为了模仿浏览器,服务端在请求时手动加了一个Chrome 91的User-Agent和Referer等header,最后的修复反而是把这些伪造的header全部删掉,直接请求就恢复正常了。

现在的问题

用了一段时间之后,发现这里面还是有一些问题,可以作为后续改进的部分。

  • 字幕获取: 应该有更好的办法去获取字幕,通过Whisper去识别,总归来说是不太靠谱的。
  • 缺少视觉信息: 现在的对话完全是基于语音识别出来的文字,失去了所有的视觉能力,不能完整地反映视频的情况。
  • ASR服务: 现在依赖自己的本地服务器来跑Whisper,但是实际上像Cloudflare[5]这样的平台都有免费档的语音识别服务,没有必要自己去搭服务器。而且现在也没有支持OpenAI兼容的语音API。
  • Windows脚本: 可以直接重用bili-parser服务端的API,本地只负责和剪贴板交互,甚至用PowerShell就够了,不需要Python。
  • iOS快捷指令: 只能复制文本,封面图的部分还需要想办法解决。

总结

本文介绍了bili-parser,一个解析B站视频信息、方便分享B站链接的小工具,以及后来加上的AI字幕、摘要和对话功能。目前视频信息和复制的部分已经可以满足日常使用,AI和多端的部分还有不少可以改进的地方,以后有空再继续做。

参考文献

  • [1] "SvelteKit • Web development, streamlined", Svelte, 2025. https://svelte.dev/docs/kit
  • [2] A. Radford, J. W. Kim, T. Xu, G. Brockman, C. McLeavey, and I. Sutskever, “Robust Speech Recognition via Large-Scale Weak Supervision,” in Proceedings of the 40th International Conference on Machine Learning (ICML), 2023, pp. 28492–28518.
  • [3] "ahmetoner/whisper-asr-webservice", GitHub, 2025. https://github.com/ahmetoner/whisper-asr-webservice
  • [4] "AI SDK", Vercel, 2025. https://ai-sdk.dev/
  • [5] "Workers AI", Cloudflare, 2025. https://developers.cloudflare.com/workers-ai/