在群晖 NAS 上搭了一套播客自动转写和 AI 整理系统

技术专业 · 今天

最近在群晖 NAS 上又折腾了一个东西。

我之前一直用 Podsync 自动下载一些播客节目,基本上每天都会有新的音频下来,中文比较多,也有一些英文和葡萄牙语的节目。

音频下载下来以后,平时当然可以直接听,但是我慢慢发现一个问题。

播客这种东西,有的节目特别是存在多人交互的节目,废话比较多,音频不容易快速捕捉到关键需要的内容。

所以我就想,既然这些节目已经全部自动下载到 NAS 里面了,能不能干脆再往后走一步:

音频下载以后,自动转成文字,然后再通过 AI 整理成比较适合阅读的文章。

这样以后一个节目既可以听,也可以看,还可以搜索和长期保存。

于是就开始折腾。

一开始想直接在 NAS 上跑 Whisper

最开始我的想法其实很简单。

既然文件都在 NAS 上,那就在 NAS 上部署一个 Whisper,下载完以后直接本地转写。

但是检查了一下我的群晖以后,很快就放弃了这个方案。

我这台是 DS218+,CPU 是 Intel Celeron J3355,双核。内存之前已经扩展到了大约 10GB,但是没有独立显卡,而且这个 CPU 连 AVX/AVX2 都不支持。

小模型其实也不是完全不能跑。

问题是,能跑和适合长期跑完全是两回事。

我的 Podsync 每天新增的音频差不多有两个小时。如果为了速度用很小的模型,准确率不太满意;如果用大一点的模型,NAS 的处理速度又很可能赶不上每天新增音频的速度。

而且 NAS 本身还有其他服务,我也不希望为了转写播客,长期把 CPU 跑满。

后来想了一下,其实没有必要非得让 NAS 干 AI 推理这件事情。

NAS 最适合干的事情还是存储、调度和自动化。

所以最后整个思路变成了:

NAS 负责发现文件、切分音频、管理任务、保存结果和提供网页。

真正比较耗资源的 Whisper 转写,交给云端。

这么一改,整个事情一下就简单了。

每天两个小时的音频,免费额度基本就够用了

我先统计了一下 Podsync 最近24小时下载的文件,正常情况下每天新增大约两个小时音频。

这个量其实不算大。

所以最后接了服务:

  • Cloudflare Workers AI 的 whisper-large-v3-turbo。

现在网页上还会分别统计 Cloudflare 当天已经使用了多少分钟,以及大概还剩多少额度。

这样每天打开网页,大概就知道今天还能处理多少音频。

后面基本就不需要我管了

整个程序最后被做成了一个独立的 Docker 应用。

Podsync 继续干它原来的事情,只负责下载播客。

我的程序把 Podsync 的音频目录挂载进来。

容器启动以后先扫描一次,之后默认每30分钟扫描一次。

这些东西我都放到了网页设置里面,包括扫描哪个目录、多久扫描一次、回溯多长时间,以及 主服务 和 Cloudflare 谁优先,都可以自己改。

这里还有一个比较重要的问题,就是怎么判断一个文件是不是已经处理过。

因为容器可能重启,Podsync 目录里面又一直有很多历史文件,总不能每次启动以后重新转一遍。

所以程序会根据文件路径、文件大小和修改时间生成一个指纹。

处理过以后就记录下来。

以后再次扫描到这个文件,直接跳过。

另外还有一个细节。

Podsync 下载一个比较大的节目时,文件虽然已经出现在目录里面了,但实际上可能还没有下载完成。

所以程序发现新文件以后不会马上转写,而是先确认这个文件至少60秒没有发生变化。

只有确认文件已经稳定以后,才会加入转写队列。

默认只处理过去24小时新增的节目。

如果某一个任务失败了,也可以直接在网页上点一下重新加入队列,不需要去服务器上执行命令。

长音频的问题也顺便解决了

播客还有一个比较麻烦的地方,就是文件经常很大。

一个节目一个小时甚至两个小时都很正常,但是免费的 Whisper API 通常都会限制单个文件大小。

所以我又在中间加了一层 FFmpeg。

如果发现音频超过22MB或者超过20分钟,就先自动处理。

统一转换成:

16kHz、单声道、48kbps。

然后按照20分钟左右切成多个片段。

这些片段分别送到 Whisper。

全部完成以后,再把文字稿重新合并,同时把每一段的时间戳恢复到原始音频的位置。

所以从使用者的角度其实感觉不到音频被切过。

看到的还是一整期节目和一份连续的字幕。

这些临时生成的切片在任务完成以后也会自动删除,不会一直占着 NAS 的空间。

做到这里以后,我又觉得光有字幕还不够

Whisper 转出来的文字,拿来搜索或者定位其实已经很好用了。

但是如果真的想从头到尾阅读,体验还是差一点。

因为语音识别出来的内容经常会有断句问题,有些地方没有标点,也会出现一些同音错字。

尤其是长节目,直接看原始字幕还是挺累的。

所以我又把 LiteLLM 接了进来。

Whisper 完成以后,字幕会自动进入另外一个 AI 处理队列。

这里我其实不是想让 liteLLM 帮我“总结”。

这一点我专门做了比较严格的限制。

我要的是整理,不是摘要。

也就是说,原来节目讲了什么,就尽量全部保留下来。

AI 只负责把明显的识别错误修一下,把标点、断句和自然段整理好,让它从一份机器字幕变成一篇正常人比较容易阅读的文章。

所以给 liteLLM 的要求里面专门写了:

不能摘要,不能缩写,不能删除观点、事实、数字和案例,也不能自己增加原文没有的结论。

中文就是中文,英文就是英文,葡萄牙语还是葡萄牙语,也不做翻译。

只根据上下文修正能够确认的识别错误。

我还专门加了一个长度检查。

因为大模型处理长文本的时候,有时候你明明告诉它不要总结,它还是可能自作主张给你压缩成一篇短文。

如果返回的文章明显比原始字幕短很多,系统就认为这次处理有问题,直接拒绝保存。

这个任务会显示失败,以后可以单独重新跑 liteLLM。

这样也不会重新消耗 Whisper 的额度。

最后干脆做了一个自己的播客阅读器

既然字幕和文章都有了,最后就又顺手做了一个 Web 页面。

现在打开以后,可以按照 Podsync 的目录看到不同节目。

可以按节目筛选,也可以直接搜索文件名或者节目名称。

每一期节目都会显示音频长度、有没有转写完成、使用的是 主服务 还是 Cloudflare,以及 liteLLM 的文章有没有整理完成。

点进去以后,上面直接就是播放器。

音频支持 Range,所以不需要把整个文件下载完以后才能播放,进度条可以直接拖。

下面是原始字幕。

每一段字幕都有时间戳。

点某一句,播放器就直接跳到那个时间。

反过来,在播放音频的时候,当前正在播放的字幕也会自动高亮。

所以有时候听到一个地方没有听明白,直接看下面的文字就可以。

或者看到字幕里面某一句比较有意思,点一下就能马上听原始音频。

Cloudflare 的时间戳还带来了一个小问题

Cloudflare 返回的时间戳比较细,甚至可以到单词级。

一开始我直接把这些内容放到网页上,结果一个比较长的节目可能会产生上万个页面元素。

桌面浏览器还好,手机上就开始明显变慢了。

后来又做了一层聚合。

把这些单词级的时间戳自动合并成大约八秒或者一句话一个段落。

这样页面元素少了很多,但是点击字幕跳转音频的功能还保留着。

手机端后来也重新调整了一次。

最早做的是一个全屏浮层阅读器,桌面上看没什么问题,但是手机上一打开详情,整个页面都被遮住了。

现在改成了页面内嵌。

节目列表、字幕和 AI 文章分别控制高度,各自滚动。

桌面、平板和手机基本都能正常使用,深色和浅色主题也都支持。

第一次正式跑了接近三个小时

整个系统部署完成以后,我第一次正式跑的时候,一共发现了4个新的音频文件,总时长接近三个小时。

然后我就基本没管它。

系统自己检查文件,自己压缩和切片,先用 主服务的whisper 转写。

主服务的whisper 碰到额度限制以后,自动切到了 Cloudflare。

字幕全部完成以后,又自动交给 litellm 整理文章。

最后再去看网页的时候,4个节目已经全部处理完成。

没有失败任务,也没有积压。

整个流程现在实际上已经变成:

Podsync 下载播客→ NAS 自动发现→ 检查文件是否下载完成→ 指纹去重→ 长音频自动压缩、切片→Whisper 转写→ 主服务不可用时自动切 Cloudflare→ 合并字幕和时间戳→ liteLLM 修正识别错误并整理文章→ 网页里面播放、定位和阅读。

最后得到的东西,和我一开始想的已经有点不一样了

一开始其实只是想解决一个很简单的问题:

怎么把 NAS 里面下载的播客自动转成文字。

但是做到最后,已经不只是一个转写工具了。

现在 Podsync 只要把节目下载下来,后面的事情我基本都不用管。

过一会再打开网页,音频、字幕和整理后的文章都已经在那里了。

想听就听。

想快速浏览就看字幕。

看到感兴趣的地方可以直接点时间戳跳过去听。

如果只是想安静地读内容,就直接看 liteLLM 整理后的文章。

而且所有节目最后都留在自己的 NAS 上,可以搜索,也可以长期保存。

我现在反而越来越觉得,对于 DS218+ 这种已经比较老的 NAS,没有必要什么 AI 都想着在本地跑。

让它去跑大模型,本身就不是它擅长的事情。

但是把 NAS 当成一个自动化中枢,我觉得反而非常合适。

文件在这里,Podsync 在这里,Docker 在这里,任务调度、FFmpeg、数据库和网页也都在这里。

真正需要算力的时候,再调用外面的 主服务whisper、Cloudflare 和 liteLLM。

这样既绕开了旧硬件性能不够的问题,也基本利用免费额度把每天新增的播客处理掉了。

Theme Jasmine by Kent Liao