使用ebooklib制作符合epub3规范的epub文件
扫描的pdf文档在电子设备上阅读时受到了很多限制,在OCR质量越来越高的今天,完全可以考虑将一本扫描的pdf电子书改造成epub电子书。改造过程大致如下:
1、OCR
可以用AI或者一些大厂的免费API接口进行识别,可参阅自己写个OCR工具,某捷休想再从我这里收费_如何自建ocr识别库-CSDN博客
2、校稿
一般扫描质量较高的文档OCR的准确率极高,但会有一些常见错误,例如用VBA将word文档处理成支持弹出式注释的epub文档可用的html内容-CSDN博客一文中的例子中的带圈数字注释引用和注释编号经常会有缺漏和错误的,引号不能配对等。我们可以将识别出的文本复制到Word中,注释引用和注释编号缺漏的地方可以一律用①补上(可以考虑用Autohotkey做个键盘映射,便于输入①),然后在后续处理过程中将其调整准确。可以用下面的宏辅助检查分段选择范围内的引号配对情况:
Sub 检查引号配对()Dim aPara As Paragraph, b As BooleanDim regLQuote, regRQuote As RegExp, mLQuote, mRQuote As IMatchCollection2Set regLQuote = CreateObject("VBScript.RegExp")Set regRQuote = CreateObject("VBScript.RegExp")With regLQuote.Global = True.Pattern = "“"End WithWith regRQuote.Global = True.Pattern = "”"End WithFor Each aPara In Selection.Range.ParagraphsSet mLQuote = regLQuote.Execute(aPara.Range.Text)Set mRQuote = regRQuote.Execute(aPara.Range.Text)If mLQuote.Count <> mRQuote.Count Thenb = TrueaPara.Range.SelectExit ForEnd IfNextIf b ThenMsgBox "请检查选定段落中的引号配对错误"ElseMsgBox "本次选择范围内未发现引号配对错误"End If
End Sub
3、编辑文档的大纲结构
与直接编辑HTML相比,在Word中编辑文档的大纲结构十分方便。以处理《何逊集校注》为例,我们在Word中做如下图所示的处理(图中误将诗内容样式为“列表段落”输入为“标题段落”):
4、在每个标题3前面插入分节符,便于进一步遍历和处理文档。宏代码如下:
Sub 在符合条件的段落前插入分节符()Dim pos As Long, styleName$styleName = "标题 3"With Selection.HomeKey wdStory '光标回到文档开头,此时Selection.Start为0Dopos = .Start '先记录光标位置.GoTo wdGoToHeading, wdGoToNext, 1 '向后移动到下一个标题,以标题为对象遍历文档If .Start = pos Then Exit Do ' 光标位置不变则已遍历完所有标题,退出循环If .Paragraphs(1).Style = styleName Then.InsertBreak Type:=wdSectionBreakContinuous ' 连续型分节符End IfLoopEnd WithEnd Sub
5、利用文档的特点,将Word文件的文本内容改为HTML。
以如上的何逊集校注为例,可以先用下面的VBA代码插入符合epub3规范的弹出式注释相关HTML标签:
Sub 何逊集改为html字串()Dim regEx As RegExp, aSec As SectionDim matches As IMatchCollection2Dim aPara, prePara As Paragraph, b As Boolean, c%, i%Dim paraTxtRng, tmpRng As Range, chapter%, href$Set regEx = CreateObject("VBScript.RegExp")regEx.Global = TrueSet prePara = ActiveDocument.Paragraphs(1)For Each aSec In ActiveDocument.Sectionsi = 0 ' 校注内容的注释编号,每节恢复为0chapter = chapter + 1For Each aPara In aSec.Range.Paragraphs' 象《七召》這種將全文分成多個部分的,每分一部分将章节号增加1' 如果最终epub文件每首诗做成一个html文件,其实就无需考虑章节问题If prePara.Style = "正文" And aPara.Style = "列表段落" Theni = 0chapter = chapter + 1End Ifhref = "c" & Format(chapter, "000") & "_"If aPara.Style = "列表段落" Then ' 处理诗内容中的注释引用regEx.Pattern = "[\u2460-\u2473]" ' 带圈数字序号1~20Set tmpRng = aPara.RangeSet matches = regEx.Execute(aPara.Range.Text)For c = 0 To matches.count - 1With tmpRng.Find.Text = matches.Item(c).Wrap = wdFindContinue.ExecuteEnd With' 由于使用了循环变量作为输入的序号,所以校稿时无需考虑注释引用编号的正确性tmpRng.Text = "<a id=""ref_" & href & Trim(Str(c + 1)) & """ epub:type=""noteref"" href=""#" & _href & Trim(Str(c + 1)) & """><sup>" & "[" & Format(c + 1, "00") & "]" & "</sup></a>"Next cElseIf aPara.Style = "正文" ThenregEx.Pattern = "^[\u2460-\u2473]"' 检查正文段落开头是否是带圈数字序号,不是就不处理If regEx.test(aPara.Range.Text) Theni = i + 1' 注意修改段落内容时为避免副作用,应该将分段符排除在修改范围之外Set paraTxtRng = ActiveDocument.Range(aPara.Range.Start, aPara.Range.End - 1)' 这里的HTML标签内容主要考虑了符合epub3规范弹出式注释的编写要求paraTxtRng.Text = "<aside epub:type=""footnote"" id=""" & href & Trim(Str(i)) & _""" class=""comment""><a href=""#ref_" & href & Trim(Str(i)) & """>" & _"[" & Format(i, "00") & "]</a>" & Mid(aPara.Range.Text, 2, Len(aPara.Range.Text) - 2) & "</aside>"End IfEnd IfIf Len(aPara.Range.Text) > 1 Then Set prePara = aParaNext aParaNext aSecEnd Sub
再根据各段落的性质将各段落用相关HTML标签包裹:
Sub 为段落包裹html标签()Dim aPara As Paragraph, paraTxtRng As RangeFor Each aPara In ActiveDocument.ParagraphsIf Len(aPara.Range.Text) > 1 And Left(aPara.Range.Text, 1) <> "<" ThenSet paraTxtRng = ActiveDocument.Range(aPara.Range.Start, aPara.Range.End - 1)If aPara.Style = "标题 2" ThenparaTxtRng.Text = "<h2>" & paraTxtRng.Text & "</h2>"ElseIf aPara.Style = "标题 3" ThenparaTxtRng.Text = "<h3>" & paraTxtRng.Text & "</h3>"ElseIf aPara.Style = "标题 4" ThenparaTxtRng.Text = "<h4>" & paraTxtRng.Text & "</h4>"ElseIf aPara.Style = "列表段落" ThenparaTxtRng.Text = "<p class=""content"">" & paraTxtRng.Text & "</p>"ElseIf aPara.Style = "正文" ThenparaTxtRng.Text = "<p class=""normal"">" & paraTxtRng.Text & "</p>"End IfEnd IfNext
End Sub
6、将Word中处理好的内容复制粘贴到任何支持正则表达式的文本处理软件中,例如Emeditor,通过将“<h3>”替换为“</section>\n<section><h3>”,然后删除第一个“</section>”,在最后补上一个“</section>”,将每首诗连带其题解和校注内容全部包裹到一对“<section>”和“</section>”之中。将原来在Word文档中添加的“<br>”标签也替换为“</p><p class="content">”,使其达到真正的分段效果。处理完成后保存此文本文件,例如命名“何逊集校注.htm”。
7、使用下面的Python代码,将上一步处理的文本文件分解成每首诗一个符合epub3规范弹出式注释要求的HTML文件:
import osfrom bs4 import BeautifulSoup# HTML模板
html_template = """
<html xml:lang="zh-CN" xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
<head><meta content="text/html; charset=utf-8" http-equiv="Content-Type" /><title>Title</title><link href="styles.css" rel="stylesheet" />
</head>
<body>
</body>
</html>
"""
path = 'htmldemo'
orig_file = '何逊集校注.htm'with open(os.path.join(path, orig_file), 'r', encoding='utf-8') as f:content = f.read()soup_content = BeautifulSoup(content, 'html.parser')i = 0# 将内容添加到HTML模板的<body>中for element in soup_content.find_all('section'):# 创建BeautifulSoup对象soup_template = BeautifulSoup(html_template, 'html.parser')h3 = element.find('h3')title = h3.get_text()soup_template.title.string = titlesoup_template.body.append(element)# 在文件名前加上序号做前缀,便于排序i = i + 1with open(os.path.join(path, 'output', f'c{i:0>3}_{title}.html'), 'w', encoding='utf-8') as html:html.write(soup_template.prettify())
8、然后可以用sigil等epub文件创建工具利用分割的HTML文件制作epub了。但是对于程序员来说,不如利用ebooklib库编个Python程序来处理:
import os
from ebooklib import epubdef make_epub(input_folder, output_file):# 创建一个Epub书籍对象book = epub.EpubBook()# 添加书名、作者等信息book.set_identifier(output_file.split('.')[0]) # 使用输出文件名作为标识符book.set_title('何逊集校注')book.set_language('zh-CN')# 设置作者book.add_author("一只虎")# 添加一个导航项book.add_item(epub.EpubNcx())nav = book.add_item(epub.EpubNav())# 用于存储CSS和JS文件的路径css_files = []js_files = []# 准备css文件和js文件for root, dirs, files in os.walk(input_folder):for file in files:if file.endswith(".css"):file_path = os.path.join(root, file)with open(file_path, 'r', encoding='utf-8') as f:content = f.read()css = epub.EpubItem(uid=file, file_name=file, content=content, media_type='text/css')css_files.append(css)elif file.endswith(".js"):file_path = os.path.join(root, file)with open(file_path, 'r', encoding='utf-8') as f:content = f.read()js = epub.EpubItem(uid=file, file_name=file, content=content,media_type='application/javascript')js_files.append(css)# 存储文本文件chapter_files = []# 添加目录文件chapter_files.append(nav)# 遍历文件夹中的所有文件for root, dirs, files in os.walk(input_folder):for file in files:if file.endswith(".html"):file_path = os.path.join(root, file)chapter = epub.EpubHtml(title=file, file_name=file)with open(file_path, 'r', encoding='utf-8') as f:chapter.content = f.read()# 添加CSS和JS文件到书籍中for css in css_files:chapter.add_item(css)for js in js_files:chapter.add_item(js)# 文本文件加入章节book.add_item(chapter)chapter_files.append(chapter)# 添加目录项,可以将链接文本进行处理(这里删除了文件名前缀及文件扩展名)book.toc.append((epub.Link(file, file[5:-5]), file))# 设置书籍的spine(书脊),对于epub3.0规范来说不设置的话制作出的书籍无法阅读book.spine = chapter_files# 添加CSS和JS文件到书籍中for css in css_files:book.add_item(css)for js in js_files:book.add_item(js)# 假设封面图片在与html文件同一文件夹下,名为cover.jpgcover_image_path = os.path.join(input_folder, "cover.jpg")if os.path.exists(cover_image_path):with open(cover_image_path, 'rb') as f:cover_image = epub.EpubImage(uid="cover", file_name="cover.jpg", media_type="image/jpeg", content=f.read())book.set_cover("cover.jpg", cover_image.content)book.add_item(cover_image)# 保存书籍为EPUB文件epub.write_epub(output_file, book, {})if __name__ == '__main__':# 输入文件夹和输出文件名input_folder = 'htmldemo/output'output_file = 'htmldemo/何逊集校注.epub'# 调用函数生成EPUB文件make_epub(input_folder, output_file)
上面的代码包含了插入css、js和封面图片的内容,但是没有相关内容也不影响上面的程序成功执行。使用ebooklib要注意的是:
1)插入的css和js不但要添加到书籍中,还要添加到HTML文件中,否则样式表和js文件不会起作用。当然,添加到HTML文件中时ebooklib只是在相应的文件中插入一些<link>及<script>标签来引用相关文件。
2)必须让书籍的“spine”指向文本文件的列表,否则epub文件无法阅读,添加的目录文件也应该加入文本文件列表,否则目录文件无法阅读。