自动化沉浸式阅读
摘要
本文解释了Storyteller的强制对齐算法如何同步电子书和有声书以实现沉浸式阅读,详细介绍了其从简单脚本到完整生态系统的演变过程以及所面临的挑战。
<p><a href="https://lobste.rs/s/n1h6mh/automating_immersive_reading">评论</a></p>
查看缓存全文
缓存时间: 2026/08/30 12:00
# 自动化沉浸式阅读
来源:https://smoores.dev/post/automating_immersive_reading/
Storyteller 强制对齐算法的工作原理。
除了一个,所有孩子都长大了。他们很快知道自己终将长大,温迪知晓此事的方式便是如此。在她两岁的一天,她在花园里玩耍,摘下另一朵花,捧着它跑向母亲。我想她当时的模样定然十分可爱,因为达林太太将手按在心口,喊道:“哦,为什么你不能永远这样呢!”关于这个话题,她们母女之间只进行了这番交谈,但从此温迪便知道自己必须长大。两岁之后,你总是会知道。两岁,是终结的开始。
《彼得·潘》J.M. 巴里
Storyteller (https://storyteller-platform.dev/) 如今已发展成一个庞大的软件生态系统,拥有全栈Web应用程序、Android和iOS原生应用、KOReader插件,以及即将推出的macOS、watchOS和tvOS应用。我们正在对上述平台的v3版本进行内测和公测,新版本将带来更新的用户界面、*大量*全新的库管理功能等。我们非常兴奋能向大家展示我们的工作成果!
但Storyteller的核心在于其对齐算法。它可以接收你提供的电子书和有声读物,并将它们*对齐*,找出有声读物中朗读电子书每个单词的所在位置。这个过程是自动完成的,无需你的任何输入。然后,它利用EPUB规范内置的音频同步系统(称为媒体叠加)将音频和同步信息嵌入EPUB。这使你能够*沉浸式*地阅读书籍,你的阅读器应用会在叙述者朗读时高亮显示每个句子,就像在上面的演示中一样(该演示使用了真实的Storyteller对齐数据!)。
最初,Storyteller就只有这样:一个Python脚本。它接受一个有声读物文件和一个电子书文件,然后输出一个带有音频同步元数据的新的电子书文件。当时,只有极少数的电子阅读器应用(以及零台电子阅读器设备)能够处理这些使用EPUB媒体叠加规范实现其功能的文件。我会在自己的电脑上运行这个脚本,将生成的EPUB复制到手机上,然后利用BookFusion早期的媒体叠加支持来阅读和聆听我的书籍。那时,我对这个自己无意中一头扎进的强制对齐领域完全陌生。我的第一个对齐算法是一个由笨拙、嵌套的while循环构成的摇摇欲坠的构造。我感觉自己仿佛在黑暗中蹒跚前行,知道尽头或许有光,却无法看见。
### 挑战
尽管如此,我在探索中还是发现了一些见解。现有的强制对齐工具,即使是为对齐电子书和有声读物这一特定任务设计的,也难以应对书籍中常见的一些挑战,而我最初的初步尝试(在不同程度上)也处理了这些问题:
#### 章节顺序
电子书和有声读物可能(且经常)有不同的*章节顺序*。例如,在电子书中被视为前言部分的内容(如献词),在有声读物中可能会被安排在结尾朗读,因为有声读物通常试图直接从正文开始。
《翡翠海浪》布兰登·桑德森
##### 致谢
多么奇妙的旅程!当我一时兴起坐下来写这本书时,我完全不知道整个项目最终会走向何方……
致谢。
多么奇妙的旅程。
在《翡翠海浪》中,致谢部分在电子书的开头,但在有声读物的结尾。
#### 章节存在性
每种格式几乎肯定会有一些章节是另一种格式完全缺失的。附录、前言、目录——这些在有声读物中几乎总是被省略。而且,有声读物也经常包含电子书中不存在的小章节。
《你只需要减重》奥布里·戈登
##### 致谢
本书的出版得益于众多人士辛勤的工作和非凡的慷慨。
在《你只需要减重》中,电子书末尾有一个致谢章节,但在有声读物中完全不存在。
#### 被跳过的内容段
有时,较小的内容段会在有声读物朗读中被跳过,或者有声读物会包含电子书中没有的内容,例如对图片或图表的描述。
《悉达多》赫尔曼·黑塞 译者:希尔达·罗斯纳
##### 电子书
这些是悉达多的想法;这是他的渴望,他的悲伤。他常常对自己重复《歌者奥义书》中的一句话:“诚然,梵天之名即为真理。诚然,知晓此名者每日皆入天界。”天界似乎常在咫尺——天界——但他从未真正抵达过,从未止息最后的渴望。
##### 有声读物
这些是悉达多的想法;这是他的渴望,他的悲伤。天界似乎常在咫尺——天界——但他从未真正抵达过,从未止息最后的渴望。
在罗斯纳翻译的《悉达多》有声读物中,朗读者跳过了电子书中的几个句子,但其他部分与文本一致。
#### 替代的用词选择
有时,有声读物导演或叙述者会故意选择不同的词或短语,因为原文难以流畅地发音或朗读出来很别扭。有时,他们也会犯错!
《你没听我说过这个吧》凯尔西·麦金尼
##### 电子书
例如,阅读这本书并不能养活你的家人或保护你的身体。
##### 有声读物
例如,聆听这本书并不能养活你的家人或保护你的身体。
非虚构类书籍,如《你没听我说过这个吧》,通常需要将“阅读”或“读”替换为“聆听”或“听”。
在这些问题中,即使是相当基础的强制对齐系统通常也能毫无问题地处理替代的用词选择。而被跳过的内容段可能具有挑战性,尤其是当音频跳过了文本中的部分时,但结果通常不会是灾难性的,只是不完美。但缺失和重新排序的章节对许多强制对齐工具来说可能是致命伤。在Storyteller之前存在的工具,如非常酷的 syncabook (https://github.com/r4victor/syncabook),要求用户预先识别哪个电子书章节对应哪个有声读物章节。这既非常手动*又*颇具挑战性,因为许多有声读物甚至没有章节元数据或合适的按章节分割的文件。我想要做得更好,这意味着要解决这个问题。我需要一个搜索算法。
### 前置条件:边界搜索
在我们甚至可以研究实际的强制对齐问题之前,我们需要大致找到(如果可能的话)给定文本章节在音频中的位置。作为一个前置问题,这个有点……呃……相当棘手。我们还没有进行任何对齐,所以我们对音频的口头内容一无所知。即使我们有一份完美的转录(而我们没有任何方法获得它¹ (https://smoores.dev/post/automating_immersive_reading/#fn1)——做这件事*本身就是*我们稍后需要解决的强制对齐问题),我们也不能直接在转录文本中扫描章节内容,因为即使是完美的转录也会偏离基线电子书文本。所以我们不能做简单的事情。
虽然我们无法获得音频的完整、准确的转录,但我们可以获得其*某种*文本表示。我们可以使用大规模多语种语音模型² (https://smoores.dev/post/automating_immersive_reading/#fn2) 来生成CTC发射值,然后贪婪地解码这些发射值以产生文本。
…我现在将解释前面的术语。我们会深入探讨。会有图表。
#### CTC、Wav2Vec 2.0 和 MMS
连接主义时间分类(CTC,是的,它听起来像《沙丘》里的东西)十多年来一直是自动语音识别和强制对齐的支柱。它本质上是一个损失函数:机器学习模型用来评估其输出并训练自身的函数。为了使用这个损失函数,模型必须包含一个“CTC头部”,一个输出“CTC发射值”的层。发射值是CTC使用的一种中间表示——稍后会深入讨论。因为任何使用CTC头部的模型都会产生相同形状的输出(即上述的CTC发射值),所以也有标准算法可以进一步将发射值解码为文本。对于我们用例,我们关心的是“无约束贪婪解码”和“Viterbi强制对齐”。我们会在涉及时详细解释这些。
因此,我们有办法通过CTC解码器将模型的内部表示转换为发射值。Wav2Vec 2.0 的方向相反——它是一个预训练的*编码器*,负责将音频数据转换为机器学习模型可以操作的内部表示。模型本身(整合了Wav2Vec 2.0编码器、CTC解码器,然后在某个数据语料库上进行微调,以“学习”最小化CTC损失函数的权重)就是大规模多语种语音(Massively Multilingual Speech, MMS)。我们可以将音频分块输入MMS,获得一些CTC发射值。发射值本身是一个二维矩阵:每个音频帧(一帧为20毫秒)对应一个字符概率向量³ (https://smoores.dev/post/automating_immersive_reading/#fn3)。
```
《彼得·潘》 J.M. 巴里
a i o e a l i u l i u a l u i g l a i h l a i h l w k i
```
这是J.M. 巴里《彼得·潘》第一句第一个单词的实际发射数据。Wav2Vec编码器以20毫秒的帧处理音频,CTC头部为每一帧输出发射值。这些是每帧最可能的前5个字符,由MMS产生。背景颜色饱和度表示该标记在给定帧中被朗读的概率。
#### 无标签解码
现在我们有了发射值,需要解决我们的前置问题:找到每个章节在音频中的起始和结束位置。发射值的一个特点是它们具有规律性——因为每个发射向量代表一帧20毫秒的音频,如果我们能找到章节开始的*帧*,也就知道了它开始的*毫秒数*。
为了搜索文本,我们需要一个可以与文本进行比较的东西。目前,我们的发射值并不太符合这个要求。但我们可以从发射值中提取文本,不是吗?如果我们遍历发射向量,并对每个向量选取概率最高的字符,会怎样?我们不会得到任何意义上“好”的转录,但我们会得到一些文本,而且其中大部分可能是正确的。
```
⋮ a i e ⋮ o u ⋮ ⋮ a i ⋮ u ⋮ r ⋮ l ⋮ ⋮ a i ⋮ u ⋮ l ⋮ w ⋮ ⋮ i ⋮ u ⋮ l ⋮ g ⋮ w ⋮ ⋮ a i ⋮ u ⋮ l ⋮ h ⋮ ⋮ a i ⋮ o ⋮ l ⋮ h ⋮ ⋮ i ⋮ k l ⋮ y ⋮ w ⋮
```
贪婪解码算法相当简单。首先,我们遍历每一帧,检索概率最高的标记。然后,我们将所有相邻的相同标记合并。接着,我们丢弃所有空白符。我们得到了一个近似的朗读内容,没有大写、标点或空格。我称这个算法为“无约束贪婪解码”。“无约束”是因为我们没有提供基线文本来尝试解码,“贪婪”是因为在每一步,我们都选择最佳概率,从不重新考虑之前的步骤。
当我们在本帖开头演示的整个部分上运行它时,看起来是这样的:
```
allchildrenexceptonegrowuptheysoonknowthattheywillgrowupandthewaywendyknewwasthisonedaywhenshewastwoyearsoldshewasplayinginagardenandshepluckedanotherflowerandranwithittohermotherisupposeshemusthavelookedratherdelightfulformisisdarlingputherhandtoherheartandcriedowhycan'tyouremainlikethisforeverthiswasallthatpassedbetweenthemonthesubjectbuthenceforthwendyknewthatshemustgrowupyoualwaysknowafteryouartootooisthebeginningoftheend
```
结果文本看起来和我们的电子书文本相当相似!我们可以通过调整电子书文本来让它们看起来更相似:移除标点、合并空格并将每个字符小写。我们甚至可以将数字转换为其拼写形式,例如“2,000”转换为“two thousand”。我之前的一篇文章⁴ (https://smoores.dev/post/unreasonable_effectiveness_of_prosemirror/) 中更详细地讨论了如何在不丢失文本在原始XHTML中位置信息的情况下进行这种调整。
#### 经过RANSAC处理的n-grams
你可能注意到我们的贪婪解码并没有完美匹配我们的查询文本。而这只是一个非常小的样本——如前所述,大多数有声读物与电子书文本会有多个偏差,而且大多数贪婪解码会有大量的转录错误。所以我们不能简单地扫描文档直到找到完全匹配的查询文本。相反,我们需要将文档和查询文本分解成足够小的片段,使得其中许多片段很可能在两者之间匹配。这些被称为“n-grams”。对我们来说,一个“gram”将等同于一个字符,我们的“n”将是10。在我们的文档和查询中,我们将记录*每一个*10字母长度的片段及其起始位置。其中许多片段会同时存在于两个文本中——我们可以利用这些匹配来定位查询在文档中的位置!
##### 电子书
除了一个,所有孩子都长大了。他们很快知道自己终将长大,温迪知晓此事的方式便是如此。在她两岁的一天,她在花园里玩耍,摘下另一朵花,捧着它跑向母亲。我想她当时的模样定然十分可爱,因为达林太太将手按在心口,喊道:“哦,为什么你不能永远这样呢!”关于这个话题,她们母女之间只进行了这番交谈,但从此温迪便知道自己必须长大。两岁之后,你总是会知道。两岁,是终结的开始。
##### 有声读物
allchildrenexceptonegrowuptheysoonknowthattheywillgrowupandthewaywendyknewwasthisonedaywhenshewastwoyearsoldshewasplayinginagardenandshepluckedanotherflowerandranwithittohermotherisupposeshemusthavelookedratherdelightfulformisisdarlingputherhandtoherheartandcriedowhycan'tyouremainlikethisforeverthiswasallthatpassedbetweenthemonthesubjectbuthenceforthwendyknewthatshemustgrowupyoualwaysknowafteryouartootooisthebeginningoftheend
首先,我们通过将所有大写字母小写、移除标点和移除空格来调整我们的电子书文本。这个算法有几个非常好的特点:
1. 它对噪声解码极其稳健。无论有声读物朗读与电子书文本有多少偏差,或者贪婪解码在估算口语内容方面做得特别糟糕,即使只有10%的n-grams匹配,那仍然是数千个我们可以用来定位文本行的点。
2. 它为我们提供了大量额外信息。我们稍后会深入探讨,但我们可以利用该算法的信息,如局部语速和已知内点的位置,来实现我们实际的强制对齐过程。
3. 它真的非常高效!
### 强制对齐
现在我们知道章节在有声读物发射值中的起始和结束位置,这要归功于我们“经过RANSAC处理的n-grams”边界搜索。这让我们可以转向一个更直接的强制对齐算法:CTC Viterbi算法。
强制对齐通常被描述为一个全局优化问题⁴ (https://smoores.dev/post/automating_immersive_reading/#fn4)。我们有一些损失函数,比如“所有被选字符概率的总和”,我们希望在整个章节中最大化该函数的输出。一般来说,像这样的全局优化问题难以高效计算。考虑所有可能的结果——为了比较它们并找出得分最高的——往往代价高昂。但有两个技巧在这里会极大地帮助我们:
#### 匹配锚点
当我们之前计算匹配时,我们发现了一些n-grams同时存在于章节和音频中。当我们使用RANSAC找到内点时,我们剩下了一些匹配点。
相似文章
Reader Alive
Reader Alive 是一款工具,可让您翻译、收听和提问关于您的电子书。
ProseMirror 模型在富文本转换中的惊人有效性
这篇博文讨论了使用 ProseMirror 的数据模型来转换和对齐电子书和有声读物中的富文本,解决了在句子或单词级别引用文本跨度以实现同步的问题。
Meta正在测试一款AI睡前故事应用,专为缺乏想象力的人设计
Meta正在测试StoryKit,这是一款AI驱动的应用,能通过自定义角色、场景和道理,生成个性化的儿童睡前故事,旨在简化家长的讲故事过程。
@tom_doerr: Alexandria 使用 AI 从书籍生成有声书 https://github.com/Finrandojin/alexandria-audiobook…
Alexandria 是一个开源工具,利用 AI 驱动的脚本注释和文本转语音技术将书籍转化为全语音有声书,支持本地/云端大语言模型、语音克隆以及内置的 Qwen3-TTS 引擎。
@wsl8297: 想把电子书或文档做成有声书?很多工具不是声音太“机器人”,就是不支持字幕同步,折腾一圈还是不满意。 我后来发现了开源项目 Abogen:支持 ePub、PDF、纯文本等,一键转高质量音频,还能自动生成同步字幕。 它底层用 Kokoro 语…
Abogen 是一个开源工具,可将 ePub、PDF 等文档一键转为高质量音频,并自动生成同步字幕,支持语音混合器和多种部署方式。