用 Docker 自建书库,Legado 直接下载 ,Legado 支持的格式很多,EPUB、TXT、PDF、UMD、MOBI、AZW3等;系统 TTS 本地朗读——全程离线、零流量、极致省电。


一、整体架构

┌─────────────────┐     OPDS/下载      ┌──────────────┐     本地TTS      ┌──────┐
│  Calibre-Web    │ ◄──────────────► │   Legado     │ ◄─────────────► │ 手机  │
│  (Docker 本地)   │    Basic Auth     │  (Android)   │   系统语音引擎    │ 扬声器 │
└─────────────────┘                   └──────────────┘                 └──────┘
  • Calibre-Web:管理电子书,提供 OPDS 接口
  • Legado:搜索、下载 EPUB、调用系统 TTS 朗读
  • 系统 TTS:本地合成语音,无需网络,零延迟

二、Calibre 桌面版(可选)

如果你需要格式转换(如 AZW3 → EPUB)、批量元数据编辑复杂排版调整等高级功能,可以在本机安装 Calibre 桌面应用。

Calibre-Web 可以直接读取 Calibre 桌面版的书库目录,两者共享同一套书籍文件:

# macOS
brew install --cask calibre

# 书库目录即 Calibre 默认路径
# Calibre-Web 的 /books 挂载到这个目录即可
┌──────────────┐     共享书库      ┌──────────────┐
│   Calibre    │ ◄─────────────► │ Calibre-Web  │
│  (桌面版)     │   ~/Calibre库/   │  (Docker)    │
│ 格式转换/编辑  │                 │ OPDS/Web阅读  │
└──────────────┘                 └──────────────┘

如果只是下载 EPUB 直接阅读,不需要安装桌面版。Calibre-Web 本身已足够。


三、Docker 部署 Calibre-Web

3.1 准备书籍目录

mkdir -p ~/calibre/{books,config}

把 EPUB 文件放入 ~/calibre/books

3.2 启动容器(如果有安装桌面版,可修改下面的config 路径,无缝对接)

docker run -d \
  --name calibre-web \
  --restart unless-stopped \
  -p 8083:8083 \
  -v ~/calibre/books:/books \
  -v ~/calibre/config:/config \
  lscr.io/linuxserver/calibre-web:latest

3.3 初始化

  1. 浏览器打开 http://<你的IP>:8083
  2. 默认账号 admin / 密码 admin123
  3. 设置书库路径为 /books
  4. 创建普通用户(如 u2),开启 允许下载 权限

四、Legado 书源配置

4.1 生成 Basic Auth 凭证

echo -n "u2:User.passwd" | base64
# 输出: dTIbase64MDI2

4.2 导入书源 JSON

在 Legado 中:我的 → 书源管理 → 右上角 ⋮ → 网络导入,粘贴以下 JSON:

[
  {
    "bookSourceComment": "Calibre-web OPDS书源",
    "bookSourceGroup": "Calibre-web",
    "bookSourceName": "我的书库",
    "bookSourceType": 3,
    "bookSourceUrl": "http://192.168.2.240:8083",
    "enabled": true,
    "enabledExplore": true,
    "exploreUrl": "新书::http://192.168.2.240:8083/opds/new",
    "header": "{\"Authorization\": \"Basic dTxxxbase64xxI2\"}",
    "respondTime": 180000,
    "ruleBookInfo": {
      "author": "@js:var m = result.match(/<author>[\\s\\S]*?<name>([^<]+)<\\/name>/); m ? m[1] : ''",
      "coverUrl": "@js:var m = result.match(/href=\"([^\"]+)\"[^>]*rel=\"http:\\/\\/opds-spec.org\\/image\"/); m ? m[1] : ''",
      "downloadUrls": "@js:baseUrl",
      "intro": "@js:var m = result.match(/<content[^>]*>[\\s\\S]*?<p>([^<]+)<\\/p>/); m ? m[1] : ''",
      "name": "@js:var m = result.match(/<title>([^<]+)<\\/title>/); m ? m[1] : ''"
    },
    "ruleContent": { "content": "" },
    "ruleExplore": {
      "author": "@js:var m = result.match(/<author>[\\s\\S]*?<name>([^<]+)<\\/name>/); m ? m[1] : ''",
      "bookList": "@js:var entries = [];\nvar regex = /<entry>[\\s\\S]*?<\\/entry>/g;\nvar match;\nwhile ((match = regex.exec(result)) !== null) {\n  entries.push(match[0]);\n}\nentries",
      "bookUrl": "@js:var m = result.match(/rel=\"http:\\/\\/opds-spec.org\\/acquisition\"[^>]*href=\"([^\"]+)\"/); m ? m[1] : ''",
      "coverUrl": "@js:var m = result.match(/href=\"([^\"]+)\"[^>]*rel=\"http:\\/\\/opds-spec.org\\/image\"/); m ? m[1] : ''",
      "intro": "@js:var m = result.match(/<content[^>]*>[\\s\\S]*?<p>([^<]+)<\\/p>/); m ? m[1] : ''",
      "name": "@js:var m = result.match(/<title>([^<]+)<\\/title>/); m ? m[1] : ''
    },
    "ruleSearch": {
      "author": "@js:var m = result.match(/<author>[\\s\\S]*?<name>([^<]+)<\\/name>/); m ? m[1] : ''",
      "bookList": "@js:var entries = [];\nvar regex = /<entry>[\\s\\S]*?<\\/entry>/g;\nvar match;\nwhile ((match = regex.exec(result)) !== null) {\n  entries.push(match[0]);\n}\nentries",
      "bookUrl": "@js:var m = result.match(/rel=\"http:\\/\\/opds-spec.org\\/acquisition\"[^>]*href=\"([^\"]+)\"/); m ? m[1] : ''",
      "coverUrl": "@js:var m = result.match(/href=\"([^\"]+)\"[^>]*rel=\"http:\\/\\/opds-spec.org\\/image\"/); m ? m[1] : ''",
      "intro": "@js:var m = result.match(/<content[^>]*>[\\s\\S]*?<p>([^<]+)<\\/p>/); m ? m[1] : ''",
      "name": "@js:var m = result.match(/<title>([^<]+)<\\/title>/); m ? m[1] : ''
    },
    "ruleToc": {
      "chapterList": "",
      "chapterName": "",
      "chapterUrl": ""
    },
    "searchUrl": "http://192.168.2.240:8083/opds/search?query={{key}}",
    "weight": 0
  }
]

注意:把 192.168.2.240 换成你的 Calibre-Web 实际 IP。bookSourceType: 3 表示这是一个下载型书源(非在线章节)。

4.3 使用流程

  1. 搜索:在 Legado 搜索书名
  2. 点击书籍:进入详情页,出现下载按钮
  3. 下载:EPUB 自动下载并导入为本地书籍
  4. 朗读:打开书籍 → 菜单 → 朗读 → 选择系统 TTS

五、Legado 源码修改(关键修复)

Legado 原版对 bookSourceType: 3(下载型书源)有一个设计缺陷:获取书籍信息时会先下载整个 EPUB 文件(可能几十 MB),导致超时或 OOM,下载按钮无法显示。

修改文件:app/src/main/java/io/legado/app/model/webBook/WebBook.kt

修改 1:getBookInfoAwait — 跳过文件下载

suspend fun getBookInfoAwait(
    bookSource: BookSource,
    book: Book,
    canReName: Boolean = true,
): Book {
    book.removeAllBookType()
    book.addType(bookSource.getBookType())
    // 新增:file 类型直接返回,不下载文件
    if (bookSource.bookSourceType == BookSourceType.file) {
        if (book.downloadUrls.isNullOrEmpty()) {
            book.downloadUrls = listOf(book.bookUrl)
        }
        return book
    }
    // ... 原有逻辑
}

修改 2:getChapterListAwait — 返回空章节列表

suspend fun getChapterListAwait(
    bookSource: BookSource,
    book: Book,
    runPerJs: Boolean = false
): Result<List<BookChapter>> {
    book.removeAllBookType()
    book.addType(bookSource.getBookType())
    // 新增:file 类型返回空列表
    if (bookSource.bookSourceType == BookSourceType.file) {
        return Result.success(emptyList())
    }
    // ... 原有逻辑
}

GitHub Actions 自动构建

# .github/workflows/debug_build.yml
name: Debug Build
on:
  workflow_dispatch:
  push:
    branches: [calibre-web]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: 17
      - uses: gradle/actions/setup-gradle@v4
      - run: ./gradlew assembleDebug
      - uses: actions/upload-artifact@v4
        with:
          name: legado_debug
          path: app/build/outputs/apk/app/debug/*.apk

六、TTS 配置

6.1 系统 TTS(推荐)

Legado 直接调用 Android 系统 TTS 引擎,本地合成、零延迟、零流量、最省电

手机上安装一个高质量 TTS 引擎即可:

  • Google 语音引擎(Play Store 下载)
  • 讯飞语记(需在系统设置中启用)
  • 小米 TTS(MIUI 自带)

6.2 自定义 HTTP TTS(备选)

如果需要特定音色,可以自建 TTS 服务。Legado 支持自定义 HTTP TTS:

{
  "name": "私有Edge",
  "url": "https://your-server/api/tts?text={{speakText}}&key=your_secret",
  "contentType": "audio/mpeg"
}

可用变量:

变量 说明
{{speakText}} 朗读文本
{{speakSpeed}} 朗读速度 (5-50)

注意:HTTP TTS 每句话都要网络请求,朗读一本书可能产生几千次请求,耗电和延迟都远高于系统 TTS。建议仅作为音色备选。


七、性能对比

方案 延迟 耗电 流量 隐私
系统 TTS 毫秒级 0 文本不出设备
HTTP TTS 数百毫秒 高(网络持续唤醒) 每句 ~10KB 文本发到服务器
在线听书 APP 秒级 每章 ~5MB 完全上传

八、常见问题

Q: 搜索到书但下载按钮不显示?

A: 确认 bookSourceType3,且使用了修改后的 Legado 版本。原版会因为 EPUB 文件太大导致超时。

Q: 下载后打开是空白?

A: 从书架直接打开下载型书源会没有章节。正确流程:搜索 → 点击书 → 详情页点下载 → 自动打开

Q: TTS 朗读中断?

A: 检查系统 TTS 引擎是否被电池优化杀死。在系统设置中关闭对 TTS 引擎的电池优化。

Q: 想在外网访问书库?

A: 用 Tailscale/ZeroTier 组网,或 Nginx 反代 + Basic Auth,不建议直接暴露端口。


九、总结

Docker 部署 Calibre-Web(10 分钟)
    +
Legado 导入 OPDS 书源(1 分钟)
    +
系统 TTS 朗读(0 配置)
    =
手机听书自由 🎧

全程本地运行,不依赖任何云服务,书籍和语音数据都不出局域网。享受纯粹的阅读和听书体验。

29eb18468430b12df6643993cd814a87