MacBook中ibook读书笔记导出
Go to file
douboer 6a8e0dc5a8 Remove ignored files from repo 2025-09-08 10:01:04 +08:00
backup 'update' 2025-09-08 09:54:04 +08:00
data 'update' 2025-09-08 09:54:04 +08:00
examples Remove ignored files from repo 2025-09-08 10:01:04 +08:00
fonts/JetBrainsMono-1.0.3 Remove ignored files from repo 2025-09-08 10:01:04 +08:00
icons Remove ignored files from repo 2025-09-08 10:01:04 +08:00
ipad_app Remove ignored files from repo 2025-09-08 10:01:04 +08:00
notes Remove ignored files from repo 2025-09-08 10:01:04 +08:00
uml Remove ignored files from repo 2025-09-08 10:01:04 +08:00
.gitignore Remove ignored files from repo 2025-09-08 10:01:04 +08:00
TODO.md 'update' 2025-09-08 09:54:04 +08:00
UI_README.md 'update' 2025-09-06 14:20:18 +08:00
ai_interface.py 'update' 2025-09-07 01:19:00 +08:00
annotationdata.py 'update' 2025-09-06 16:43:13 +08:00
bookintro.json 'update' 2025-09-07 02:13:30 +08:00
booklist_parse.py 'update' 2025-09-07 01:19:00 +08:00
charts.py 'update' 2025-09-08 09:54:04 +08:00
config.py 'update' 2025-09-07 12:39:28 +08:00
cover_mixin.py 'update' 2025-09-07 01:19:00 +08:00
defaultcover.jpeg 'update' 2025-09-07 01:19:00 +08:00
detaildesign.md 'update' 2025-09-07 13:06:31 +08:00
exportbooknotes.py 'update' 2025-09-07 01:19:00 +08:00
finished_books_mixin.py 'update' 2025-09-07 01:19:00 +08:00
generate_ui.py 'update' 2025-09-06 14:20:18 +08:00
ibook_export_app.py 'update' 2025-09-08 09:54:04 +08:00
ibook_export_app.ui 'update' 2025-09-07 12:39:28 +08:00
ibook_export_app_matplot.py 'update' 2025-09-08 09:54:04 +08:00
ipad_bundle_export.py 'update' 2025-09-07 12:39:28 +08:00
kmanapp.qrc 'update' 2025-08-15 13:49:02 +08:00
log 'update' 2025-09-07 01:19:00 +08:00
mainwindow.ui 'update' 2025-08-15 13:49:02 +08:00
mreadme.md 'update' 2025-09-07 12:39:28 +08:00
opf_parse.py 'update' 2025-08-15 17:20:30 +08:00
push.sh 'update' 2025-08-15 13:49:02 +08:00
readme.md 'update' 2025-09-07 13:10:52 +08:00
requirements.txt 'update' 2025-09-08 09:54:04 +08:00
review_worker.py 'update' 2025-09-07 01:19:00 +08:00
run.sh 'update' 2025-09-08 09:54:04 +08:00
test.ui 'update' 2025-08-15 13:49:02 +08:00
toc_parse.py 'update' 2025-08-15 17:20:30 +08:00

readme.md

iBooks 笔记专家 详细设计文档

版本: 1.1 (2025-09 重构整理)
维护者: 项目开发组
说明: 本文档统一重新编排章节,增加架构与 UML 部分,便于后续扩展与维护。

版本 日期 说明
1.0 2025-08 初版文档
1.1 2025-09 重组目录新增模块拆分、UML、AI 简评与可视化章节整理

1. 概述

本工具用于从 macOS iBooksApple Books应用的数据文件中提取用户的书籍笔记并以 Markdown 格式导出。支持从 iBooks 的数据库和 plist 文件自动同步数据,支持交互式选择书籍导出,导出内容结构清晰,便于后续整理和阅读。 支持按最近打开时间排序书籍,菜单显示书名与时间戳,导出流程高效。


2. 功能

2.1 主要功能

  • 自动同步 iBooks 数据库和书籍信息文件到本地 ./data 目录。
  • 解析 iBooks 笔记数据库,构建结构化的 booksnote 数据。
  • 解析书籍元数据(如书名、路径等)。
  • 支持交互式模糊搜索选择要导出的书籍。
  • 按章节导出所选书籍的所有笔记,格式为 Markdown。
  • 书名中如含有“-xxxx”后缀仅保留“-”前的主书名。
  • 书籍选择菜单按最近打开时间last_open降序排序显示格式为“书名 [时间戳]”。

2.1 GUI

img img img

笔记导出markdown格式 img img


3. UML 图Mermaid

注:使用 Mermaid 语法,支持在支持渲染的 Markdown 查看。类之间仅展示主要依赖/调用,非完整字段集合。

3.1 类图(核心模块)

img

3.2 时序图:应用启动

img

3.3 时序图:选择书籍 + AI 简评

img

3.4 时序图:导出 Markdown

img

3.5 时序图:已读书籍网格刷新

img

3.6 时序图:点击已读封面跳转

img


4. 主要数据结构

4.1 booksnote

booksnote = {
  assetid: { label_path: { uuid: {
      'creationdate': '2023/7/12',
      'filepos': None,
      'idref': '008.xhtml',
      'note': None,
      'selectedtext': '這就是宣傳的恐怖之處'
      }}}
}
  • assetid:书籍唯一标识
  • label_path:章节名
  • uuid:笔记唯一标识
  • 其余字段为笔记内容及元数据

5. 主要流程

5.1 数据同步

  • 自动将 iBooks 的数据库和 plist 文件复制到本地 data/ 目录,便于后续处理。

5.2 构建 booksnote

  • 通过 get_annotations 解析 SQLite 笔记数据库,获取所有笔记。
  • 通过 parse_books_plist 解析书籍元数据,获取书名、路径等信息。
  • 遍历每本书的所有笔记结合OPF、NCX文件和HTML 文件,定位章节名。
  • 若无法通过目录文件定位章节,则尝试通过笔记选中文本在 HTML 文件中查找章节,否则标记为“未找到章节”。

5.3 交互式选择书籍

  • 读取 Books.plist 获取所有书籍元数据。
  • 读取 BKLibrary.sqlite获取每本书的最近打开时间last_open苹果时间戳基准2001-01-01
  • 生成书名列表(优先 displayname,其次 itemname,否则用 assetid),并去除“-xxxx”后缀。
  • 按 last_open 时间戳降序排列,菜单显示“书名 [时间戳]”,时间戳为 last_open 字段。
  • 使用 InquirerPy 提供模糊搜索交互界面,供用户选择要导出的书籍。

5.4 导出 Markdown

  • 仅导出用户选择的书籍。

  • Markdown 格式如下:

    # 笔记导出 2025-08-06 12:00
    ## 书名
    ### 章节名
    选中文本
    > 笔记内容
    
  • 每条笔记独立分行,章节分组。


6. 关键函数说明

6.1 build_booksnote

  • 输入:注释数据库路径、书籍 plist 路径
  • 输出:结构化的 booksnote 字典
  • 逻辑:遍历所有笔记,结合书籍元数据和目录信息,归类到章节下

6.2 export_booksnote_to_md

  • 输入booksnote、booksinfo、导出路径
  • 输出Markdown 字符串,并写入文件
  • 逻辑:遍历每本书、每个章节、每条笔记,按格式输出

7. 交互与用户体验

  • 通过命令行交互,用户可模糊搜索并选择要导出的书籍。
  • 若无可导出的笔记,程序自动退出并提示。
  • 导出后,显示导出文件路径和书名。

8. 代码片段示例

8.1 书名处理逻辑

name = info.get('displayname') or info.get('itemname') or assetid
# 如果书名中包含“-”,只取“-”前面的部分
if '-' in name: name = name.split('-', 1)[0].strip()

8.2 交互式选择与排序

from booklist_parse import get_books_last_open
last_open_times = get_books_last_open('data/BKLibrary.sqlite')
for assetid, info in booksinfo.items():
    ...
    ts = last_open_times.get(assetid, {}).get('last_open', 0)
    assetid2lastopen[assetid] = ts
sorted_assetids = sorted(assetid2name.keys(), key=lambda aid: assetid2lastopen[aid], reverse=True)
choices = [f"{assetid2name[aid]} [{assetid2lastopen[aid]}]" for aid in sorted_assetids]
answer = inquirer.fuzzy(
    message="请选择要导出的书名(支持模糊搜索):",
    choices=choices,
    multiselect=False,
    instruction="上下键选择,输入可模糊筛选,回车确定"
).execute()

9. 依赖说明

  • Python 3
  • 主要依赖库:InquirerPy, bs4, shutil, os, datetime, sqlite3
  • 需有 iBooks 数据库、plist 文件和 BKLibrary.sqlite 的本地访问权限

10. 目录结构

  • data/:存放同步下来的数据库和 plist 文件(含 AEAnnotation.sqlite、Books.plist、BKLibrary.sqlite 等)
  • notes/:导出的 Markdown 文件
  • examples/epub 示例文件夹

11. 主要代码文件说明(细化)

11.1 exportbooknotes.py

  • 采用 OOP 设计,核心类为 BookNotesExporter
    • build_booksnote(bookid=None):构建结构化笔记数据。
    • export_booksnote_to_md(booksnote, booksinfo, out_path=None):导出为 Markdown。
    • find_file_by_extget_toc_tree 等辅助方法。
  • 数据同步:自动复制 iBooks 数据库和元数据到本地。
  • 菜单交互:按最近打开时间戳排序,显示“书名 [时间戳]”,支持模糊搜索。
  • 只处理用户选中书籍的笔记,按章节分组导出 Markdown。
  • 依赖核心解析模块,负责主流程调度。

11.2 annotationdata.py

  • OOP 设计,核心类为 AnnotationManager
    • get_annotations(bookid=None):返回所有或指定 assetid 的笔记。
    • parse_location(location):静态方法,解析定位信息。
  • 解析 AEAnnotation.sqlite提取所有或指定 assetid 的笔记。
  • 支持苹果时间戳转换,结构化输出。

11.3 booklist_parse.py

  • OOP 设计,核心类为 BookListManager
    • get_books_info():获取书籍元数据。
    • get_books_last_open():获取每本书的最近打开时间。
  • 解析 Books.plist获取书籍元数据书名、作者、路径、时间等
  • 解析 BKLibrary.sqlite获取每本书的最近打开时间。

11.4 opf_parse.py

  • OOP 设计,核心类为 OPFParser
    • parse_opf(filepath):静态方法,返回 id->href 映射。
  • 解析 epub 的 OPF 文件获取章节与文件映射关系idref -> href

11.5 toc_parse.py

  • OOP 设计,核心类为 TOCParser
    • parse_navpoints(navpoints):递归解析 navPoint 节点。
    • find_label_path(node, ref, filepos, path):查找章节路径。
    • find_section_by_selectedtext(html_path, selectedtext):通过选中文本定位章节标题。
    • parse_html_title(html_path):解析 html 文件标题。
  • 解析 NCX 目录文件,递归构建章节树结构。

11.6 backup/booksnote.py

  • 历史/备份脚本,辅助数据迁移或格式转换。

12. 扩展与维护建议

  • 可扩展支持多本书批量导出
  • 可增加导出格式(如 HTML、PDF
  • 可优化章节定位算法,提升准确率
  • 可增加 GUI 交互界面

如需进一步细化某一部分设计,请告知!


13. GUI 架构与模块调用关系2025 拆分后更新)

13.1 模块职责概览

模块 主要类/函数 职责 关键依赖
ibook_export_app.py IBookExportApp GUI 入口,组合各 Mixin组织信号/槽,生命周期管理 CoverMixin, FinishedBooksMixin, BookReviewWorker, BookListManager, BookNotesExporter
cover_mixin.py CoverMixin 解析、查找、缩放显示封面 config (目录), Qt Widgets
finished_books_mixin.py FinishedBooksMixin 已读书籍网格数据装载与自适应布局、点击跳转 BookListManager.get_finished_books_this_year, CoverMixin.find_book_cover
review_worker.py BookReviewWorker 后台线程获取书籍 AI 简评并写入 bookintro.json ai_interface.DashScopeChatClient
booklist_parse.py BookListManager 书籍基础元数据、阅读统计、已读书籍列表 annotationdata.AnnotationManager, config
annotationdata.py AnnotationManager 解析笔记 SQLite返回结构化笔记 SQLite DB
exportbooknotes.py BookNotesExporter 构建/导出 Markdown 笔记 annotationdata, toc_parse, opf_parse
toc_parse.py TOCParser 解析 NCX/HTML 标题,定位章节路径 文件系统/HTML
opf_parse.py OPFParser 解析 OPF 获取 manifest 映射 OPF XML
charts.py 图表组件 周 / 月 / 年 / 气泡指标可视化 BookListManager 汇总数据

13.2 运行时对象关系(简化 UML 文本表示)

IBookExportApp
 ├── BookListManager (数据/统计)
 ├── BookNotesExporter (导出)
 ├── [Composition via Mixin] CoverMixin
 ├── [Composition via Mixin] FinishedBooksMixin
 ├── 0..n BookReviewWorker (异步 AI 简评线程池 _active_workers)
 └── charts.* Widgets (按需创建)

IBookExportApp 通过多继承获得封面与已读网格功能:

  1. 封面查找 -> CoverMixin.find_book_cover
  2. 网格构建 -> FinishedBooksMixin._populate_finished_books_grid
  3. AI 简评 -> BookReviewWorker 发起,完成后回调 _on_review_finished

13.3 启动序列Startup Sequence

  1. 用户运行 ibook_export_app.py → 创建 QApplication
  2. IBookExportApp.__init__
  • 加载 .ui
  • sync_source_files(config)(复制原始库到 data/
  • 构建 BookListManager → 加载 plist / 统计数据
  • 构建书籍列表(按 last_open 排序)填充 QListWidget
  • 初始化封面标签 & _load_initial()(前三本封面 + 首本 AI 简评启动)
  • _populate_finished_books_grid()(已读网格初填)
  • 安装事件过滤器(视口 Resize + Tab 切换策略 C + A
  • 安排延迟 _relayout_finished_grid() 确保初次布局正确
  1. 主窗口显示;用户可交互。

13.4 书籍切换流程Selecting a Book

  1. 用户在列表中选中条目 → currentRowChangedupdate_book_info(row)
  2. 刷新右侧三张封面(当前 + 后两本轮播预览)
  3. 构建基础信息 _base_info_cache
  4. bookintro.json 已有简评 → 直接渲染;否则启动新 BookReviewWorker → 占位“简评获取中...”
  5. 线程完成 → 通过信号调用 _on_review_finished → 更新 HTML。

13.5 AI 简评线程生命周期

  1. 实例化 BookReviewWorker(bookname, prompt, json_path)
  2. 连接 finished 信号到:
  • _on_review_finished
  • _remove_worker(清理活动线程列表)
  1. worker.start() → 线程内部:调用 DashScopeChatClient.ask();写入/更新 bookintro.json;发送 finished 信号。
  2. 主线程根据 _current_bookname 校验是否仍是当前书,防止串写。

13.6 已读书籍网格刷新逻辑

事件触发:

  1. 程序启动初次调用 _populate_finished_books_grid()
  2. Tab 切换到“已读书籍”标签 → _on_main_tab_changed() → 若命中,立即 _relayout_finished_grid() 并延迟一次;(后续可扩展为定期重新查询)
  3. (可选待扩展)外部刷新按钮调用 _populate_finished_books_grid()

数据来源:BookListManager.get_finished_books_this_year()

  • 查询本地 BKLibrary.sqliteZISFINISHED=1 AND ZDATEFINISHED NOT NULL
  • 将 Apple epoch(2001) 秒转为 datetime,过滤 year==当前年
  • 返回列表后排序(时间倒序)

13.7 封面加载与缩放流程

  1. _load_initial() / update_book_info() 内调用 find_book_cover() 获取路径
  2. 原图 QPixmap 存入 _cover_pixmaps_original
  3. _apply_cover_scale() 使用当前 cover_ratio(默认 1.2)和弹性策略计算目标高度
  4. 固定宽 180px受硬上限 400px 与(可选)文本区 45% 限制
  5. 非弹性模式忽略文本高度,仅 ratio + 硬上限。

13.8 导出流程Export Notes

  1. 用户点击“导出”按钮 → export_notes()
  2. 取当前行 assetid → BookNotesExporter.build_booksnote(bookid)
  3. 组装单书字典 → export_booksnote_to_md() 输出 Markdown 到 notes/ 目录
  4. 弹窗提示路径。

13.9 统计图表流程

  1. 启动后调用 _init_charts()(懒加载)
  2. 获取周 / 月 / 年聚合数据及总指标
  3. 构造原生自绘组件 BarChartWidget / ScatterChartWidget / BubbleMetricsWidget
  4. 添加到对应 Layout。

13.10 关键调用关系(摘要)

update_book_info -> find_book_cover (CoverMixin)
update_book_info -> BookReviewWorker.start -> _on_review_finished
_populate_finished_books_grid (FinishedBooksMixin) -> manager.get_finished_books_this_year
_on_finished_cover_clicked -> _switch_to_export_tab -> listwidget.setCurrentRow -> update_book_info
export_notes -> BookNotesExporter.build_booksnote -> export_booksnote_to_md
_init_charts -> manager.get_total_readtime* 系列函数

13.11 数据流摘要

iBooks 原始文件 -> sync_source_files -> data/*.sqlite / Books.plist
  └─ BookListManager 载入 -> booksinfo / open_times / 阅读统计
     ├─ IBookExportApp 构建主列表
     ├─ FinishedBooksMixin 查询已读 -> 网格
     └─ charts.py 生成可视化

注释数据库 -> AnnotationManager -> 笔记结构 -> BookNotesExporter.build_booksnote -> Markdown

AI 请求 -> BookReviewWorker -> DashScopeChatClient.ask -> bookintro.json -> _on_review_finished 渲染

13.12 扩展点与建议

  1. 已读书籍:增加“显示全部年份 / 仅今年”开关;提供手动“刷新”按钮。
  2. 封面缓存:引入 LRU (assetid -> QPixmap) 降低重复磁盘扫描。
  3. AI 简评:加速策略(本地缓存 TTL、批量预取前 N 本)。
  4. 异步任务:统一线程池/任务队列,避免过多 QThread 分散管理。
  5. 测试建议:
  • 单元:BookListManager.get_finished_books_this_year 年份过滤、无行时返回。
  • 单元:封面查找:构造临时目录含多个候选文件。
  • 集成:启动后模拟选择书籍 → 断言 _current_bookname 及 HTML 含字段。
  1. 性能:大书量时(>1000列表初始化可用分页或懒加载。
  2. 打包:后续可用 PyInstaller将可执行与资源icons、ui整合。

13.13 风险与缓解

风险 描述 缓解
数据不同步 data/ 下 sqlite 未更新导致已读缺失 提供“重新同步”按钮;比对文件修改时间
AI 接口失败 网络/配额问题 回退本地提示文本;重试按钮
UI 首次网格错排 视口宽度未稳定 采用双阶段(立即+延迟)重排 & Resize 监听
线程未回收 多次切换书籍产生积压 维护 _active_workers 列表并在完成回收

(本节为 2025-09 结构化重构新增)

14. 书籍阅读时长统计与可视化

14.1 阅读时长统计逻辑

  1. readtime30d每本书最近30天每天的阅读时长分钟索引0为今天索引29为30天前。
  2. readtime12m每本书今年每月的累计阅读时长分钟索引0为1月索引11为12月。统计逻辑为遍历今年每一天按月累计。
  3. readtime_year:每本书今年总阅读时长(分钟),为readtime12m各月之和。
  4. 支持无笔记但当天有打开书籍时,阅读时长设为READ_TIME_OPEN_DAYconfig.py配置默认30分钟
  5. 多条笔记时统计相邻笔记时间差仅累加小于3小时的部分更真实反映实际阅读行为。

14.2 全局统计函数

  • get_total_readtime_year():返回全年所有书的累计阅读时间(分钟)。
  • get_total_readtime12m()返回全年所有书的月度累计阅读时间长度12的列表单位分钟
  • get_total_readtime(days=30)返回最近days天每天所有书籍的总阅读时间分钟索引0为今天。

设计说明

  • 所有统计均以“分钟”为单位,便于可视化和分析。
  • 年度统计遍历今年每一天,保证月度和年度数据完整。
  • 统计逻辑与实际阅读行为高度贴合,支持无笔记但有打开书籍的场景。

14.3 可视化设计(统计标签页)

布局:统计页使用 2x2 宫格:

  • 左上frame_bubble综合指标气泡图。
  • 右上frame_year全年 12 个月阅读时长柱状图。
  • 左下 (frame_week):最近 7 天阅读时长柱状图索引0=今天)。
  • 右下 (frame_month):最近 30 天阅读时长柱状图索引0=今天)。

数据来源

  • 周图:get_total_readtime(days=7) 结果列表(单位:分钟)。
  • 月图:get_total_readtime(days=30) 结果列表(单位:分钟)。
  • 年图:get_total_readtime12m() 返回长度 12 列表(分钟)。
  • 综合:
    • 全年阅读小时数 = get_total_readtime_year() / 60向下取整或保留1位小数
    • 月均阅读小时数 = (sum(month_list) / 12) / 60
    • 近7天阅读小时数 = sum(week_list) / 60
    • 日均阅读分钟数 = sum(month_list[:30 或 recent30]) / 30使用最近30天合计除以30

气泡图

  • 使用 5 个气泡新增“已读”分别表示全年累计、月均、近7天、日均、今年已读完书籍数量。
  • 半径 r ~ sqrt(value_normalized) 以减弱大值差异;先将不同单位映射到统一“分钟等价”尺度:h -> *60m -> 原值book -> *60(约定 1 本折算 1 小时,确保视觉上不至过小)。
  • 默认布局4 指标布局在十字形当包含“已读”且数量≥5 时采用菱形 + 中心分布(中间放“已读”)。
  • 颜色:全年(#5b6ee1)、月均(#c9b2d9)、近7天(#f4b2c2)、日均(#b9b542)、已读(#6aa84f)。
  • 文本格式:值 + 单位\n标签;单位规则:h 显示“x.x 小时 / x 小时”,m 显示“x 分钟”,book 显示“x 本书”。
  • 归一化约束:自动检测重叠,通过求最小非重叠缩放系数 S使圆之间保留最小 6px 间距。

渲染技术

  • 使用原生 Qt 自绘组件QWidget + QPainter实现柱状图与气泡图文件 charts.py
  • 优势:减少第三方依赖(移除 matplotlib启动更快、打包体积更小自绘可精细掌控布局与样式。
  • 结构:
    • BarChartWidget:通用柱状图组件,支持数值标签、自适应缩放、单位显示。
    • BubbleMetricsWidget:四指标气泡图,按归一化后的平方根缩放半径,支持动态指标扩展。
  • 刷新策略:当前初始化时构建;若后续增加刷新按钮,可对组件调用 setData/setMetrics 后 update()。

更新策略

  1. 启动时已调用 sync_source_files,再构建 BookListManager
  2. 通过管理器获取三类聚合数据。
  3. 生成 numpy 数组(可选)并绘制。
  4. 若无数据(全 0显示占位提示“暂无阅读数据”。

异常处理

  • 捕获绘图异常ImportError/RuntimeError在 frame 中放置 QLabel 显示错误信息而不是抛出。

后续扩展

  • 柱状图支持堆叠 / 渐变填充、鼠标 hover tooltip。
  • 气泡图支持动画过渡或改为雷达/仪表盘形式;“已读”气泡可按年份切换(未来提供年份选择器)。
  • 增加刷新按钮与 Esc 退出全屏逻辑。