如何使用ebooklib从现有EPUB生成合规的最小化EPUB文件?
问题描述
我希望使用Python和ebooklib从现有EPUB文件生成一个「最小化」的EPUB版本,仅保留必要元数据(title、creator、date、publisher)、封面图片、目录(toc)、书脊(spine)及全部文本,移除多余的元数据、图片、字体等内容。
以下是我当前的代码:
import ebooklib as ebl from ebooklib import epub def minimal_epub(filename): old, new = epub.read_epub(filename), epub.EpubBook() for key in ['title', 'creator', 'date', 'publisher']: metadata = old.get_metadata('DC', key) if metadata: new.add_metadata('DC', key, metadata[0][0]) print(new.get_metadata('DC', key)) # show current metadata field print(new.metadata) # show all metadata fields # assume that the largest image in archive is the cover image sortkey = lambda item: -len(item.content) # sort images by decreasing sizes cover = sorted(old.get_items_of_type(ebl.ITEM_IMAGE), key=sortkey)[0] new.set_cover(cover.file_name, cover.content) # keep largest image as cover print(cover.file_name) # show cover filename for item in old.get_items_of_type(ebl.ITEM_DOCUMENT): new.add_item(item); print(item) # show name of document items for item in old.get_items_of_type(ebl.ITEM_STYLE): new.add_item(item); print(item) # show name of styles new.toc = old.toc; print(new.toc) # show links in table of content new.spine = old.spine; print(new.spine) # show spine new.add_item(epub.EpubNcx()); new.add_item(epub.EpubNav()) # navigation epub.write_epub(filename.replace('.','+.'), new) >>> minimal_epub('path/to/file.epub')
我无法直接定位EPUB文件中的封面图片,因此假设体积最大的图片为封面,该方法多数情况下可行。但生成的最小化EPUB文件仅约80%能被标准EPUB阅读器正常打开,剩余部分会因格式错误无法正确显示。推测可能遗漏了某些必要文件,但我对EPUB格式不熟悉,且ebooklib文档不够详尽,希望能得到相关建议。
解决方案
1. 精准识别封面图片
不要依赖文件大小判断封面,EPUB规范中封面通常会在元数据或OPF文件里标记,优先通过规范方式获取:
# 从元数据提取封面ID cover_id = None for meta in old.metadata.get('http://www.idpf.org/2007/opf', 'meta'): if meta[0].get('name') == 'cover': cover_id = meta[0]['content'] break # 根据ID获取封面图,找不到再用原方法兜底 if cover_id: cover_item = old.get_item_with_id(cover_id) if cover_item: new.set_cover(cover_item.file_name, cover_item.content) else: sortkey = lambda item: -len(item.content) cover = sorted(old.get_items_of_type(ebl.ITEM_IMAGE), key=sortkey)[0] new.set_cover(cover.file_name, cover.content)
2. 修复目录与书脊的引用失效问题
直接赋值旧书的toc和spine会导致内部归属关系混乱,需要重新构建:
重构目录(TOC)
new.toc = [] for toc_item in old.toc: if isinstance(toc_item, epub.Link): linked_item = old.get_item_with_href(toc_item.href) if linked_item in new.items: new_link = epub.Link(linked_item.href, toc_item.title, toc_item.id) new.toc.append(new_link) elif isinstance(toc_item, tuple): # 处理嵌套目录 new_subitems = [] for sub_item in toc_item[1]: if isinstance(sub_item, epub.Link): linked_item = old.get_item_with_href(sub_item.href) if linked_item in new.items: new_subitems.append(epub.Link(linked_item.href, sub_item.title, sub_item.id)) new.toc.append( (toc_item[0], new_subitems) )
重构书脊(Spine)
new.spine = ['nav'] # 新版EPUB必须包含nav项 for item_id in old.spine: if item_id == 'ncx': continue # 用nav替代ncx,无需保留 item = old.get_item_with_id(item_id) if item and item in new.items: new.spine.append(item.id)
3. 正确处理导航文件
优先保留原书的导航文件,避免ebooklib自动生成的导航与原结构冲突:
# 检查原书是否有自定义nav项 nav_item = None for item in old.get_items_of_type(ebl.ITEM_NAVIGATION): nav_item = item break if nav_item: new.add_item(nav_item) if nav_item.id not in new.spine: new.spine.insert(0, nav_item.id) else: new.add_item(epub.EpubNav())
4. 只保留被引用的样式文件
并非所有样式都必要,仅保留文档实际引用的样式:
# 收集所有文档引用的样式href used_styles = set() for doc_item in old.get_items_of_type(ebl.ITEM_DOCUMENT): content = doc_item.content.decode('utf-8') # 简单匹配<link>标签中的样式引用 import re matches = re.findall(r'<link[^>]+href="([^"]+)"[^>]+stylesheet', content) for href in matches: used_styles.add(href) # 仅添加被引用的样式 for item in old.get_items_of_type(ebl.ITEM_STYLE): if item.href in used_styles: new.add_item(item)
5. 验证生成的EPUB格式
生成文件后,用EPUB验证工具(如IDPF EPUB Validator)检查错误,常见问题包括:
- 确保所有item的ID唯一无重复
- 元数据中的日期符合ISO 8601格式(如
2023-10-05) - 封面图片的MIME类型正确(ebooklib的
set_cover通常会自动处理,但特殊格式需手动指定)
整合以上步骤后,生成的EPUB兼容性会大幅提升。
内容的提问来源于stack exchange,提问作者sciroccorics
相关产品推荐
相关产品推荐

