Skip to content

Repository files navigation

kindle-footnote · 把脚注直接印进正文

把 EPUB 里的弹窗脚注 / 跳转尾注,改写成紧跟正文的内嵌注释。 推送到 Kindle 之后,注释就印在引用它的那段正文下面 —— 不点、不跳、不弹窗

用原生 Kindle 阅读器就能看,不需要越狱,不需要装 KOReader,不改推送方式。

转换前                          转换后
─────────────────              ─────────────────
正文段落……后面跟着角标[1]。       正文段落……后面跟着角标[1]。
(点一下 → 弹窗挡住半屏)            [1] 注释正文注释正文……
(再点一下 → 跳到书末尾注)          正文继续往下走……

设备背景:在 Kindle Paperwhite 5 上开发并实测,书籍通过 Send to Kindle 推送 EPUB。 工具本身只是改 EPUB 文件,不依赖任何特定设备或越狱环境。


快速开始(Windows,3 步)

  1. 装好 Python 3(3.8 以上,装的时候勾 "Add Python to PATH")。
  2. 下载本仓库(右上角 Code → Download ZIP,或 git clone)。
  3. .epub 文件拖到 拖到这里转换(内嵌注).bat 上。

首次运行会自动建 .venv 并装 lxml,要联网,等几十秒。之后就是秒开。 输出文件在原书同一个文件夹,名字是 原文件名-tight.epub原书不会被改动

可以一次拖多本书上去。

其他系统 / 命令行用户

pip install lxml
python epub_footnote_inline.py 书.epub --style tight --class-fallback

也可以装成命令行工具,之后直接 kindle-footnote 调用:

pip install git+https://github.com/c30w/kindle-footnote.git
kindle-footnote 书.epub --style tight --class-fallback

参数说明见下面「方法 B(命令行)」。


这个工具适合谁

  • Send to Kindle 推 EPUB,受够了脚注弹窗挡住正文。
  • 书里注释多(学术书、译著、古籍),跳来跳去根本读不下去。
  • 想继续用原生 Kindle 阅读器,不想折腾 KOReader。

不适合:想要「严格意义上屏幕最底部、跟着翻页实时计算的页底注」的人 —— 那个只有 KOReader 的排版引擎做得到, 而它读不了推送的 KFX。详见下面第一节的原理说明。



一、先说结论:为什么越狱解决不了这件事

Kindle 原生阅读器没有"脚注常驻页底"这个开关。脚注是弹窗、还是跳转、还是普通文字, 完全由书本身的标记决定

<aside epub:type="footnote" id="fn1">…注释内容…</aside>

只要书里是这种写法,阅读器就一定会做成点击弹窗。这个行为写在 Amazon 的渲染引擎(KFX 阅读器)里, KUAL 只能在系统里启动别的程序,改不了原生阅读器的渲染逻辑

另外还有两点要清楚:

  1. Send to Kindle 推到 PW5 上的书,到手已经是 KFX 了(亚马逊服务器转的),不再是 EPUB。 所以"在设备上改 EPUB"这条路从一开始就不存在。
  2. KOReader 读不了 KFX(它只支持 EPUB / PDF / Mobi / FB2 等), 所以"装了 KOReader 也能读推送的书"是不成立的。

所以唯一可行的路就是:推送之前,在电脑上把书改好

能做到什么程度:重排电子书没有"固定页",页面是 Kindle 打开时按当前字号实时算出来的, 所以在电脑上改书做不到"严格意义上的屏幕最底部"。 本工具做到的是注释紧跟在引用它的那段正文下面——翻页时它就在你眼前,不用点、不用跳、不弹窗, 实际阅读体验几乎等同于页底注。 (真正的动态页底注只有 KOReader 的排版引擎做得到,但它读不了推送的 KFX,本套工具不涉及这条路。)


二、方案:推送之前先改书(保留 Send to Kindle 全流程)

原理:在电脑上把 <aside epub:type="footnote"> 里的注释文字搬到引用它的那段正文下面, 变成一段带小字号样式的普通 <div>,同时抹掉 epub:type 语义。 这样亚马逊转 KFX 时它就是普通段落,直接印在你读到的位置上 —— 不用点,不用跳,不会被弹窗挡住

怎么用

方法 A(推荐,零命令行):把 epub 文件拖到 bat 上

文件 效果
拖到这里转换(内嵌注).bat 注释紧跟引用它的段落,纯缩进 1.2em、无竖线、行距 1 倍(tight 档,最省地方);同时兼容 class="fnote" 等老式脚注
  • 可以一次拖多本书上去。
  • 输出文件在原书同一个文件夹,名字是 原文件名-tight.epub,原书不会被改。
  • 第一次运行会自动建一个本地 Python 环境并装 lxml(要联网,几十秒),之后就秒开了。

转换后长这样:

正文段落……后面跟着引用角标[1]。

  [1] 注释正文注释正文注释正文……
  • 角标 [1] 留在正文里,变成不可点的上标(点它不再弹窗)。
  • 注释块开头会自动补一个和角标一致的编号;如果注释正文自己已经带了同样的编号(比如「【3】…」),就不会重复加。

方法 B(命令行,可调参数)

.venv\Scripts\python.exe epub_footnote_inline.py 书.epub

:: 常用参数
--style tight|compact|loose   紧凑度三档(默认 compact;tight 最省地方)
-o 新文件名.epub              指定输出文件名
--suffix=-tight               输出原名 + 后缀(注意用等号,因为值以 - 开头)
--no-label                    注释块前面不加编号
--keep-endnotes               文末的注释区也保留(注释会出现两次)
--keep-marker-link            角标保留可点击(默认改成不可点的上标)
--class-fallback              兼容老式 class="footnote" / class="fnote" 脚注(拖 bat 时已自动开启)
--no-drop-empty-file          保留被搬空后的"注释"页(默认自动删掉)
--trust-links                 完全相信书里的链接(怀疑配错了才用,见下)

书里的链接是错的怎么办 —— 脚本会自动按编号纠正

很多转换出来的书(多看 / 微信读书导出、Calibre 转的)链接本身就是乱的,或者把脚注写成了老式 class="fnote" 段落,常见三种情况:

  1. 注释条目没写 id,回跳链接还是死的 href="#" —— 按链接找不到,注释就插不进去;现在会优先读取注释块内部回跳 <a> 的 id。
  2. 角标指到了别的注释上 —— 角标 [32]href 指向内容其实是 [20] 的那条。 照着链接走,就会把错误的注释插到正文里,比不处理还糟。
  3. 脚注定义和正文不在同一个 XHTML 文件 —— 现在支持按 partXXXX.html#id 的跨文件链接反查,不再把它误判成未匹配。

脚本的做法是编号优先、链接兜底:先按 id / 回跳链接找,找到之后还要比对「角标上的编号」 和「注释正文开头的编号」是否一致;一致才信,不一致就改按编号重新配。 只在同一个 xhtml 文件内配对,不跨文件猜。

转换时会打印它做了什么:

识别脚注 541 条 / 命中引用 541 条 / 内嵌插入 541 块 / 未匹配 0 条
链接错位、按编号改配:12 条
注释没写链接、按编号补配:96 条

(实测一本 541 条注释的书:全部命中,其中约 20% 的注释链接在书里本来就是错的。)

如果你发现某本书配错了(注释内容和角标号对不上),加 --trust-links 退回「只信链接」的旧行为。

跑完会打印一行统计,例如:

识别脚注 38 条 / 命中引用 36 条 / 内嵌插入 36 块 / 未匹配 2 条
已移除搬空的注释页:OEBPS/notes.xhtml
  • 未匹配 = 这条注释在正文里找不到引用它的角标,脚本就保持原样没动它。
  • 如果"内嵌插入 0 块",命令行再加 --class-fallback 试一次;拖 bat 本身已经自动开启这个兼容模式。

三档紧凑度怎么选

预设 字号 行距 上下留白 区分方式 适合
tight 0.78em 1 倍 0.1em / 0.22em 纯缩进 1.2em,无竖线 注释多、想尽量不打断正文
compact 0.82em 1 倍 0.2em / 0.35em 左侧 2px 细竖线 默认,注释和正文分得清
loose 0.85em 1.35 倍 0.6em / 0.9em 左侧 2px 细竖线 注释长、需要看清换行

拖 bat 用的是 tight。想换档就用命令行 --style compact / --style loose

同一段正文连着引了多条注释时,第二条及以后会自动加一个 kfx-note-adjacent 类, 把和上一条之间的空隙压到最小(tight 档是 0,compact 档是 0.05em),看起来是贴在一起的。

脚本会顺手清掉的东西

注释被搬进正文之前,脚本还会做两件清理,都是为了让注释块别被撑大:

  1. 拆掉无语义的包装标签。多看 / 微信读书导出的书长这样:

    <aside epub:type="footnote">
      <ol class="duokan-footnote-content">
        <li class="duokan-footnote-item">[1] 注释正文……</li>
      </ol>
    </aside>

    ol / li 天生带缩进和项目符号,留着它每条注释前后都要多空一大截。 脚本会把它拍平成 <div class="kfx-inline-note">[1] 注释正文……</div>。 判断标准是「只有一个 li」或「类名里带 footnote / note / duokan 等字样」—— 作者真的在注释里列了三条的那种列表会原样保留。

  2. 清掉注释里的回跳链接。注释开头那个 [1] 通常是<a href="#noteref-xxx">, 点了会跳回正文。现在注释就在正文里,这个链接没用了,而且是死链(书本身链接错位时 还会跳错地方)。脚本按「站内跳转 + 链接文字只有一个编号」这个特征识别并删掉, 外链(http://)不动 —— 那种可能是注释里的引用出处。

样式不满意怎么改

打开 epub_footnote_inline.py,最上面有个 STYLE_PRESETS 字典,tight / compact / loose 三段的 CSS 都在里面,改完保存重新拖一次就行(用的是普通 CSS,单位用 em,会跟着你 Kindle 上的字号设置一起缩放):

.kfx-inline-note{font-size:0.78em;line-height:1;margin:0.1em 0 0.22em 0;padding:0 0 0 1.2em;text-align:justify;text-indent:0;}
.kfx-inline-note p,.kfx-inline-note div,.kfx-inline-note li{margin:0;line-height:1;text-indent:0;}
.kfx-inline-note ol,.kfx-inline-note ul{list-style:none;margin:0;padding:0;text-indent:0;}
.kfx-inline-note a{text-decoration:none;}
.kfx-note-marker{font-size:0.72em;vertical-align:super;line-height:1;}
.kfx-note-label{font-size:0.92em;margin-right:0.2em;}
.kfx-note-adjacent{margin-top:0;}

几个常见改法:

  • 想更紧凑:把 margin 里的两个数继续调小(前者是距上一段,后者是距下一段);0 0 0.05em 0 基本就是贴着了。
  • 想加竖线:在 .kfx-inline-note 里加 border-left:2px solid #9a9a9a;
  • 想换线色#9a9a9a 是灰,改 #000(黑)或 #c8c8c8(更浅)。
  • 行距还想再压line-height:1 已经是最小值了(= 单倍行距),再小字会叠在一起。
  • 注释里的链接有下划线:已经有 .kfx-inline-note a{text-decoration:none;} 去掉了。

三、另一个选择:不改书,只想别弹窗遮住正文

只去掉角标上的 epub:type="noteref",Kindle 就不会弹窗了,变成点击跳转到文末注释 (文末那条注释里通常有返回链接)。还是要点一次,但至少不会盖住正文。

用 Calibre 的「编辑书籍」→「搜索替换(正则)」批量做:

查找:\s*epub:type="noteref"
替换:(空)

对所有 xhtml 文件执行一遍,保存,再推送。


四、常见问题

Q:转换后 Kindle 上还是弹窗? A:说明还有残留的 epub:type="footnote"。大概率是脚本没认出来(看"未匹配"数字), 试着加 --class-fallback;或者书里有第二套注释(比如译注)用了别的 class, 那就把那个 class 名加进 CLASS_DEF_RE 的正则里。

Q:注释编号丢了 / 重复了? A:脚本会把正文角标的文字(如 [1])搬到注释块开头补上,所以编号不会丢。 如果注释正文自己开头就已经带了同样的编号(比如「【3】…」),脚本会跳过不补, 避免出现「[3]【3】…」这种重复。 判断只看注释正文开头那一个编号 —— 像「1848 年发生了很多事」这种以年份开头的不会被 误当成编号,「第一条:…」里的「一」也不会(它不是在最前面)。 万一还是重复,加 --no-label 全部不补,靠书里原有的编号。 如果重复出现在正文角标本身,而且原书结构是 <a><sup>(1)</sup></a>,旧版本可能会把外层和内层一起保留,显示成两个 (1);当前版本会自动把它展平成单层上标,不需要手工改 EPUB。

Q:注释内容跟角标号对不上? A:先别急,这种情况脚本大概率已经自动纠正了(见上面「编号优先」那节)。 如果确实还有对不上的,加 --trust-links 对比一次,看哪种更准。 两个都不对的话,说明这本书的注释编号本身就不连续,那就 --no-label 去掉自动编号, 只保留注释原文里自带的编号。

Q:注释块之间空得还是太大? A:按这个顺序排查:

  1. 确认用的是 --style tight(拖 bat 默认就是)——行距已经是 1 倍、上下只剩 0.1em / 0.22em。
  2. 书如果是多看 / 微信读书导出的,确认注释块里没有残留 ol / li —— 脚本会自动拆平, 但要是它没认出来(包装标签上既没编号类 class、又有多个 li),就把那个 class 名加进 LIST_WRAP_CLASS_RE 的正则里。
  3. 还嫌大就按上面「样式不满意怎么改」直接调 CSS 里的 margin —— 前者是距上一段,后者是距下一段。

Q:推送后提示格式不支持? A:先确认输出文件是 .epub 且能正常打开。Send to Kindle 单文件上限 200MB。 本工具不会加 DRM,也不会动封面、目录、字体。

Q:会损坏原书吗? A:不会。原文件只读,结果写到 原文件名-tight.epub

Q:还能调字体、字号、行距吗? A:能,而且注释会跟着一起变。脚本注入的 CSS 只用 em 作为单位(0.78em = 正文字号的 78%), 不写死 px,也不设 position / height,所以 Kindle 上怎么调版式都不会把注释撑坏。


五、文件说明

文件 用途
epub_footnote_inline.py 主程序:识别弹窗脚注,搬到引用它的段落后面
拖到这里转换(内嵌注).bat 一键转换(中文名),输出 原文件名-tight.epub
convert-drag-and-drop.bat 同上,英文文件名版,内容完全一致
requirements.txt 依赖清单,只有一个 lxml
_test/run_tests.py 一键回归测试:6 样本 × 3 档 + 结构校验 + 配对校验
_test/ 其余测试样本与校验脚本,删掉不影响使用
DESIGN.md 设计说明与踩坑记录,给想改代码的人
.venv/ 本地 Python 环境,首次运行自动生成,不上传

_test/ 里 6 个样本分别覆盖:标准 EPUB3 脚注、注释单独一个文件、不规范 HTML (未闭合 <br>、只有 class="footnote")、同段多引用 + 编号去重、链接错位的书驼峰类名的书class="noteContent",角标外面还套了一层 class="subScript")。 run_tests.py 一键跑完全流程;verify_outputs.py 校验成书结构(mimetype/zip/XML/OPF), check_pairing.py 校验「每个角标后面跟的注释编号是否对得上」。

已经实测支持哪些写法

书的写法 例子 是否需要 --class-fallback
标准 EPUB3 <aside epub:type="footnote" id="fn1"> 不需要
类名 footnote / fnote <p class="fnote"> 需要(拖 bat 已默认开启)
驼峰 / 连字符类名 <p class="noteContent">note-content 需要(拖 bat 已默认开启)
多看 / 微信读书 duokan-footnote-item 需要(拖 bat 已默认开启)
注释和正文分属不同文件 角标 href="part0057.html#ch53" 不需要,自动跨文件配对
id 写在注释内部的 <a> <p class="fnote"><a id="ch53"> 不需要,自动识别

角标类名里带 ref / marker / sub / sup 的(比如 noterefnoteRefsubScript) 会被当成正文角标而不是注释定义,不会误搬正文。


六、免责声明

本工具只做格式重排:把书籍中已有的注释挪个位置,不解密、不移除任何 DRM。 请仅对你合法拥有的电子书使用,并遵守当地法律与书籍的授权条款。

输出的是一本新的 EPUB,原文件只读、不会被修改。

七、许可证

MIT License —— 随便用、随便改,注明出处即可。

八、实测过的书

回归测试覆盖以下结构(_test/ 里有对应样本,可自行复现):

注释数 结果
标准 EPUB3 脚注 全量通过
老式 class="fnote"(分属不同 XHTML) 6492 识别 6492 / 命中 6479 / 未匹配 13
驼峰类名 class="noteContent" 2421 识别 2421 / 命中 2421 / 未匹配 0
真书(注释含无链接条目) 541 识别 541 / 命中 541 / 按编号补配 108

「未匹配」= 这条注释在正文里找不到引用它的角标,脚本保持原样不动,不会乱猜

跑回归测试(6 个样本 × 3 档,含结构校验和配对校验):

pip install lxml
python _test/run_tests.py

样本文件已随仓库提供,直接跑即可。想重新生成样本就 python _test/run_tests.py --regen

About

把 EPUB 的弹窗脚注改成正文内嵌注释,推送到 Kindle 后不用点、不弹窗 · Inline footnotes for Kindle

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages