2026-09-23 08:00:00
做视频的人大概都遇到过这种小事:视频已经剪完了,只差把字幕贴上去,但为了这么一点收尾工作,还得打开一个不算便宜的剪辑软件,或者把文件传到某个在线网站,等处理、等下载,顺便担心视频会不会留在别人服务器上。
今天我刚好就遇到了这么一个问题,公司需要录制几段简单的教程视频,等我录制完准备添加字幕的时候发现,我找了一圈没找到特别顺手的工具,不是要登录注册就是要上传字幕文件等。
我的需求很简单,就是给一段视频填写几段字幕说明,这么一个简单的需求,硬是折腾了我半天,最后我使用了口播的方式去解决了。但是当我坐下来休息的时候开始思考,为什么没有这么简单的工具,大概率是有的,只是我没找到而已。
想了半天决定自己动手。
于是我做了“字幕工作室”:一个完全运行在浏览器里的视频字幕编辑和烧录工具。拖进一个本地 MP4,在时间轴上分段、写字、调样式,预览满意后直接导出带字幕的视频。不用注册,不用后端,也没有水印。
它不是一个完整的视频剪辑器,定位很窄,就是处理字幕这件事。

时间轴是核心。视频播放到某个位置,添加一个字幕段,再编辑文字和起止时间。字幕段可以拖动、复制、删除,预览区会按照当前播放时间实时把字幕叠加到画面上。样式可以统一设置,也可以单独覆盖某一段,比如大部分字幕都放底部,只有个别说明文字放顶部。颜色、字号、透明度、位置和对齐方式都能调。
真正麻烦的部分其实在导出。浏览器里播放视频很简单,但要把字幕重新画进每一帧,再编码成一个新的 MP4,并不只是截几张图。这里用到了浏览器提供的 WebCodecs,通过 VideoDecoder 逐帧解码原视频,把画面和字幕一起绘制到画布上,再用 VideoEncoder 编码,最后交给 mp4-muxer 封装。音频如果原本是 AAC,就直接转封装进新文件,尽量避免重新处理。
原视频的解析交给 mp4box。导入后先读取分辨率、时长、编码和音轨这些信息,导出时也据此决定能否走完整流程。听着像一条很自然的流水线,实际写起来要处理不少细节:编码器可能延迟返回元数据,解码和编码队列需要一边消费一边背压,视频帧用完必须及时释放,浏览器不同版本对 H.264 的支持也会有差异。很多工作不是“能不能实现”,而是怎样让长视频在内存和性能都有限的情况下尽量稳定。
这个工具坚持本地处理,不是为了显得技术先进,而是因为视频文件本来就适合留在本地。整个过程中视频不会上传,编辑状态和最近使用的视频通过 IndexedDB 存在浏览器里,下次打开可以继续。代价也很明确:缓存依赖浏览器配额,清空站点数据就会丢失;超大视频仍然受设备内存和编码器限制。目前最稳妥的环境还是桌面版 Chrome 或 Edge,Safari 和部分视频编码格式会受到平台能力约束。
我比较喜欢这种形态。它没有后端,没有账号体系,也没有复杂的协作功能,打开网页就能完成一件具体的事。用完关掉页面,项目留在自己的电脑上,不需要考虑文件什么时候会被删除,也不需要为了一个边缘需求维护一整套服务器。
欢迎体验,可以提一些意见和改进方案。
当前更新,支持本地识别音频,转为文字自动生成字幕到对应的时间节点,然后可以手动调节内容(毕竟识别也不是 100% 准确)。
GitHub: https://github.com/anghunk/video-transcript
在线使用:https://video-transcript.zishu.me
2026-09-22 08:00:00
上一篇文章写了怎么把 Trae 桌面端的模型变成本地 OpenAI 兼容接口。当时脑子里想的还是“代理”:Trae 的私有协议进来,标准的 OpenAI 协议出去,能调通就行。
用了一段时间之后,想法慢慢变了。
因为我发现自己在调的不只是 Trae。DeepSeek 的官方 key、Kimi、智谱、本地 Ollama,甚至偶尔用一下 Anthropic 和 Gemini——每个都有自己一套地址、key、模型名。Trae 这边有两个区域,代理还得开两个端口。客户端里塞了四五套配置,每次换工具都要重新填一遍。
所以后来重写的时候,方向就不是“把 Trae 转发得更稳”,而是“把本地所有模型渠道收拢成一个入口”。也就是一个本地大模型网关。
代理做的是“翻译”:上游是什么,就尽量原样透传成什么。
网关做的是“收口”:不管上游是 Trae、OpenAI 兼容服务、Anthropic、Gemini 还是 Ollama,对客户端来说都只是同一个地址、同一个模型列表、同一套鉴权。
具体到现在的项目:
所有渠道都注册在同一个网关里,模型 id 统一带前缀,比如 trae-cn/glm-5.3、deepseek/deepseek-chat、ollama/llama3.1。客户端只认一个 baseURL,配一把 key,从 /v1/models 拉到什么就调什么,不需要知道模型背后是哪家。
网关自己负责拆模型 id、选渠道、转发、记录用量。Trae 登录态这种只有本机能拿的东西,也成了其中一个渠道,而不是整个项目的全部。
做成网关之后,很多以前“凑合能用”的地方自然就有了答案。
之前 key 只有“有”和“没有”两种状态,现在可以限制某个 key 只能访问某些渠道、某些模型。之前用量全靠猜,现在每次请求都会落到 SQLite,管理台能看到汇总、分布和最近记录。之前想给 CC Switch、opencode 配置,得手动填地址和 key,现在管理台直接唤起官方导入,opencode 也有脚本自动写入实时模型目录。
这些功能单个看都不算复杂,但合在一起才像“网关”而不是“脚本”。
这个项目做到后面,最大的变化不是代码变多,而是看待它的方式变了:Trae 只是模型来源之一,真正的产品是本地那个统一的 /v1 端点。
如果你只是想偶尔在别的工具里用一下 Trae,一个轻量代理就够了。但如果你想在自己的电脑上长期维护一个“什么模型都能接、所有客户端都只配一次”的入口,网关这个思路更顺。
实现思路和部分代码参考:
版权与署名见仓库的 LICENSE 与 THIRD_PARTY_NOTICES.md。
Github: https://github.com/anghunk/trae-proxy
提醒:本项目只适用于自己已登录的账号,请遵守 Trae 的服务条款,不要用于绕过计费或多人共享。
2026-09-21 08:00:00
Trae 桌面端登录后能用一批模型,但只能在它自己的界面里用。我想在别的工具里也调这些模型——不想再单独买 API key,也不想把凭据复制来复制去。
于是就有了 trae-proxy:一个纯 Node、零第三方依赖的本地代理,把 Trae 桌面端已登录的模型暴露成标准的 OpenAI 兼容 API。任何支持 OpenAI 协议的客户端、IDE 插件、SDK、CLI,填上 baseURL + apiKey 就能用。
国内版(cn) http://127.0.0.1:39303/v1
国际版(ai) http://127.0.0.1:39304/v1
一个进程同时服务两个区域,各自用独立的持久 bearer key 做本地鉴权。
Trae 桌面端的登录态是加密存在本地 storage.json 里的,对话走的是私有 SSE 事件协议。所以代理要做三件事:
node:crypto:硬编码盐 + AES-128-CBC + SHA-512 KDF,不需要安装 Trae 之外的任何东西。Cloud-IDE-JWT 鉴权、SOLO 通道的请求体格式。/v1/chat/completions 增量,包括 tool_calls 和 usage。有一个刻意的设计:token 刷新只在进程内存里做,绝不写回桌面端文件、不落地副本。代理只读登录态,也不提供账号切换——避免把「读」变成「改」。
运行门槛也压到了最低:Node 22.19+ / 24+,靠原生类型擦除直接跑 TypeScript,不用构建、不用 npm install。
这部分才是真正费时间的地方。
某天开始,所有对话请求都失败,但表现非常具有误导性:
4011 requests have exceeded the rate limit(看起来像被限流)4001 the param is invalid(看起来像参数写错)实际原因是上游协议更新,请求体新增了 request_id / session_id 两个必填字段,老客户端不传就被拒。而且服务端用 HTTP 200 + error 事件来报错,状态码完全帮不上忙。
修法很简单——逐请求生成两个 UUID(去掉横杠)。但从「限流」这个误导性错误码反推到「缺字段」,绕了不少路。
我用的不是私有部署,是购买的企业版 SaaS 套餐。这类账号的 storage.json 里会有一个 iCubeHostInfo:
"iCubeHostInfo": {
"consoleHost": "https://console.enterprise.trae.cn",
"apiHost": "https://console.enterprise.trae.cn"
}
企业版账号在公开网关上会被拒(就是上面那个假的 4011)。所以代理的做法是:
每个请求实时读取当前登录态的
iCubeHostInfo,自动选路——探测到企业网关就整体切过去(对话和模型目录都是),普通账号回落公开网关。
好处是切换账号不用重启进程。这也是为什么文档里要同时说明普通版和企业版都支持。
Trae 的模型不是从单一接口拿的,而是分散在多个 directory function 下。代理的策略是按优先级 union 所有能拉到的结果,first-wins——同一个模型出现在多个 function 里时,第一个命中的决定调用时该带哪个 function 字段。
因此一个很实际的问题:CN 区最初漏了 solo_agent 这个目录,导致客户端里有、代理里没有几个模型。把 solo_agent 追加到目录列表末尾(借 first-wins 保证不影响已有模型)后,一次性多出 4 个可用模型。
这是最反直觉的一个。
现象:DeepSeek-V4.1-Flash 在 Trae 客户端里能正常选择、能正常生成,但走代理转发就稳定报 4001 the param is invalid,其他模型全部正常。
排查过程(穷举):
function:solo_work_lite / solo_work_remote / solo_agent / chat_v3 / builder_v3 / builder —— 全部 4001agent_type / mode_type 组合 —— 目录数量不变__dev、__max、全小写、-Official —— 全部 4001最后换到客户端 builder 用的 create_agent_task 通道,服务端终于给出了直白的错误:
config item is empty for config opt:
{"Function":"solo_agent","ConfigName":"DeepSeek-V4.1-Flash",...}
服务端配置表里就没有这个模型。
那客户端为什么能用?因为 Trae 桌面端的 native 二进制内置了一份 fallback 模型表。客户端本地缓存里确实有 DeepSeek-V4.1-Flash,但它缺少服务端下发时才有的 encrypted_model_params 字段——说明它来自本地内置表,而不是网关下发。客户端自己也清楚这一点,日志里会回退:
legacy recent selection unresolved before fallback
{"legacyModelKey":"1_-_DeepSeek-V4.1-Flash","fallbackApplied":true}
结论:这是客户端「内置了模型」和「网关服务端还没开通」之间的时间差。代理侧无能为力,只能等后台开通。
这个项目里最花时间的不是写代码,而是从误导性的错误码里反推真实原因。HTTP 200 + 业务错误事件、假的「限流」、客户端有服务端没有的模型,这几件事凑在一起,每一步都能把人引到错误的方向。
我的经验是:当同一个请求「状态码正常但内容不对」时,不要相信错误信息本身,要去构造对照实验——固定其他变量只换一个,看哪个变量真正改变了结果。上面那条 config item is empty 就是这么逼出来的。
这个项目是站在别人肩膀上的。实现思路和部分代码参考了:
版权与署名见仓库的 LICENSE 与 THIRD_PARTY_NOTICES.md。
Github: https://github.com/anghunk/trae-proxy
提醒:本项目只适用于自己已登录的账号,请遵守 Trae 的服务条款,不要用于绕过计费或多人共享。
2026-09-19 08:00:00
升级 Xcode 27 后,在 HBuilderX 里运行到 iOS 模拟器基座,直接报错:
Command failed: open -a Simulator
Unable to find application named 'Simulator'
Xcode 26 及以前一直正常,这是 Xcode 27 才出现的问题。
Xcode 27 起不再附带 Simulator.app,模拟器界面并入了新的 DeviceHub.app:
ls /Applications/Xcode.app/Contents/Applications/
# DeviceHub.app 在这里,Simulator.app 没了
模拟器本体没坏(simctl、运行时、设备都正常),但 HBuilderX 启动模拟器时写死执行 open -a Simulator,系统里已经没有叫这个名字的应用,于是启动失败。这是 DCloud 论坛的已知问题,官方暂未适配。
在 /Applications 下创建一个名为 Simulator.app 的转发壳,把 open -a Simulator 转给 DeviceHub:
mkdir -p "/Applications/Simulator.app/Contents/MacOS"
cat > "/Applications/Simulator.app/Contents/Info.plist" <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>Simulator</string>
<key>CFBundleDisplayName</key>
<string>Simulator</string>
<key>CFBundleIdentifier</key>
<string>com.local.simulator-launcher</string>
<key>CFBundleExecutable</key>
<string>SimulatorLauncher</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSUIElement</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>13.0</string>
</dict>
</plist>
EOF
cat > "/Applications/Simulator.app/Contents/MacOS/SimulatorLauncher" <<'EOF'
#!/bin/bash
# Xcode 27 起不再附带 Simulator.app,模拟器界面由 DeviceHub 承担,
# 把 open -a Simulator 转发给 DeviceHub。
exec /usr/bin/open -a "/Applications/Xcode.app/Contents/Applications/DeviceHub.app" "$@"
EOF
chmod +x "/Applications/Simulator.app/Contents/MacOS/SimulatorLauncher"
# 注册到 LaunchServices
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister \
-f /Applications/Simulator.app
验证一下:
open -a Simulator # DeviceHub 正常打开即可
然后重启 HBuilderX,重新运行到 iOS 模拟器即可恢复和以前一样的流程。如果 DeviceHub 里没自动弹出模拟器画面,在它左侧设备列表点一下对应的 iPhone。
想撤销的话,删掉 /Applications/Simulator.app 就行;等 HBuilderX 官方适配 Xcode 27 后也可以删。
2026-08-14 08:00:00
昨天晚上 DeekSeek Harness 发了预览版本,我也是第一时间安装了,它提供了两种安装方式,下载源码运行或者通过 npm 运行。
Github: https://github.com/deepseek-ai/deepseek-harness
Npm: https://www.npmjs.com/package/@deepseek-ai/dsh

跟传统的 Agent 工具不同的话,它目前不包含客户端、cli 端,运行方式是在终端执行 dsh web 或者下面这个方式,在本地起一个服务,然后浏览器访问。
xxx@abcdeMacBook-Air ~ % npx @deepseek-ai/dsh web
dsh web: http://127.0.0.1:3080
它与大多数"以 LLM 调用为核心的框架"不同:整个产品由插件构成,没有任何特权核心。模型适配器、工具注册表、会话日志、Agent 循环、沙箱、Web 界面……每一个能力都是一枚 Cordis 插件,都能在配置层被替换、被拦截、被扩展。这种"一切皆插件"的架构让 dsh 拥有极强的可组合性。
这也是我对他很感兴趣的地方,它抛弃了 Skills、Tools、MCP、Hooks 这些概念,把一切内容都插件化,做成可插拔的形式,沙箱你不满意,OK 直接重写一个;权限控制不满意,重写一个。几乎是提供了一个骨架,然后你可以任意扩展,同时默认已经给你搭配好了。

还有一个很大的特色就是看到每条对话的思维轨迹,我觉得在学习 Agent 运行原理时非常有用。可以清除的看到每次会话,AI 都做了什么,调用了什么工具等等。
打个比方说,别的 Agent 是一辆新能源电车,你可以自己装冰箱彩电大沙发,但是不论你怎么改造,它本质上还是一辆车;但是我认为 dsh 抛弃了这套做法,他可以是一辆车,也可以是一艘船,或者一架飞机,这完全取决于开发想要怎么去搭配它。
今天玩了一天,感触很深,虽然目前还是预览阶段,但是它的生态已经初见端倪,随着时间的推移,这份生态必定会越发庞大,我非常看他好它的前景。这不只是一个 Agent 工具,更像是一个新的概念在创造。
然后我让 DeekSeek Harness 它自己读了一遍自己源码,写了一份源码解析指南,分享出来:
《DeepSeek Harness 源码解析指南》 (作者:DeekSeek Harness)
2026-08-10 08:00:00
我目前使用的 AI 工具是 Zcode,然后搭配 Opencode Go 套餐里面的 deepseek-v4-flash 模型。性价比很高,速度和模型能力都非常棒,所以被我当成主力模型。
但是它存在一个巨大的问题就是不支持多模态,官网目前好像是支持视觉识别了,但是我是调用了 api 在第三方客户端。而我恰好非常需要这个场景,所以我就想了一个办法,如果能在不更改当前会话模型的情况下,进行图片识别,那就很完美。
首先我想到了利用 skills,调用多模态模型工具去识别图片,把识别到的结果返回给当前会话模型。
这个思路听着简单,但一开始我也有点拿不准:skill 这东西,说白了就是给模型的一份 Markdown 说明书,它本身不会"跑在别的模型上"。真正干活的是当前模型——它照着说明书,用自己手头的工具(比如跑个 Python 脚本)去调外部 API。
也就是说,关键不是 skill 本身多厉害,而是它能不能把"调用外部多模态模型"这件事,变成当前模型随手就能做的动作。
于是整个流程就成了这样:
整个链路对使用者来说来说是无感的,图还是那张图,任务还是那个任务,中间只是多了一个"帮我看了看"的环节。
代码上其实就两个文件:一个 SKILL.md 负责告诉模型"什么时候该用、怎么用",一个 analyze_image.py 负责真正调 API。配置放在 config.json 里,填上 API key 就行。
而且兼容了不同 API 格式,不管是 OpenAI 格式还是 Anthropic 都可以兼容,参考下面的说明文档即可。
我目前搭配的是阿里百炼的 qwen3.7-plus,效果非常不错。可以手动下载,也可以通过指令下载
具体代码我也已经开源出来了,下载到你的 AI Agent 目录下即可,README 中也有详细说明,如果有类似的需求可以试试看。
Github: https://github.com/anghunk/image-analysis-fallback
git clone https://github.com/anghunk/image-analysis-fallback.git ~/.zcode/skills/image-analysis-fallback