为了学 AI,我用 Go + Fyne 手撸了一个原生视频下载器
大家好,我是老墨。
一个写了十几年代码,发际线依然坚挺,但最近确实有点焦虑的中年程序员。
为啥焦虑?还不是因为这该死的 AI。
从 24 年初 Sora 横空出世,到 Claude 3.5 杀疯了,再到最近 DeepSeek 甚至能自己修 Bug,这世界变化快得像开了二倍速。以前我们卷算法、卷架构,现在倒好,不仅要卷提示词,还要防着被自己的 IDE 抢了饭碗。
老墨我痛定思痛,觉得不能坐以待毙。打不过就加入嘛,我也开始疯狂恶补 AI 知识。
01 一个悲伤的故事:学习资料太多也是一种烦恼
要学 AI,最好的路径是什么?这年头文档更新赶不上模型迭代,最鲜活的知识全在视频里。YouTube 上的 Andrej Karpathy 大神课,B 站上的各种论文精读、实战通过…
于是我的浏览器收藏夹很快就爆了。
但我这人有个毛病,看视频喜欢囤。一来是有些干货太硬,得反复咀嚼;二来是作为技术人,总有一种由于网络不确定性带来的"松鼠症"——好东西必须存在本地硬盘里才踏实。
这时候问题来了:市面上的下载器,怎么就没一个顺手的?
- 某雷:广告多是其次,关键是我想下的 YouTube 和 B 站视频它基本都解析不了。对于我们这种想看外网 AI 前沿教程的人来说,它形同虚设。
- 某 IDM:嗅探功能很强,但碰到 YouTube/B 站 这种音视频分离的高画质(DASH流)视频就歇菜了,经常只能下个无声画面,心累。而且 Mac 上没有原生版,体验割裂。
- Electron 系工具:界面是好看了,但这动不动几百兆的内存占用,我开个 PyCharm 跑模型本来就捉襟见肘,哪还有余粮养它们?
- 命令行 yt-dlp:这是真神,功能无敌。用了很久,自己写脚本都写了多个。但时间久了脚本太多,每次想下个视频还得敲命令,复制粘贴 URL,还得拼代理参数… 实在是比较麻烦。
“求人不如求己,为什么不能自己写一个GUI?”
这念头一出,我就兴奋了。写了几年的 Golang,这点事情不在话下。说干就干,开搞开搞。
02 技术选型:要做就做原生的
既然决定自己干,那必须立好 Flag:
- 要快:启动快,下载快。
- 要美:虽然我是后端出身,但也不能忍受丑陋的 UI。
- 要轻:拒绝 Electron,拒绝 WebView,我要纯原生。
基于这个标准,技术栈基本就锁死了:
语言:Go
Go 的并发模型 (Goroutine) 处理多线程下载简直是降维打击,编译出来就是一个二进制文件,干净利落。
GUI 框架:Fyne
这是个基于 OpenGL 渲染的 Go 原生 UI 库。说实话,刚开始用的时候有点痛苦,因为它的布局逻辑和传统 HTML/CSS 完全不同,但一旦上手,那种丝滑的渲染速度和跨平台的便利性(一套代码跑 Windows/Mac/Linux),真香。
核心引擎:yt-dlp + FFmpeg
没必要重复造轮子。核心解析和下载调用 yt-dlp,视频音频合并用 FFmpeg。我做的是一个极致的调度器和 GUI Wrapper。
03 核心架构大揭秘
老规矩,不谈架构只吹牛逼都是耍流氓。
这个软件我起名叫 VDD (Video Download Desktop),架构其实并不复杂,核心就为了解决一件事:如何优雅地管理异步任务。
1+---------------------+ +------------------------+
2| UI Layer (Fyne) | <---> | Core Logic (Go) |
3| [Main] [Sub] [Set] | Events| [Manager] [Scheduler] |
4+---------------------+ +------------------------+
5 ^ |
6 | Binding | Dispatch
7 | v
8+---------------------+ +------------------------+
9| Data Storage | | Downloader Engine |
10| (SQLite/JSON) | | [Queue] [Worker] |
11+---------------------+ +------------------------+
12 |
13 +------------+-------------+
14 | Exec (Pipe)| | Exec
15 v v v
16 +--------------+ +--------------+
17 | yt-dlp | --------> | ffmpeg |
18 | (Extraction) | Raw | (Merge) |
19 +--------------+ +--------------+
这里有几个有点意思的技术点(也是踩坑最多的地方):
1. 高并发下的状态一致性与 DATA RACE
看似简单的“并发下载”,实则暗藏杀机。VDD 需要同时维护“内存中的任务状态队列”和“SQLite 数据库”以及“GUI 视图状态”。
起初我天真地用了原生 Map 来管理任务,结果程序跑起来动不动就 Panic。因为多个 Goroutine(下载进度更新、用户点击删除、定时刷新)同时在读写。
解决法宝:
- 全量改用
sync.RWMutex读写锁保护临界区。 - 通过
channel将所有状态变更收敛到一个单线程的 Controller 中串行处理,这种 “Do not communicate by sharing memory; share memory by communicating” 的 Go 哲学,真不是盖的。
2. 跨进程管道通信 (IPC) 的“黑魔法”
yt-dlp 是一个外部进程,怎么把它的进度条“偷”出来?
- 难点一:缓冲区死锁。如果不及时消费 stdout/stderr,Pipe 满了会导致子进程卡死。我起初遇到这个问题查了好久,最后发现必须用独立的 Goroutine 去实时 Drain 掉输出流。
- 难点二:僵尸进程。用户手快点了“取消”,GUI 上任务删了,后台 yt-dlp 还在偷偷跑流量。这里必须利用 Go 的
Context上下文机制,精准控制进程树的 Kill 信号,确保连带 FFmpeg 子进程一起干掉。 - 难点三:正则解析。不同平台的输出格式千奇百怪,为了洗出准确的“剩余时间”和“下载速度”,我写了一套复杂的正则状态机。
- 难点四:状态管理。由于 goroutine 多,core 与 UI 层的状态同步是个大麻烦,我专门定义状态管理器来驱动状态的流转。
3. Fyne UI 的性能调优与自定义渲染
Fyne 是基于 OpenGL 的,渲染极快,但它的布局系统如果不熟悉,很容易写出“又丑又慢”的界面。
- 懒加载列表:当你有 1000 条下载记录时,直接渲染会卡死。我重写了
widget.List的 Refresh 逻辑,利用它的复用机制,只渲染屏幕可见的十几条 Item,丝滑度瞬间提升 10 倍。 - 中文字体:Fyne 默认字体不支持中文,为了打包体积(不能塞个 20MB 的中文字体进去),我精选了一款轻量开源字体并嵌入二进制,解决了乱码问题,同时让编译后的体积控制在 100MB 以内(windows集成ffmpeg会比较大,mac和linux提及都比较小,几十M)。
4. 订阅系统的增量比对 (V1.4.0 新功能)
为了追 AI 大佬的更新,我实现了一个轻量级的订阅系统。核心逻辑是:定时任务 + 增量比对。 软件启动时,后台静默调用 yt-dlp 获取频道列表,和本地 SQLite 数据库比对。发现新的 Video ID,直接推入下载队列。整个过程对用户完全无感。你早上打开电脑,倒杯咖啡的功夫,大佬昨晚发的最新论文解读视频已经躺在你的硬盘里了。
04 成果展示:VDD 长啥样?
经过几个月的打磨,VDD 现在的状态我已经比较满意了。
极简主义的界面
没有广告,没有多余的按钮。

多主题风格支持
目前支持浅色和深色两种主题,支持跟随系统。

单视频下载模式
你可以随时下载单个喜欢的视频,最高可下载 8K 分辨率哦。

智能的订阅系统
这就刚才说的“追更神器”。看这一个个小红点(角标),那是知识在向我招手。

强悍的下载能力
4K、8K 原画只要源视频支持,统统能下。而且利用 Go 的特性,我做到了对系统资源的极低占用。几十个任务同时跑,界面依然 60 帧丝滑。
05 写在最后
其实开发 VDD 的过程,比使用它更有趣。
以前总觉得 GUI 开发麻烦,但通过这次实战,发现 Go + Fyne 的组合在开发效率和运行性能之间找到了一个绝佳的平衡点。当然,过程中也没少踩坑,比如 Fyne 的中文字体渲染问题、macOS 的系统权限问题等等,有机会再专门写一篇文章吐槽。
这个项目我准备将 Core 部分进行开源,目前在代码整理阶段,敬请期待。虽然代码写得可能不如那些大牛优雅,但也算是老墨在这个 AI 时代交出的一份作业吧。

VDD (Video Download Desktop)
VDD 仍然在快速迭代,欢迎关注。如果你也有视频下载的需求,或者单纯想看看 Go 写桌面应用能做到什么程度,欢迎去下载体验,别忘了提出你的宝贵的意见和建议哦。
在这个技术飞速迭代的时代,保持好奇心,保持动手能力,或许是我们作为程序员最后的倔强。
我是老墨,我们下期见。