11 KiB
11 KiB
2025年阅读统计功能设计补充
书籍阅读时长统计
readtime30d
:每本书最近30天每天的阅读时长(分钟),索引0为今天,索引29为30天前。readtime12m
:每本书今年每月的累计阅读时长(分钟),索引0为1月,索引11为12月。统计逻辑为遍历今年每一天,按月累计。readtime_year
:每本书今年总阅读时长(分钟),为readtime12m
各月之和。- 支持无笔记但当天有打开书籍时,阅读时长设为
READ_TIME_OPEN_DAY
(config.py配置,默认30分钟)。 - 多条笔记时,统计相邻笔记时间差(仅累加小于3小时的部分),更真实反映实际阅读行为。
全局统计函数
get_total_readtime_year()
:返回全年所有书的累计阅读时间(分钟)。get_total_readtime12m()
:返回全年所有书的月度累计阅读时间(长度12的列表,单位:分钟)。get_total_readtime(days=30)
:返回最近days天每天所有书籍的总阅读时间(分钟),索引0为今天。
设计说明
- 所有统计均以“分钟”为单位,便于可视化和分析。
- 年度统计遍历今年每一天,保证月度和年度数据完整。
- 统计逻辑与实际阅读行为高度贴合,支持无笔记但有打开书籍的场景。
可视化设计(统计标签页)
布局:统计页使用 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)。
- 全年阅读小时数 =
气泡图:
- 使用 4 个气泡分别表示上述四项指标。
- 半径 r ~ sqrt(value_normalized) 以减弱大值差异;对“小时数”统一换算为分钟后再归一。
- 颜色建议:全年(蓝)、月均(橙)、近7天(绿)、日均(紫)。
- 文本格式:
标签\n数值+单位
,例如:全年\n120h
,日均\n45min
。
渲染技术(已更新):
- 使用原生 Qt 自绘组件(QWidget + QPainter)实现柱状图与气泡图,文件
charts.py
。 - 优势:减少第三方依赖(移除 matplotlib),启动更快、打包体积更小;自绘可精细掌控布局与样式。
- 结构:
BarChartWidget
:通用柱状图组件,支持数值标签、自适应缩放、单位显示。BubbleMetricsWidget
:四指标气泡图,按归一化后的平方根缩放半径,支持动态指标扩展。
- 刷新策略:当前初始化时构建;若后续增加刷新按钮,可对组件调用 setData/setMetrics 后 update()。
更新策略:
- 启动时已调用
sync_source_files
,再构建BookListManager
。 - 通过管理器获取三类聚合数据。
- 生成 numpy 数组(可选)并绘制。
- 若无数据(全 0),显示占位提示“暂无阅读数据”。
异常处理:
- 捕获绘图异常(ImportError/RuntimeError),在 frame 中放置 QLabel 显示错误信息而不是抛出。
后续扩展:
- 柱状图支持堆叠 / 渐变填充、鼠标 hover tooltip。
- 气泡图支持动画过渡或改为雷达/仪表盘形式。
- 增加刷新按钮与 Esc 退出全屏逻辑。
iBooks 笔记导出工具 详细设计文档
1. 概述
本工具用于从 macOS iBooks(Apple Books)应用的数据文件中提取用户的书籍笔记,并以 Markdown 格式导出。支持从 iBooks 的数据库和 plist 文件自动同步数据,支持交互式选择书籍导出,导出内容结构清晰,便于后续整理和阅读。 支持按最近打开时间排序书籍,菜单显示书名与时间戳,导出流程高效。
2. 主要功能
- 自动同步 iBooks 数据库和书籍信息文件到本地
./data
目录。 - 解析 iBooks 笔记数据库,构建结构化的
booksnote
数据。 - 解析书籍元数据(如书名、路径等)。
- 支持交互式模糊搜索选择要导出的书籍。
- 按章节导出所选书籍的所有笔记,格式为 Markdown。
- 书名中如含有“-xxxx”后缀,仅保留“-”前的主书名。
- 书籍选择菜单按最近打开时间(last_open)降序排序,显示格式为“书名 [时间戳]”。
3. 主要数据结构
3.1 booksnote
booksnote = {
assetid: { label_path: { uuid: {
'creationdate': '2023/7/12',
'filepos': None,
'idref': '008.xhtml',
'note': None,
'selectedtext': '這就是宣傳的恐怖之處'
}}}
}
assetid
:书籍唯一标识label_path
:章节名uuid
:笔记唯一标识- 其余字段为笔记内容及元数据
4. 主要流程
4.1 数据同步
- 自动将 iBooks 的数据库和 plist 文件复制到本地
data/
目录,便于后续处理。
4.2 构建 booksnote
- 通过
get_annotations
解析 SQLite 笔记数据库,获取所有笔记。 - 通过
parse_books_plist
解析书籍元数据,获取书名、路径等信息。 - 遍历每本书的所有笔记,结合OPF、NCX文件和HTML 文件,定位章节名。
- 若无法通过目录文件定位章节,则尝试通过笔记选中文本在 HTML 文件中查找章节,否则标记为“未找到章节”。
4.3 交互式选择书籍
- 读取 Books.plist 获取所有书籍元数据。
- 读取 BKLibrary.sqlite,获取每本书的最近打开时间(last_open,苹果时间戳,基准2001-01-01)。
- 生成书名列表(优先
displayname
,其次itemname
,否则用assetid
),并去除“-xxxx”后缀。 - 按 last_open 时间戳降序排列,菜单显示“书名 [时间戳]”,时间戳为 last_open 字段。
- 使用 InquirerPy 提供模糊搜索交互界面,供用户选择要导出的书籍。
4.4 导出 Markdown
-
仅导出用户选择的书籍。
-
Markdown 格式如下:
# 笔记导出 2025-08-06 12:00 ## 书名 ### 章节名 选中文本 > 笔记内容
-
每条笔记独立分行,章节分组。
5. 关键函数说明
5.1 build_booksnote
- 输入:注释数据库路径、书籍 plist 路径
- 输出:结构化的 booksnote 字典
- 逻辑:遍历所有笔记,结合书籍元数据和目录信息,归类到章节下
5.2 export_booksnote_to_md
- 输入:booksnote、booksinfo、导出路径
- 输出:Markdown 字符串,并写入文件
- 逻辑:遍历每本书、每个章节、每条笔记,按格式输出
6. 交互与用户体验
- 通过命令行交互,用户可模糊搜索并选择要导出的书籍。
- 若无可导出的笔记,程序自动退出并提示。
- 导出后,显示导出文件路径和书名。
7. 代码片段示例
7.1 书名处理逻辑
name = info.get('displayname') or info.get('itemname') or assetid
# 如果书名中包含“-”,只取“-”前面的部分
if '-' in name: name = name.split('-', 1)[0].strip()
7.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()
8. 依赖说明
- Python 3
- 主要依赖库:
InquirerPy
,bs4
,shutil
,os
,datetime
,sqlite3
- 需有 iBooks 数据库、plist 文件和 BKLibrary.sqlite 的本地访问权限
9. 目录结构
data/
:存放同步下来的数据库和 plist 文件(含 AEAnnotation.sqlite、Books.plist、BKLibrary.sqlite 等)notes/
:导出的 Markdown 文件examples/
:epub 示例文件夹
9.1 主要代码文件说明(细化)
-
exportbooknotes.py
- 采用 OOP 设计,核心类为
BookNotesExporter
:build_booksnote(bookid=None)
:构建结构化笔记数据。export_booksnote_to_md(booksnote, booksinfo, out_path=None)
:导出为 Markdown。find_file_by_ext
、get_toc_tree
等辅助方法。
- 数据同步:自动复制 iBooks 数据库和元数据到本地。
- 菜单交互:按最近打开时间戳排序,显示“书名 [时间戳]”,支持模糊搜索。
- 只处理用户选中书籍的笔记,按章节分组导出 Markdown。
- 依赖核心解析模块,负责主流程调度。
- 采用 OOP 设计,核心类为
-
annotationdata.py
- OOP 设计,核心类为
AnnotationManager
:get_annotations(bookid=None)
:返回所有或指定 assetid 的笔记。parse_location(location)
:静态方法,解析定位信息。
- 解析 AEAnnotation.sqlite,提取所有或指定 assetid 的笔记。
- 支持苹果时间戳转换,结构化输出。
- OOP 设计,核心类为
-
booklist_parse.py
- OOP 设计,核心类为
BookListManager
:get_books_info()
:获取书籍元数据。get_books_last_open()
:获取每本书的最近打开时间。
- 解析 Books.plist,获取书籍元数据(书名、作者、路径、时间等)。
- 解析 BKLibrary.sqlite,获取每本书的最近打开时间。
- OOP 设计,核心类为
-
opf_parse.py
- OOP 设计,核心类为
OPFParser
:parse_opf(filepath)
:静态方法,返回 id->href 映射。
- 解析 epub 的 OPF 文件,获取章节与文件映射关系(idref -> href)。
- OOP 设计,核心类为
-
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 目录文件,递归构建章节树结构。
- OOP 设计,核心类为
-
backup/booksnote.py
- 历史/备份脚本,辅助数据迁移或格式转换。
10. 扩展与维护建议
- 可扩展支持多本书批量导出
- 可增加导出格式(如 HTML、PDF)
- 可优化章节定位算法,提升准确率
- 可增加 GUI 交互界面
如需进一步细化某一部分设计,请告知!