0%

记录扫描版epub转pdf(python实现)

最近在学习python,找了一本epub的电子书,但是打开之后发现,是一本扫描版的,一页一张图片的epub,看着很不舒服,扫描版的还是应该做成pdf,所以就研究了一下怎么手动把epub转成pdf。

认识epub

ePub(Electronic Publication的缩写,意为:电子出版),是一个自由的开放标准,属于一种可以“自动重新编排”的内容;也就是文字内容可以根据阅读设备的特性,以最适于阅读的方式显示。EPub档案内部使用了XHTML或DTBook (一种由DAISY Consortium提出的XML标准)来展现文字、并以zip压缩格式来包裹档案内容。(复制自百度百科)

通过上面我们可以知道,通过文件管理器或者解压工具可以打开epub格式的电子书,OEBPS/目录内就是电子书的内容,我们主要关心的就是text00000.html以及一堆乱序的图片。

首先打开text00000.html,打开之后可以在浏览器内看电子书,但是我们的目的不是这个,用文本编辑器打开应该能看见类似这样的代码

1
2
3
4
5
6
7
8
9
...
<span id="filepos0000000421"></span>
<p height="1em" width="0pt"><img align="baseline" height="2768" width="1944" src="Image00091.jpg" />
</p>
<p height="1em" width="0pt"><img align="baseline" height="2773" width="1948" src="Image00116.jpg" />
</p>
<p height="1em" width="0pt"><img align="baseline" height="2768" width="1944" src="Image00009.jpg" />
</p>
...

span标签的作用是作为目录的标记,img标签是按照顺序排列的图片,如果是常规的epub电子书,p标签内应该有书里面的文本。

主要流程

如果是我以前,我的流程应该是

  1. 手动把epub拆包
  2. 手动用vscode对html正则替换,只保留图片名
  3. 写个bash脚本把图片按html的顺序批量重命名
  4. 用imagemagick把图片合并成pdf。

但是现在在学python,而且通过大量的网络查资料,我觉得这些操作全部都可以用python自动化完成(库多就是好),所以我就写了这篇博客来记录,如何用python来将扫描版epub转换成pdf,顺便二值化处理(以前就想弄了,但是用C语言不会读取图片,读取图片我只会手搓bmp)。

需要用到的库有:

  • zipfile 用于读取epub文件
  • re 用于正则提取图片名
  • PIL 用于将图片序列保存为pdf,以及图片二值化,非自带需要通过pip install pillow安装

zip操作

这里只介绍需要用到的操作,更多的可以自己去网上查。

首先在开头导入库from zipfile import ZipFile,注意,我这里是只导入了ZipFile,用来打开zip文件,如果规范一点,在用ZipFile打开文件之前,应该用zipfile.is_zipfile(filename)来判断是不是zip文件,我这里偷懒了。

打开zip

ZipFile的用法跟打开普通文件的方法类似,使用的时候注意大小写。

1
2
3
4
5
6
7
8
9
10
11
#两种写法

#第一种(需要手动关闭)
file=ZipFile(filename)
......
file.close()

#第二种(不需要手动关闭)
with ZipFile(filename) as file:
......

打开之后可以使用ZipFile的两个方法,namelist获取压缩包内的文件名列表,infolist获取文件信息列表。

打开zip内的文件

ZipFile也有一个方法叫做open,用法跟普通的open类似。

1
2
3
with ZipFile(epub_name) as epub_file:
with epub_file.open("OEBPS/text00000.html") as file:
......

之后就可以像操作普通文件一样操作zip里的文件了。

正则表达式

正则表达式是一个很好用的东西,简单的很简单,复杂的基本上都不记得要去网上查,python里面使用正则表达式是通过re这个库,使用起来还是很简单的。

通过观察,我们需要提取的就是img标签内的src的值,所以我写的正则表达式是<img.*src=\"(.+)\" />,用来提取img的src的双引号内的文件名。

python内使用正则表达式首先需要编译

1
2
import re
pattern=re.compile("<img.*src=\"(.+)\" />")

之后就可以通过pattern的findall方法来提取字符串了。

1
2
3
4
with ZipFile(epub_name) as epub_file:
file_list=[]
with epub_file.open("OEBPS/text00000.html") as file:
file_list=pattern.findall(file.read().decode("utf-8"))

这样就将图片名按顺序保存到file_list里了。

二值化处理

二值化就是让图片只保留两种颜色,可以让扫描版的图片更加清晰,同时还能缩小图片体积(从32位色彩变成了1位色彩)。

首先,封面我们还是留着颜色,这样好看点,我们就假定第一张图片就是封面,先将第一张图片从列表里拆出来。

1
file_0,*file_list=file_list

现在file_list里的就是我们需要二值化的图片了,二值化的步骤就是把图片转化成灰度图,之后设定一个阈值,小于这个值的就是纯黑,大于这个值的就是纯白,先上代码:

1
2
3
4
5
6
7
8
9
10
threshold=200
gray_lut=[int(i>=threshold) for i in range(256)]

img_list=[]
for filename in file_list:
with epub_file.open("OEBPS/"+filename) as file:
img=Image.open(file)
img=img.convert('L')
img=img.point(gray_lut,'1')
img_list.append(img)

convert('L')是将图片转换成灰度图,也可以转换成其他颜色模式,有需要自行查找。

point方法就很有意思了,是逐像素映射,感觉可以做很多有意思的事情,第一个参数就是颜色映射,第二个参数是颜色模式,’1’就是只有黑白了。

之后将处理后的图片添加到img_list里,就是我们pdf的图片序列了。

保存为pdf

不要忘了封面,保存就一行的事,直接上代码了。

1
2
3
with epub_file.open("OEBPS/"+file_0) as file:
cover=Image.open(file)
cover.save(pdf_name,"PDF",save_all=True,append_images=img_list)

最终完成版

为了使用方便,我还写了个控制台传参,这个看个人喜好,不是重点,不过多说明。注意,因为我这里只有灰度阈值一个参数,所以我只判断了”–”开头,如果有多个参数,建议用字典来保存参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from zipfile import ZipFile
from PIL import Image
import re
import sys
import os

epub_name=None
pattern=re.compile("<img.*src=\"(.+)\" />")
threshold=200

arg_list=sys.argv[1:]
for arg in arg_list:
str=re.sub("'",'"',arg)
if str.startswith("--"):
threshold=int(str.split('=')[1])
elif os.path.exists(str):
epub_name=str
else:
print("error: "+str+": No such file or directory.")
exit()

if epub_name == None:
print("fatal error: no input files.")
exit()

gray_lut=[int(i>=threshold) for i in range(256)]
pdf_name=epub_name.rsplit('.',1)[0]+".pdf"

with ZipFile(epub_name,"r") as epub_file:
file_list=[]
with epub_file.open("OEBPS/text00000.html") as file:
file_list=pattern.findall(file.read().decode("utf-8"))

file_0,*file_list=file_list

img_list=[]
for filename in file_list:
with epub_file.open("OEBPS/"+filename) as file:
img=Image.open(file)
img=img.convert('L')
img=img.point(gray_lut,'1')
img_list.append(img)

with epub_file.open("OEBPS/"+file_0) as file:
cover=Image.open(file)
cover.save(pdf_name,"PDF",save_all=True,append_images=img_list)

之后可以用pyinstaller把py脚本打包成exe,这样使用就更方便一点,在windows下面直接将epub文件拖到exe文件上就可以使用了。按我自己使用,原本73MB的epub电子书转换之后只有7MB,只有原来的百分之十,而且也清晰了,效果还是很好的。

感想

这次算是我学了python之后的第一次实战,给我的感觉还是很不错的,虽然大多是在调库,但是确实做到了我用C语言很难做到的事情,用C语言我一不会找库,二如果解析zip、jpg、pdf这些我全部学习怎么写,那一定会花很多时间,而且到最后估计也就是在复制代码,所以,人生苦短,我用python

也挺羡慕现在的小孩,好像听说小学就学python了,如果我小学的时候就学python,现在可能早就实现所有事情自动化了,我是个很懒的人,在学会编程之后,什么都想着能不能让程序做,从解题到用电脑处理文件,虽然很多时候能力受限,都只是停留在想。但是python帮我们解决了很多能力上的问题,我们只要敢想敢做就行了。