13 KiB
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)去反推定位内容,没法实现,浪费时间。
解决思路:
- 基本信息
- 笔记数据在这个表中:~/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
-
copy以上前三个文件到./data目录下分别命名为:AEAnnotation.sqlite、BKLibrary.sqlite、Books.plist BKLibrary.sqlite暂不使用,使用Books.plist
-
解析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。
-
查询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': '應該有一些系統性的表達機制'}, } …… }
-
需要导出的书名作为参数,比如"最好的辩护",书名做从booksinfo中做模糊查询,如匹配到多本,在shell中提示,选择哪一本书。 从booksinfo获取本书的uuid和这本书epub的路径path,进而从annotations中获取这本书的笔记。
-
依据这本书的路径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'}, …… } } }
- 使用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
- 增加QTUI
- 增加统计和展示 统计
- 周活跃 - 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_days60min(60mins为每天阅读时间,可配置)
- 本年度已阅读书籍封面图片橱窗,6xn展示,图片西面注上书名,支持把6xn导出成一张图片。
读完时间依据ZISFINISHED为1,ZDATEFINISHED,计算是否今年读完。
封面图片通过在IBOOKS_BOOKS_DIR对应的书籍下查找cover.jpg/png/jpeg文件,如果没有,查找cover*html文件,解析该文件获取图片路径(相对于html文件的路径)。如:
,取出../Images/data-url-image.jpeg
,取出Image00007.jpg
202500907
- 使用GPT5生成ipad app版本
- vscode 下载插件swift
- 安装Command Line Tools for Xcode。MacBook一直提醒我安装,正好更新了。