Refactoring English: Month 9

Michael Lynch

《Refactoring English》:第九个月

原文由 Michael Lynch 发布,订阅该博客

一句话总结

剪辑视频访谈的快乐与痛苦

亮点

  • 从早期读者那里获得了关于章节列表的有用反馈。
  • 剪辑访谈视频的过程令人沮丧,但制作文字稿却很有乐趣。
  • 推广我的自由职业博客编辑服务的计划比预期更顺利。

目标评分

每个月初,我都会定下当月想完成的目标。以下是本月目标的完成情况:

给20位从未交流过的读者发送个性化邮件

和几位读者聊过之后,我意识到现阶段更好的策略是对所有读者做一次广泛的问卷调查

发布《Refactoring English》的新章节

  • 结果:发布了“直击要点”
  • 评分:A

我终于完成了关于“开头”的章节。这是我写起来最吃力的一章,因为开头正是我写作中最感棘手的部分。所以,这一章既是关于如何写开头,也是我试图逆向拆解自己写开头的方法。

这一章也严重超时。我最初只计划花6小时完成,结果实际花了19小时。

完成剩余的营销任务

  • 结果:完成了访谈剪辑,但未完成行动号召部分的修改
  • 评分:B+

我完成了访谈的剪辑,这是此前最大的遗留任务。我还没来得及调整书的官网设计,把重点从订阅免费邮件列表转向购买抢先体验版。

《Refactoring English》数据一览

指标2025年7月2025年8月变化
独立访客8,0612,863-5,198 (-64%)
预售收入$800.04$312.63-$487.41 (-61%)
赞助收入$48.25$48.25$0.00 (0%)
总收入$848.29$360.88-$487.41 (-57%)

上个月,我还觉得这本书的数据挺健康。尽管没有发布热门新文章,网站访问量和收入都有所上涨。

后来,我给各项数据加上了图表,才看出了不同的规律:这本书的收入似乎与官网访客数高度相关。虽然我以为7月份并没有什么热门文章,但当我8月份完全没有在官网上发布任何内容时,各项数据便一落千丈。

我的体会是,我确实需要持续在官网上发布新内容,才能不断找到新读者,尤其是愿意为这本书付费的读者。

解读读者对章节列表的反馈

在一对一与读者的交流中,大家觉得有价值的章节差异很大。我觉得要了解读者最关心哪些章节,更好的办法是发起一次问卷调查。

我原本预期回应率会很低,因为之前在邮件列表里征求反馈时,只收到了寥寥几条回复。没想到这次读者对问卷热情高涨,两周内就收到了133份回复。

我在书的官网上对这些反馈做了详细分析:

简而言之,我收获了很有价值的反馈,并据此重新调整了章节顺序,还重构了一个不受读者欢迎的章节。

这次与以往征求反馈时的回应率差异,也很有意思。以前我是在发送样章后请大家提意见,我想关键区别在于我要求读者付出的工作量不同。这次问卷几分钟就能完成,而要对一个章节提反馈,则需要花30分钟阅读和思考,也许读者收到邮件时根本没准备好投入这么多时间。

剪辑30分钟视频访谈的惊人难度

早在2024年7月,作为重启我的写作课程Hit the Front Page of Hacker News的一部分,我录制了一场与Adam Gordon Bell的访谈。结果在休陪产假前我没能完成课程改版,于是这个重启计划就被无限期搁置了。

这让这场访谈处在了一个尴尬的位置。Adam 热心地抽出时间接受采访,如果完全不发布,我会感到很愧疚。

当我开始为《Refactoring English》提供抢先体验时,我觉得正是发布这场访谈的好时机。如果大家喜欢这场访谈,或许也会去关注这本书。

你一定有过这样的任务:一直拖着不做,心里还想,“拖了这么久真傻,只要坐下来花一小时就能做完,也不用一直挂在心上了。”我当时笃定这场访谈就是这类任务。

结果完全不是这么回事。

音画不同步顽疾的回归

我用的是名为 Riverside 的服务来录制访谈。通话结束后,Riverside 会为通话双方分别生成视频文件,以及一个合并、同步好的对话版本。当时我只是抽查了一下视频,确认能正常播放,但从未仔细看过。

我以为只要拿着合并好的版本直接传到 YouTube 就行了。顶多如果有卡顿或过长的跑题,就剪掉一点,我觉得视频已经基本完成了。

直到一年后我终于坐下来仔细观看时,才发现音画不同步——还没看到嘴动,就已经听到了声音。

在 Riverside 生成的视频中,音频和视频有轻微的不同步。

没关系,我可以用FFmpeg重新处理视频,把音频稍微挪一下。

可惜没那么简单。结果发现对话两端的音画偏移并不一样:Adam 那端偏移了约425毫秒,而我这端约150毫秒。这意味着我得回到双方各自的原始未合并视频,分别修正偏移,再自行重新合并。

寻找可用的开源视频编辑器

我以前剪视频的标准工具是 Adobe Premiere,但去年我换到了 Linux,而 Premiere 并不支持 Linux。再加上,我现在已经对Adobe 这家公司感到厌倦

我开始用 Shotcut 来剪辑,这是一款我去年夏天开始学习的剪辑软件。光是弄明白如何在 Shotcut 里把两个视频并排放置就花了不少时间,最后我是靠缩放和裁剪滤镜勉强拼出来的。

在 Shotcut 里剪辑时,预览播放极其卡顿——即便在我的那台相当新的高配台式机上,合并两路1080p视频时也十分吃力。所以要听实际效果,我只能导出视频。而 Shotcut 又不支持只导出片段,每次都得导出完整的30分钟,每次都要花上好几个小时。顺带一提:我后来才发现,在 Shotcut 里可以降低预览分辨率来提升剪辑时的流畅度。

导出后我发现,每次分割片段时,Shotcut 都会插入一个刺耳的爆音。即使我在分割点上实际上什么都没剪,这个爆音也会出现。仅仅是将一段连续的片段分割成两个相邻片段,这个动作本身就会产生爆音。

仅仅分割片段而不做任何修改,就会在音频中产生“爆音”。

我发现这个爆音是Shotcut 的一个已知问题,这让我难以置信。每次分割都产生刺耳的杂音,还怎么剪视频?但不少评论者却说每个视频或音频剪辑工具都有同样的问题。

什么?!?

我用其他工具剪过数百个媒体文件,从来没见过哪个会在分割时插入爆音。

我又尝试了 Linux 上的其他开源剪辑工具。Kdenlive 在我剪了几分钟后就崩溃了。Flowblade 根本无法加载,不过我最终找到了一个变通办法。Flowblade 看起来像是更简化的 Shotcut,于是我又在 Flowblade 里重新开始剪辑,又得重新琢磨怎么做出并排视频的效果。

在 Flowblade 里剪了一个小时后,我尝试导出视频,结果爆音又出现了。他们也有相关的 bug 报告,而他们对这个问题的解释是基于 Dan Dennedy 的说法——而他正是 Shotcut 的作者。而且 Flowblade 似乎也是基于 MLT 构建的,这个媒体框架正是 Shotcut 的底层。所以,我又回到了同一个 bug 上。

总之,关于这次剪辑历险的讲述已经又长又乏味,就直接跳到结尾吧:我最终通过将视频中音频的采样率从 44.1 kHz 转换为 48 kHz 绕过了爆音问题。这消除了爆音,但我也不知道为什么会有效。

剪辑过程中我还遇到了很多其他 bug,但都太琐碎,就不在这里赘述了。

剪辑视频的经验教训

  • 尽可能多地用 FFmpeg 脚本来完成预处理。
  • 保存好 FFmpeg 脚本,以备之后需要调整预处理时使用。
    • 即便你非常确信以后再也不需要预处理了,也要保存脚本,最好纳入版本控制。
  • 用极端数值测试 FFmpeg 脚本,确认它确实按你预期工作。
    • 我尝试将音频平移200毫秒来修正音画偏移,但依然不同步。接着试了300毫秒,还是不同步。然后是400毫秒。最后我直接跳到2000毫秒,才意识到脚本一定有 bug,因为200毫秒和2000毫秒的修正听起来完全一样。
  • 当不确定该用哪个参数时,用 FFmpeg 脚本生成多个不同版本,以便对比选择。
    • 为了测试消除我这端背景噪音的不同方案,我就是这么做的。
  • 44.1 kHz 的音频采样率在剪辑时似乎会引发问题。
    • 在预处理阶段将采样率转换为 48 kHz 后,爆音问题就解决了。
    • 我完全不知道为什么。
  • 尽早在剪辑过程中尝试导出视频,以检查最终效果。
  • 在轨道层面而非片段层面应用视频/音频滤镜。
    • 即便一开始只有一个大片段,一旦开始剪辑,就会产生数十个拥有独立滤镜设置的片段。
    • 如果你发现某个滤镜设错了,就得在每个片段上重做一遍。

沉迷于访谈文字稿的意外乐趣

和 Adam Gordon Bell 的视频剪完后,本该就此结束了吧?我在剪辑上花了那么多时间,按理说肯定急着发布然后收工。

错了!

视频剪完后,我又开始纠结起文字稿来。不过,这部分我倒是做得很开心。

我感觉网上看到的每一份访谈文字稿,设计师的思路都是:“我们就把60年前打字机打出来的法庭笔录,原封不动地搬到网上,趣味性和互动性就保持那个水平吧。”

如果我们能利用浏览器,让对话文字稿变得更有趣一点,会怎么样?

拜托!我们完全可以用网页实现一些打字机做不到的事情。

于是,我先用whisper-ctranslate2生成了初稿文字稿,然后花了大量时间让它更准确、更具互动性、也更好读:

我为访谈文字稿添加了一些小功能,让阅读更有趣。

  • 对话双方分别用不同颜色的气泡显示,一眼就能看出是谁在说话。
  • 每个气泡里都有一个小播放按钮,点击后页面会自动滚动到视频对应位置并开始播放。
  • 我把最喜欢的几句话摘出来,做成了重点引用。
  • 我添加了标题,帮助梳理讨论的结构。
  • 我校对了文本,修正了转录错误。

我还没有发布这个视频,因为周五刚给邮件列表的订阅者发送了新章节。它会在本周末前(2025-09-12前)发布在书的博客上。

帮助 Tyler Cipriani 登上 Hacker News 榜首

有时候,计划的进展甚至比我预想的还要顺利。

给真实的作者提供反馈有助于我写这本书,所以我一直在为其他独立开发者博主提供自由职业编辑服务。在介绍我编辑服务的页面上,我想放一个编辑案例,但又不想让付费客户把他们已经付费的作品拿给我当宣传材料。

所以,我的计划是找一个人,让我免费为他编辑文章,作为交换,我可以公开编辑笔记,而对方则署名我是编辑。

我的进阶目标是让这篇文章在我的潜在读者聚集的地方获得关注,比如 Hacker News、Lobsters 和 reddit。如果读者读到文章末尾看到“由Refactoring English编辑”,就会想:“咦,这是什么?”

对潜在客户来说,如果我能指着一位过往客户说:“看,这个人聘请了我,他的文章就在你想获得成功的地方成功了”,也会很有说服力。

几个月前,Tyler Cipriani 聘请我对他的博客做了一次整体点评。他似乎对结果很满意,于是我向他提出了免费编辑的想法,他也同意了。

我与 Tyler 就他的文章《The future of large files in Git is Git》进行了几轮反馈。我们合作得很愉快,也给我带来了写书的好灵感。

我原本的额外目标只是让文章登上 Hacker News 首页,结果它远超预期,一举登上了Hacker News 榜首Lobstersreddit的榜首。

对我们俩来说,最大的收获之一就是要根据目标读者来调整写作。Tyler 文章的早期草稿假定读者已经熟悉Git LFS——一个用于管理大文件的 Git 扩展。

我指出,普通的 Git 用户未必对 Git LFS 足够了解,未必能看懂文章里的所有内容。Tyler 起初不太认同,他觉得但凡处理过大文件的 Git 用户肯定都了解 Git LFS。

为了让 Tyler 相信读者对 Git LFS 的了解远不如文章所假设的那么多,我列出了自己对 Git LFS 的认知和经验:

  • 如果我的 git 仓库里有大文件,或者需要频繁更新二进制文件,就应该使用 Git LFS
  • 我从未使用过 Git LFS
    • 我可能参与过一两个使用 Git LFS 的开源项目,但从未接触过 LFS 相关的部分。
  • 我不知道各个代码托管平台的大小限制是多少,但我想如果触及限制,平台会提示我,到时再处理
    • 如果我想在 git 仓库里存一个大于5 MB 的文件,我会先想办法避免这么做
  • 我以为 Git LFS 是一个与平台无关、到处都能用的功能。
  • 我以为 Git LFS 是一项已有10多年历史、成熟稳定的技术
  • 我不知道一旦开始使用 Git LFS,仓库就再也摆脱不了它了
  • 我会对在 git 中存储大文件的方法感兴趣,也会在 HN/Lobsters 上点开相关文章,但除非真的遇到需要在 git 仓库中存储大文件的情况,否则我不会主动去思考和解决这个问题。

Tyler 说,这份清单让他豁然开朗。之前他之所以抗拒这个反馈,是因为他确信读者会了解 Git LFS。看到我的清单后,他才意识到,即便读者表面上知道 Git LFS 是做什么的,也未必了解它的工作原理。

Tyler 这次领悟的有趣之处在于,这份清单其实他自己也能写出来。他对目标读者会知道什么有着同样的预判,只是需要再深入一层去思考,读者“知道”一项技术到底意味着什么。

Side projects

Hacker News Observer切换到时序数据库,速度提升500倍

过去几年,我常听人提起“时序数据库”,但一直不明白它到底是做什么的,和普通的关系型数据库有什么区别。去年我在一个 side project 里甚至用过 InfluxDB,因为需要兼容 Grafana。但我依然没搞懂它为什么叫“时序”数据库,为什么不能直接用 SQLite。

最近和另一位开发者聊天时,他提到用时序数据库来呈现不同粒度的数据视图,比如秒级和天级。他甚至没有多解释,我却突然灵光一现,心想:“哦!原来时序数据库就是干这个的!”

Hacker News Observer 是我的一个 side project,它每分钟查询一次 Hacker News,记录过去几周内每篇文章的点赞数、评论数和排名。我希望以后能深入挖掘,发现一些有趣的模式,但目前我只是在看一些高层级的汇总数据,比如首页文章的总点赞数和评论数:

切换到 DuckDB 后,这个页面的加载速度提升了500倍

我最初用的是 SQLite,上面的图表需要两分钟才能渲染出来。这也不难理解,因为每天有数千篇文章,每篇文章又有数千次快照,然后我还得在每次快照中找出前30名(首页),再按小时分桶汇总。SQLite 并没有专门用于将每分钟数据聚合成小时级视图的函数,所以需要执行大量开销很大的查询。

有了对时序数据库的理解后,我让 LLM 推荐一些类似 SQLite 的时序数据库选项,它推荐了 DuckDB。然后我就让 LLM 把我的数据库从 SQLite 迁移到了 DuckDB。光是这次迁移,就把图表的加载时间从两分钟降到了250毫秒,速度提升了约500倍。

所以,我想这就是时序数据库的用途吧。

Gleam Chat Log Parser一点点啃掉陈年日志

在我的聊天记录解析器项目上,我只取得了一点点进展。处理了包含离开消息的日志,以及带有Windows 风格换行符(\r\n)的日志。奇怪的是,在 Erlang(以及因此在 Gleam)中,\r\n 竟然算作一个字符,这让我困惑了好一阵。

收尾

完成了什么?

经验教训

  • 在剪辑视频时要更加严谨
  • 剪辑访谈视频很枯燥,但编辑和美化文字稿却很有趣。
  • 如果只需要几分钟而不是30分钟,愿意提供反馈的用户会多一个数量级。

下月目标

  • 发布能为《Refactoring English》官网吸引新读者的内容。
  • 发布《Refactoring English》的新章节。
  • 给20位从未交流过的读者发送个性化邮件。

本文章由 muse-spark-1.2-contributor 进行翻译

评论