⽣成Kindle可读的mobi和PDF电⼦书
购买kindle之后,⾃然欣喜万分,不来⾃于⼯具本⾝,⽽来⾃于发现⾃⼰能够静下⼼来阅读长篇和复杂的⽂字了,可喜可贺。更重要的
是,kindle减轻了我眼睛的莫⼤的压⼒。但马上就出现⼏个问题
万圣节南瓜灯的做法
不是所有的电⼦书都有kindle,最常见的是扫描PDF
⼤量的论⽂⽆法阅读,这和上⾯的问题⼀致
⽹络上很多精彩的博客,新闻,都是没法阅读的
可能有⼈说,⽤⼿机看不就得了?⽤⼿机看花边娱乐新闻当然很好,可是当看数学推导时,推送栏上⾯妹⼦发来的消息,会直接把你的思路全部打乱。没⽤过kindle的⼈,是有些难以体会那种接近于纸张的质感的。OK,既然是程序员,我们就尝试解决这些问题。
有关kindlegen和HTML
kindlegen是亚马逊官⽅出品的⼀个电⼦书⽣成⼯具。但它明显就没打算让普通⽤户使⽤,命令⾏界⾯,
⼏乎没有任何像样的⽂档。只是在实例样例⾥给了⼏个⽣成电⼦书的⽂件。我就因为没有⽂档兜了⼤弯,翻遍国外各⼤⽹站,才慢慢摸清kindlegen的使⽤细节。
可以这么理解,KG是将⼀组HTML和相关⽂件,打包成mobi⽂件的⼯具。
最简单的例⼦,随意编写⼀个HTML⽂件,送给KG,会⽣成对应的mobi。基本有title,h1,h2,正⽂,kindle渲染就差不多了。如果需要修改样式,可以提供CSS⽂件。
但是,这样的做法,没有图⽚,没有超链接,⽆法提供⽬录,如果输⼊单⼀的⼤型HTML⽂件,kindle的渲染性能就不⾜了。
因此,需要⽣成层级化,多⽂件形式的html⽂件夹,然⽽kg并不能直接识别html⽂件夹,还是需要⼀些元数据描述。
编写元数据⽂件
要想解决这个问题,就需要编写两个⽂件,opf和ncx, 他们可以理解为KG的makefile, KG通过这两个⽂件索引HTML,⽬录和其他多媒体资源。介绍如下:
值得注意的是,所有的⽂件都应该保存在本地,尤其是jpg, html中的图⽚超链接,需要重定向到本地的jpg⽂件,如果依然在服务器上,据我所知,kg是不负责渲染下载的。
资源聚合⽂件: opf和ncx
由于opf⽂件⾮常重要,我们下⾯就讲解opf的格式:
<package xmlns="/2007/opf" version="2.0" unique-identifier="BookId">
<metadata xmlns:dc="/dc/elements/1.1/" xmlns:opf="/2007/opf">
涂经纬老公<dc:title>电⼦书标题</dc:title>
马诺门照艳<dc:language>en-us</dc:language>
</metadata>
<manifest>
<!-- table of contents [mandatory] -->
<item id="tochtml" media-type="application/xhtml+xml" href="toc.html"/>
<item id="item0" media-type="application/xhtml+xml" href="Artical-1277621753.html"/>
.
..
<!--下⾯是图⽚-->
<item id="0.368541311142" media-type="image/jpg" href="Images/-1720404282.jpg"/>
</manifest>
<spine toc="desertfire">
<!-- 下⾯描述了KG⽣成电⼦书后⽂本的顺序 -->
<itemref idref="toc"/>
<itemref idref="tochtml"/>
<itemref idref="item31"/>
</spine>
<guide>
<reference type="toc" title="Table of Contents" href="toc.html"></reference>
<reference type="text" title="Welcome" href="toc.html"></reference>
</guide>
</package>
```
需要注意的有以下⼏点:
所有资源都需要⼀个id,命名任意,但不能重复
media-type描述了资源的类型,记住两类基本就够⽤了,"application/xhtml+xml"代表HTML⽂件,"image/jpg"或 "image/png"代表图⽚。
其他都可以省略,只是会影响电⼦书完整性。
由于这两个⽂件内部其实都是html,所以修改编辑都很容易。
最终,KG的命令⾏⽬标,不是⽬录HTML,⽽是OPF⽂件!将所有的⽂件放⼊⼀个⽂件夹后,启动KG命令⾏,最后KG会在该⽬录下⽣成你⼼仪已久的mobi!
编辑HTML和OPF⽂件
知道其原理后,主要的任务是填充HTML和OPF⽂件,⼏页内容还好,如果内容繁多,不论是⼿⼯( ⊙ o ⊙ ),还是编程字符串拼接,都会变得异常低效。
此时,就需要模板引擎出⼿了,python推荐使⽤Jinja2, 资料众多,功能强⼤,性能尚可。⽣成opf的模板⽂件,基本就长下⾯这个样⼦:
<package xmlns="/2007/opf" version="2.0" unique-identifier="BookId">
pdf电子书制作<metadata xmlns:dc="/dc/elements/1.1/" xmlns:opf="/2007/opf">
<dc:title>{{ title }}</dc:title>
<dc:language>en-us</dc:language>
</metadata>
<manifest>
<!-- table of contents [mandatory] -->
<item id="toc" media-type="application/x-dtbncx+xml" href=""/>
<item id="tochtml" media-type="application/xhtml+xml" href="toc.html"/>
{% for item in navigation %}
<item id="{{ item.id }}" media-type="application/xhtml+xml" href="{{ item.href }}"/>
{% endfor %}
{% for item in media %}
<item id="{{ item.id }}" media-type="image/{{ item.format}}" href="{{ item.href}}"/>
{% endfor %}
任天野个人资料</manifest>
<spine toc="{{ title }}">
<!-- the spine defines the linear reading order of the book -->
<itemref idref="toc"/>
<itemref idref="tochtml"/>
{% for item in navigation %}
<itemref idref="{{ item.id }}"/>
{% endfor %}
</spine>
<guide>
<reference type="toc" title="Table of Contents" href="toc.html"></reference>
<reference type="text" title="Welcome" href="toc.html"></reference>
</guide>
</package>
我在此处就不费事讲解jinja2的语法了。这样,就能解决阅读⽹页新闻和HTML资源的问题了。
⽣成扫描版MOBI
下⼀个问题,是如何阅读扫描版的PDF,如电⼦书和论⽂。有以下⼏类初始想法:
权衡之后,我们选⽤第⼆种⽅案。PDF分为两类,⼀种是⼀页⼀栏,如电⼦书,另⼀种是⼀页两栏,如论⽂。
那么,为了保证质量,有以下的步骤:
将PDF转换为图⽚
如果使⽤python,则有⼀些类库可以使⽤,如imagemagick和⼀系列相关类库。
但这些类库安装⽐较⿇烦,因此笔者使⽤了软件⽣成,此处强烈推荐⼀款软件:
AP PDF to IMAGE 国产软件?的骄傲!不需要其他任何类库,体积⼩,性能稳定,⽣成图⽚尺⼨可调,可批量处理,⾮常清晰!
百度可搜索各类绿⾊版下载,我都想给作者⽀付宝捐钱了。
图⽚处理
如果你是PS⼤神,当然可以使⽤宏和批量命令完成这些,此处我们⽤的还是python,使⽤著名的PIL类库,下⾯贴出代码:
# coding=utf-8
import os
import Image as img
import jinja2 as jj
import extends
import libs.kindlestrip as kp
# 要PDF转JPG时,如果⽤python的⽅案,则需要安装⼀堆库
# ⽤现成的⼯具,则难以与Python集成,⽽且速度很慢,⽬前还是采⽤现成的⼯具吧
# 当⽣成论⽂时,第⼀页的上半部分,单独抽出,剩下的分为四页导出。设置如下
horizon = 2
vertic = 2
firstpage = True
# ⽣成普通横版PDF时,则为如下设置:
# horizon = 1
# vertic  = 2
# firstpage=False
topblood = 0.05;
sideblood = 0.06;
booktitle = u"Paper";
author = "zhaoyiming"
outputfolder = "pdf2mobi/";
imgTypes = ['.png', '.jpg', '.bmp']
kindlegen = r""# kindlegen position
shouldsplit = True;
imagefolders = outputfolder + 'raw';
splitfolder = outputfolder + 'split'
docs = [];
pageindex = 0;
if shouldsplit == True:
for root, dirs, files in os.walk(imagefolders):
index = 0;
for currentFile in files:
crtFile = root + '\\' + currentFile
format = crtFile[crtFile.rindex('.'):].lower();
if format not in imgTypes:
continue;
crtIm = img.open(crtFile)
crtW, crtH = crtIm.size
hStep = crtW * (1 - 2 * sideblood) // horizon
vStep = crtH * (1 - 2 * topblood) // vertic
hstart = crtW * sideblood
vstart = crtH * topblood;
if (firstpage == True and pageindex == 0):
crtOutFileName = 'pdf2mobi/split/' + str(index) + format,
box = (hstart, vstart, crtW, crtH // 3)
box = list((int(x) for x in box));
cropped = p(box)
cropped.save(crtOutFileName[0])
myimg = {};
myimg["href"] = "split/" + str(index) + format;
myimg["id"] = index;
myimg["format"] = format;
myimg["width"] = box[2] - box[0];
myimg["height"] = box[3] - box[1];
docs.append(myimg)
index += 1;
for j in range(horizon):
for i in range(vertic):
crtOutFileName = 'pdf2mobi/split/' + str(index) + format,
box = (hstart + j * hStep, vstart + i * vStep, hstart + (j + 1) * hStep, vstart + (i + 1) * vStep)                    box = (int(x) for x in box);
cropped = p(box)
cropped.save(crtOutFileName[0])
myimg = {};
myimg["href"] = "split/" + str(index) + format;
myimg["id"] = index;
myimg["format"] = format;
myimg["width"] = hStep;
myimg["height"] = vStep;
docs.append(myimg)
index += 1;
pageindex += 1;
else:
for root, dirs, files in os.walk(imagefolders):
index = 0;
for currentFile in files:
crtFile = root + '\\' + currentFile
format = crtFile[crtFile.rindex('.'):].lower();
if format not in imgTypes:
continue;
myimg = {};
myimg["href"] = "split/" + str(index) + format;
myimg["id"] = index;
myimg["format"] = format;
myimg["width"] = "1347";
myimg["height"] = "1023";
docs.append(myimg)
index += 1;
images = [];
env = jj.Environment(loader=jj.FileSystemLoader([r"templates/"]))
articaltemplate = _template('jpgs.html')
opftemplate = _template('opf.html')
ncxtemplate = _template('ncx.html')
extends.SaveFile(outputfolder + "toc.html", der(navigation=docs, title=booktitle, author=author)); extends.SaveFile(outputfolder + booktitle + ".opf",
兔六extends.SaveFile(outputfolder + "", der(navigation=docs, title=booktitle, author=author)); currentPath = os.getcwd() + "\\" + place("/", "\\") + booktitle + ".opf";
mobipath = place("/", "\\") + booktitle + ".mobi";
kindlepath = os.getcwd() + "\\" + place("/", "\\");
cmd = kindlepath + "" + currentPath;
cmd = de();
print cmd;
os.system(cmd);
kp.Convert(mobipath, mobipath)
(我觉得我应该把代码上传到github上,恩,⼀会再说)
这样,就能⽣成可读的漂亮的PDF转mobi了。
最终效果
这些代码花了我⼀个下午的时间,不过与爬⾍配合,⽣成各位⼤神的博客,效果真是⾮常赞!妈妈再也不⽤担⼼我的眼睛了!终于可以随时随地,没有⼴告地批量看⼤神们的博客了!
有任何问题,欢迎随时讨论。