239 lines
13 KiB
Markdown
239 lines
13 KiB
Markdown
|
||
# iBooks笔记导出
|
||
|
||
## epub结构
|
||
|
||
sqlite3 ~/Library/Containers/com.apple.iBooksX/Data/Documents/AEAnnotation/AEAnnotation_v10312011_1727_local.sqlite
|
||
sqlite> .tables
|
||
**ZAEANNOTATION** Z_METADATA Z_PRIMARYKEY
|
||
ZBCCLOUDSYNCVERSIONS Z_MODELCACHE
|
||
|
||
**ZAEANNOTATION**表:
|
||
| 字段名 | 值 | 含义描述推测 |
|
||
| ---- | ---- | ---- |
|
||
| Z_PK | 4662 | 可能是该条记录在数据库中的主键(Primary Key),用于唯一标识这条批注数据 |
|
||
| Z_ENT | 1 | 可能代表实体类型(Entity Type),用于区分数据库中不同类型的记录,此处可能标识为“批注”类实体 |
|
||
| Z_OPT | 1 | 可能是版本号或优化字段(Optimization),用于记录数据的更新次数或状态标识 |
|
||
| ZANNOTATIONDELETED | 0 | 标识批注是否被删除,0表示未删除,1可能表示已删除 |
|
||
| ZANNOTATIONISUNDERLINE | 1 | 标识批注是否为下划线样式,1表示该批注是下划线标注,0可能表示非下划线 |
|
||
| ZANNOTATIONSTYLE | 0 | 可能表示批注的样式属性(如颜色、粗细等),0可能对应默认样式 |
|
||
| ZANNOTATIONTYPE | 2 | 表示批注的类型,可能用于区分是下划线、高亮、注释等不同类型,2此处可能对应下划线批注 |
|
||
| ZPLABSOLUTEPHYSICALLOCATION | 0 | 可能是批注在文档中的绝对物理位置信息,具体含义需结合文档存储格式判断 |
|
||
| ZPLLOCATIONRANGEEND | 0 | 批注内容在文档中的位置范围终点,可能是字符偏移量或段落索引等 |
|
||
| ZPLLOCATIONRANGESTART | 9 | 批注内容在文档中的位置范围起点,可能是字符偏移量或段落索引等,与终点配合确定批注选中的文本范围 |
|
||
| **ZANNOTATIONCREATIONDATE** | 774064827.1 | 批注的创建时间,可能是时间戳格式(需转换为具体日期时间) |
|
||
| ZANNOTATIONMODIFICATIONDATE | 774064829.2 | 批注的最后修改时间,时间戳格式,记录批注内容或属性的更新时间 |
|
||
| **ZANNOTATIONASSETID** | 768E1CD0B3086166F791683869B12425 | 可能是该批注所属文档的唯一标识(Asset ID),用于**关联批注对应的电子书或文档** |
|
||
| ZANNOTATIONCREATORIDENTIFIER | com~apple~iBooks | 标识创建该批注的应用程序,此处表明是苹果的iBooks应用创建的批注 |
|
||
| **ZANNOTATIONLOCATION** | epubcfi(/6/20[id155]!/4/412/1,:0,:97) | 采用EPUB格式的CFI(Content Fragment Identifier),**精确标识批注在电子书文档中的位置** |
|
||
| ZANNOTATIONNOTE | | 批注的备注内容,此处为空表示该批注没有额外添加的文字备注 |
|
||
| **ZANNOTATIONREPRESENTATIVETEXT** | 在听完这个充满暴力、腐败和表里不一的悲剧故事之后,希维格和我都已精疲力尽。我们不知道到底谁比较可恶:是罪不可逭的席格和犹太人防卫联盟,还是恶意侵犯席格的宪法基本人权、并且否认曾许下承诺的政府官员。 | 可能是批注所关联的代表性文本,通常是包含批注选中内容的上下文文本 |
|
||
| **ZANNOTATIONSELECTEDTEXT** | 在听完这个充满暴力、腐败和表里不一的悲剧故事之后,希维格和我都已精疲力尽。我们不知道到底谁比较可恶:是罪不可逭的席格和犹太人防卫联盟,还是恶意侵犯席格的宪法基本人权、并且否认曾许下承诺的政府官员 | 被用户选中并添加批注(下划线)的具体文本内容,**优先使用该字段作为内容定位依据**。 |
|
||
| ZANNOTATIONUUID | 7097EAE0-0EDB-4552-9AE0-FA6AB390B90B | 批注的唯一标识符(UUID),用于在系统中唯一标识这条批注,避免重复 |
|
||
| ZFUTUREPROOFING1 | | 预留字段,可能用于未来功能扩展,目前未使用 |
|
||
| ZFUTUREPROOFING10 | | 预留字段,用于未来功能扩展,目前未使用 |
|
||
| ZFUTUREPROOFING11 | | 预留字段,用于未来功能扩展,目前未使用 |
|
||
| ZFUTUREPROOFING12 | | 预留字段,用于未来功能扩展,目前未使用 |
|
||
| ZFUTUREPROOFING2 | | 预留字段,用于未来功能扩展,目前未使用 |
|
||
| ZFUTUREPROOFING3 | | 预留字段,用于未来功能扩展,目前未使用 |
|
||
| ZFUTUREPROOFING4 | | 预留字段,用于未来功能扩展,目前未使用 |
|
||
| ZFUTUREPROOFING5 | | 预留字段,用于未来功能扩展,目前未使用 |
|
||
| ZFUTUREPROOFING6 | | 预留字段,用于未来功能扩展,目前未使用 |
|
||
| ZFUTUREPROOFING7 | | 预留字段,用于未来功能扩展,目前未使用 |
|
||
| ZFUTUREPROOFING8 | | 预留字段,用于未来功能扩展,目前未使用 |
|
||
| ZFUTUREPROOFING9 | | 预留字段,用于未来功能扩展,目前未使用 |
|
||
| ZPLSTORAGEUUID | 774064829.2 | 可能是与存储相关的唯一标识符,用于关联批注在存储系统中的位置或状态 |
|
||
| ZPLUSERDATA | | 可能用于存储与用户相关的自定义数据,此处为空表示无额外用户数据 |
|
||
|
||
|
||
|
||
## 笔记数据
|
||
### 书籍元数据
|
||
PLIST_PATH = os.path.expanduser("~/Library/Containers/com.apple.BKAgentService/Data/Documents/iBooks/Books/Books.plist")
|
||
|
||
{
|
||
"Books" => [
|
||
|
||
……
|
||
|
||
603 => {
|
||
"artistName" => "(美)德肖维茨"
|
||
"BKAllocatedSize" => 1388544
|
||
"BKBookType" => "epub"
|
||
"BKDisplayName" => "最好的辩护-德肖维茨"
|
||
"BKGeneratedItemId" => "768E1CD0B3086166F791683869B12425"
|
||
"BKGenerationCount" => 1
|
||
"BKInsertionDate" => 773817769
|
||
"BKPercentComplete" => 1
|
||
"book-info" => {
|
||
"package-file-hash" => "768E1CD0B3086166F791683869B12425"
|
||
}
|
||
"genre" => "法律"
|
||
"isPreview" => 0
|
||
"itemName" => "最好的辩护"
|
||
"path" => "/Users/gavin/Library/Mobile Documents/iCloud~com~apple~iBooks/Documents/最好的辩护.epub"
|
||
"sourcePath" => "/Users/gavin/Library/Containers/com.apple.iBooksX/Data/Library/Caches/Inbox/最好的辩护-德肖维茨.epub"
|
||
"updateDate" => 2025-07-10 05:22:49 +0000
|
||
}
|
||
……
|
||
]
|
||
|
||
|
||
### 笔记数据库
|
||
DB_PATH = os.path.expanduser("~/Library/Containers/com.apple.iBooksX/Data/Documents/AEAnnotation/AEAnnotation_v10312011_1727_local.sqlite")
|
||
|
||
## books
|
||
~/Library/Containers/com.apple.iBooksX/Data/Documents/BKLibrary/BKLibrary-1-091020131601.sqlite
|
||
gavin@GavinsMAC BKLibrary % sqlite3 ~/Library/Containers/com.apple.iBooksX/Data/Documents/BKLibrary/BKLibrary-1-091020131601.sqlite
|
||
sqlite> .tables
|
||
ZBCCLOUDSYNCVERSIONS ZBKJALISCOSTATUS Z_MODELCACHE
|
||
ZBKCOLLECTION **ZBKLIBRARYASSET** Z_PRIMARYKEY
|
||
**ZBKCOLLECTIONMEMBER** Z_METADATA
|
||
|
||
|
||
## 问题
|
||
### 20250729
|
||
从epubcfi,如epubcfi(/6/20[id155]!/4/412/1,:0,:97)去反推定位内容,没法实现,浪费时间。
|
||
|
||
**解决思路**:
|
||
|
||
1. 基本信息
|
||
- 笔记数据在这个表中:~/Library/Containers/com.apple.iBooksX/Data/Documents/AEAnnotation/AEAnnotation_v10312011_1727_local.sqlite
|
||
- 书籍清单在这个表中:~/Library/Containers/com.apple.iBooksX/Data/Documents/BKLibrary/BKLibrary-1-091020131601.sqlite
|
||
- 书籍元数据在这里:~/Library/Containers/com.apple.BKAgentService/Data/Documents/iBooks/Books/Books.plist
|
||
- 书籍在这个directory中(epub为文件夹,非压缩文件):~/Library/Mobile Documents/iCloud~com~apple~iBooks/Documents
|
||
|
||
2. copy以上前三个文件到./data目录下分别命名为:AEAnnotation.sqlite、BKLibrary.sqlite、Books.plist
|
||
BKLibrary.sqlite暂不使用,使用Books.plist
|
||
|
||
3. 解析Books.plist,存在名为booksinfo的defaultdict(dict)中。booksinfo数据结构如下:
|
||
booksinfo = {
|
||
"768E1CD0B3086166F791683869B12425": {
|
||
"BKDisplayName" : "最好的辩护-德肖维茨",
|
||
"artistName" : "(美)德肖维茨",
|
||
"BKBookType" : "epub",
|
||
"BKGeneratedItemId" : "768E1CD0B3086166F791683869B12425",
|
||
"itemName" : "最好的辩护",
|
||
"path" : "/Users/gavin/Library/Mobile Documents/iCloud~com~apple~iBooks/Documents/最好的辩护.epub"
|
||
}
|
||
……
|
||
}
|
||
其中768E1CD0B3086166F791683869B12425为BKGeneratedItemId。
|
||
|
||
4. 查询AEAnnotation.sqlite,获取ZANNOTATIONCREATIONDATE ZANNOTATIONASSETID ZANNOTATIONLOCATION ZANNOTATIONSELECTEDTEXT ZANNOTATIONUUID
|
||
如:
|
||
ZANNOTATIONCREATIONDATE 746202137.8, #需要转化为2025/7/30这样的格式。
|
||
ZANNOTATIONASSETID 5F2C1C40566C61A2267907E621A664A4
|
||
ZANNOTATIONLOCATION epubcfi(/6/98[id61]!/4[1CQAE0-902f5dcbb4a04a858d573ec5ee66e862]/58/1,:28,:226) #需要获取idref = id61,filepos=1CQAE0-902f5dcbb4a04a858d573ec5ee66e862
|
||
ZANNOTATIONSELECTEDTEXT 这才是对人而言的自由
|
||
ZANNOTATIONUUID 0456103A-7161-451A-B762-1305ECBECBDB
|
||
并把数据存到名为annotations的defaultdict(dict)中。annotations的数据结构如下:
|
||
annotations = {
|
||
'CB9A605DCD687C4FA544DD4BCCD00D43': {
|
||
'326CA2CF-3298-45D5-A93F-440E6F2A0B33': {
|
||
'creationdate': '2023/7/12',
|
||
'filepos': None,
|
||
'idref': '008.xhtml',
|
||
'note': None,
|
||
'selectedtext': '這就是宣傳的恐怖之處'},
|
||
'2BD61C93-D1F1-4553-90CB-043A6E06DBA1': {
|
||
'creationdate': '2023/7/12',
|
||
'filepos': None,
|
||
'idref': '008.xhtml',
|
||
'note': None,
|
||
'selectedtext': '應該有一些系統性的表達機制'},
|
||
}
|
||
……
|
||
}
|
||
|
||
5. 需要导出的书名作为参数,比如"最好的辩护",书名做从booksinfo中做模糊查询,如匹配到多本,在shell中提示,选择哪一本书。
|
||
从booksinfo获取本书的uuid和这本书epub的路径path,进而从annotations中获取这本书的笔记。
|
||
|
||
6. 依据这本书的路径path,解析epub文件夹下的文件。
|
||
假定epub目录为epubdir,xx.opf(package.opf|standard.opf|content.opf)和toc.ncx文件可能的位置在:
|
||
epubdir/content.opf epubdir/toc.ncx
|
||
epubdir/OEBPS/content.opf epubdir/OEBPS/toc.ncx
|
||
epubdir/EPUB/package.opf epubdir/EPUB/toc.ncx
|
||
epubdir/item/standard.opf epubdir/item/toc.ncx
|
||
|
||
- 解析opf文件,存入default(dict)数据结构contentopf:
|
||
contentopf = {
|
||
"768E1CD0B3086166F791683869B12425": {
|
||
id163 : index_split_000.html, # idref: ref
|
||
id162 : index_split_001.html,
|
||
id161 : index_split_002.html,
|
||
id160 : index_split_003.html,
|
||
id159 : index_split_004.html,
|
||
id158 : index_split_005.html
|
||
……
|
||
}
|
||
}
|
||
|
||
- 解析toc.ncx文件,存入default(dict)数据结构contenttoc:
|
||
contenttoc =
|
||
{
|
||
"768E1CD0B3086166F791683869B12425": {
|
||
{ 'num_1': {'filepos': None, 'label': '\n最好的辩护\n', 'ref': 'index_split_000.html'},
|
||
'num_100': { 'filepos': None, 'label': '\n第八章父亲的罪\n',
|
||
'num_101': { 'filepos': 'filepos698889', 'label': '\n没有父亲的生活\n', 'ref': 'index_split_016.html'},
|
||
'num_102': {'filepos': 'filepos706518', 'label': '\n越狱\n', 'ref': 'index_split_016.html'},
|
||
……
|
||
}
|
||
}
|
||
}
|
||
|
||
7. 使用annotationdata.py获取annotations,把annotations扩充label_path,
|
||
通过annotations的assetid从bookinfo获取path路径,在path下找到.opf和.ncx,使用opf_parse.py和toc_parse.py获取label_path
|
||
|
||
|
||
booksnote = {
|
||
assetid: { label_path: { uuid: {
|
||
'creationdate': '2023/7/12',
|
||
'filepos': None,
|
||
'idref': '008.xhtml',
|
||
'note': None,
|
||
'selectedtext': '這就是宣傳的恐怖之處'
|
||
}}}}
|
||
|
||
|
||
|
||
应该从epubcfi中的id155,去找到html文件,在从笔记内容去匹配对应文件中的位置。
|
||
需要有个函数吧html的数据结构dict化,很快可以从内容找到title。
|
||
|
||
从epubcfi的ZANNOTATIONASSETID找到bookid
|
||
从ZANNOTATIONASSETID 依据bookid => epub路径
|
||
解析book到dict中。
|
||
解析content.opf,中的[id,href]
|
||
关联id,title,章节内容
|
||
=> bookdict = [id,title,[内容]]
|
||
从ZAEANNOTATION的笔记内容,遍历bookdict找到内容,找到title
|
||
最后按bookname,title归并笔记
|
||
|
||
### 202500906
|
||
1. 增加QTUI
|
||
2. 增加统计和展示
|
||
统计
|
||
- 周活跃 - 30天每天的阅读时长,柱状图
|
||
某本书,根据每条笔记note中ZANNOTATIONCREATIONDATE,如果某天没有note,则阅读时间为0;如果只有一条note,阅读时间为READ_TIME_DAY=60(在config.py中配置);如果note超过1条,计算第一条和最后一条的时间差,作为阅读时长。放在readtime30d这个list中。
|
||
- 月活跃 - 30天每天的阅读时长,柱状图
|
||
每本书,ZANNOTATIONCREATIONDATE,落在30天前到今天的天数*60min(60mins为每天阅读时间,可配置)
|
||
- 已阅读的书籍: 每本平均阅读时长。所有书籍:总阅读时长,年阅读时长,年平均每日阅读时长,累计阅读天数。用气泡图表示。
|
||
如果已读完,表ZBKLIBRARYASSET的ZISFINISHED字段为1;
|
||
表ZBKLIBRARYASSET的ZDATEFINISHED,读完时间,可以统计今年读完的书籍
|
||
*表ZBKLIBRARYASSET的(ZDATEFINISHED - ZCREATIONDATE),得出书本创建到读完的阅读周期,并非真正的阅读时间。
|
||
表ZAEANNOTATION的ZANNOTATIONCREATIONDATE,找出最早的一条批注创建时间,(ZDATEFINISHED-ZANNOTATIONCREATIONDATE),大约周期read\_days。近似的用read_days*60min(60mins为每天阅读时间,可配置)
|
||
- 本年度已阅读书籍封面图片橱窗,6xn展示,图片西面注上书名,支持把6xn导出成一张图片。
|
||
读完时间依据ZISFINISHED为1,ZDATEFINISHED,计算是否今年读完。
|
||
封面图片通过在IBOOKS_BOOKS_DIR对应的书籍下查找*cover*.jpg/png/jpeg文件,如果没有,查找cover*html文件,解析该文件获取图片路径(相对于html文件的路径)。如:
|
||
<image width="890" height="1186" xlink:href="../Images/data-url-image.jpeg"/>,取出../Images/data-url-image.jpeg
|
||
<img src="Image00007.jpg" />,取出Image00007.jpg
|
||
|
||
### 202500907
|
||
1. 使用GPT5生成ipad app版本
|
||
- vscode 下载插件swift
|
||
- 安装Command Line Tools for Xcode。MacBook一直提醒我安装,正好更新了。
|
||
|
||
|
||
|
||
|