EPub格式电子书的制作

最近在写的一个项目涉及到epub格式电子书的制作,借这个机会总结一下epub这个电子图书标准,并利用Python语言生成一本简单的epub格式电子书。

EPub的前世今生

为什么会有EPub

我曾说过,电子书的阅读越来越流行是未来阅读发展的不可避免的趋势。纸质书籍是否会在历史长河中消失我们无从知晓,但可以确定的是,数字化阅读在未来至少十年中,会润物细无声般成为更多人的一种生活方式。

或许很多人都没有察觉到,我们这一代经历的正是一场关于人类获取信息,生产内容方式的巨变。从因特网诞生,电子邮件、超链接、富文本的广泛使用,再到现在所谓的"互联网2.0",人们从“下载者”转变为“上传者”,这场转变的发展也不过是数十年而已,说到这,想起一张著名的图片:

比尔盖茨拿着光盘

这张光盘能装下的信息比下面所有纸能记录下的都多

--比尔盖茨,1994

随着技术的发展,人们开始不满足于简单的文本书籍,于是富文本格式开始出现(就网页浏览来说,可以理解为HTML是骨架,CSS是皮肤,JavaScript是动作,Wold当然也算,但不够开放通用),这不仅仅是表现形式的变化,交互性也开始展现了。在这个过程中,EPub作为一种自由的电子书开放标准,自然而然地孕育而生,也自然而然地进化着。
我们为什么需要EPub?我想一篇文章中的一段比我说的更好:

EPUB enables content to be created by an author or publisher once, via different tools and services, distributed through many channels, and viewed, online or offline, using many different devices and applications. The EPUB specifications form a kind of “contract” between content creators and reading systems to enable this interoperability.

来自epubzone上的一篇文章

所以,EPub是什么

简单来说, EPub格式是一种电子书的标准,事实上几乎成为了行业标准,注意观察的话,几乎所有的电子书阅读器,从硬件到软件,都支持EPub格式的电子书(Kindle是一朵奇葩,原生系统不支持EPub,因为它要推自己的Mobi格式)。更具体的内容可以查wikipedia-EPUB, 这里说个好玩的吧,EPub格式电子书采用zip压缩格式来包裹书籍内容以及格式控制的文件(因为遵循IDPF推出的OCF规范,而OCF规范遵循ZIP压缩技术),所以我们可以把.epub改成.zip,然后解压缩,直接阅读书籍的内容,这样一来,在PC | Mac上,没有EPub阅读器,照样可以打开EPub阅读。

怎么构建一本EPub格式的电子书

上面提到,EPub格式的电子书其实是一个压缩包文件,里面有几个按照规范定义的文件,所谓标准,就是规范EPub文件中某些文件的格式、内容和位置等等。因此,如果我们想要自己制作一本EPub格式的电子书,首先要了解要制作的内容压缩为zip文件前的文件结构是什么,一个典型的EPub的文件结构是这样的:

IBM epub

其实结构可以更简单,下面给出我用Python语言构建的EPub文件的文件结构:


my epub

对比一下可以看出来有些文件并不是必须的,下面简单介绍一下EPub文件的目录结构:

mimetype文件

这个内容是固定的,就一行
application/epub+zip

表明可以被EPub工具打开或zip工具打开

META-INF文件夹

根据OCF(Open Container Format)标准,该文件夹包含一个文件container.xml,内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles> 
    <rootfile full-path="OPS/content.opf" media-type="application/oebps-package+xml"/> </rootfiles>
</container>

它的功能是告诉阅读器电子书根文件路径以及打开方式,如果你修改了content.opf的名字或者把它放在其他位置,应该写明完整的路径。

OEBPS文件夹

OEBPS目录用于存放OPS文档、OPF文档、CSS文档、NCX文档, OEBPS这个名字是可变的,可以根据containter.xml进行配置。这里是OPS文件夹。

opf文件:

content.opf文件的内容:

<?xml version="1.0" encoding="UTF-8" ?>
<package version="2.0" unique-identifier="PrimaryID" xmlns="http://www.idpf.org/2007/opf">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
<dc:title>thisisbooktitle</dc:title>
<dc:creator>frank</dc:creator>
<dc:description>this is description</dc:description>
<meta name="cover" content="cover"/>
</metadata>
<manifest>
<item id='chapter1.html' href='chapter1.html' media-type='application/xhtml+xml'/>
<item id='chapter2.html' href='chapter2.html' media-type='application/xhtml+xml'/>
<item id="ncx" href="content.ncx" media-type="application/x-dtbncx+xml"/>
<item id="cover" href="cover.jpg" media-type="image/jpeg"/>
</manifest>
<spine toc="ncx">
<itemref idref='chapter1.html'/>
<itemref idref='chapter2.html'/>
</spine>
</package>

这是一个标准的XML文件,遵循OPF规范,主要属性有:

  • metadata
    包括dc-metadata和x-metadata,dc-metadata有:
<title>:题名
<creator>:责任者
<subject>:主题词或关键词
<description>:内容描述
<contributor>:贡献者或其它次要责任者
<date>:日期
<type>:类型
<format>:格式
<identifier>:标识符
<source>:来源
<language>:语种
<relation>:相关信息
<coverage>:履盖范围
<rights>:权限描述

如果是未知属性可以用x-metadata描述

  • menifest
    文件列表, 列出OEBPS文档及相关的文档,由一个子元素构成,<item id="" href="" media-type="">,该元素由三个属性构成:
id:表示文件的ID号
href:文件的相对路径
media-type:文件的媒体类型
  • spine toc="ncx"
    表明书籍的阅读次序,其中有一个元素itemref idref="",idref是menifest中的id
  • opf还有很多其他属性,实际中用的并不多,即使用到也是一目了然的,如有需要可以连猜带蒙+搜索引擎。
ncx文件

content.ncx文件的内容:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
<ncx version="2005-1" xmlns="http://www.daisy.org/z3986/2005/ncx/">
<head>
  <meta name="dtb:uid" content=" "/>
  <meta name="dtb:depth" content="-1"/>
  <meta name="dtb:totalPageCount" content="0"/>
  <meta name="dtb:maxPageNumber" content="0"/>
</head>
 <docTitle><text>thisisbooktitle</text></docTitle>
 <docAuthor><text>frank</text></docAuthor>
<navMap>
<navPoint id='chapter1.html' class='level1' playOrder='1'>
<navLabel> <text>chapter1.html</text> </navLabel>
<content src='chapter1.html'/></navPoint>
<navPoint id='chapter2.html' class='level1' playOrder='2'>
<navLabel> <text>chapter2.html</text> </navLabel>
<content src='chapter2.html'/></navPoint>
</navMap>
</ncx>

该文件的作用是描述电子书的目录结构,这里的content.ncx文件并没有很明显的体现。有兴趣的话可以解压一本EPub格式的电子书看一看。

最后,给出利用Python制作简单EPub文件的代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import shutil

title = 'thisisbooktitle'
creator = 'frank'
description = 'this is description'

htmllist = ['chapter1.html', 'chapter2.html']    # 来自《亲爱的安德烈》中的两章

os.mkdir('tmp')

tmpfile = file('tmp/mimetype', 'w')
tmpfile.write('application/epub+zip')
tmpfile.close()

os.mkdir('tmp/META-INF')

tmpfile = file('tmp/META-INF/container.xml', 'w')
tmpfile.write('''<?xml version="1.0" encoding="UTF-8" ?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
   <rootfiles> <rootfile full-path="OPS/content.opf" media-type="application/oebps-package+xml"/> </rootfiles>
</container>
''')
tmpfile.close()

os.mkdir('tmp/OPS')

if os.path.isfile('cover.jpg'):     # 如果有cover.jpg, 用来制作封面
    shutil.copyfile('cover.jpg', 'tmp/OPS/cover.jpg')
    print 'Cover.jpg found!'

opfcontent = '''<?xml version="1.0" encoding="UTF-8" ?>
<package version="2.0" unique-identifier="PrimaryID" xmlns="http://www.idpf.org/2007/opf">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
%(metadata)s
<meta name="cover" content="cover"/>
</metadata>
<manifest>
%(manifest)s
<item id="ncx" href="content.ncx" media-type="application/x-dtbncx+xml"/>
<item id="cover" href="cover.jpg" media-type="image/jpeg"/>
</manifest>
<spine toc="ncx">
%(ncx)s
</spine>
</package>
'''

dc = '<dc:%(name)s>%(value)s</dc:%(name)s>'
item = "<item id='%(id)s' href='%(url)s' media-type='application/xhtml+xml'/>"
itemref = "<itemref idref='%(id)s'/>"

metadata = '\n'.join([
        dc % {'name': 'title', 'value': title},
        dc % {'name': 'creator', 'value': creator},
        dc % {'name': 'description', 'value': description},
        ])

manifest = []
ncx = []

for htmlitem in htmllist:
    content = file(htmlitem, 'r').read()
    tmpfile = file('tmp/OPS/%s' % htmlitem, 'w')
    tmpfile.write(content)
    tmpfile.close()
    manifest.append(item % {'id': htmlitem, 'url': htmlitem})
    ncx.append(itemref % {'id': htmlitem})

manifest='\n'.join(manifest)
ncx='\n'.join(ncx)

tmpfile = file('tmp/OPS/content.opf', 'w')
tmpfile.write(opfcontent %{'metadata': metadata, 'manifest': manifest, 'ncx': ncx,})
tmpfile.close()

ncx = '''<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
<ncx version="2005-1" xmlns="http://www.daisy.org/z3986/2005/ncx/">
<head>
  <meta name="dtb:uid" content=" "/>
  <meta name="dtb:depth" content="-1"/>
  <meta name="dtb:totalPageCount" content="0"/>
  <meta name="dtb:maxPageNumber" content="0"/>
</head>
 <docTitle><text>%(title)s</text></docTitle>
 <docAuthor><text>%(creator)s</text></docAuthor>
<navMap>
%(navpoints)s
</navMap>
</ncx>
'''

navpoint = '''<navPoint id='%s' class='level1' playOrder='%d'>
<navLabel> <text>%s</text> </navLabel>
<content src='%s'/></navPoint>'''

navpoints = []
for i, htmlitem in enumerate(htmllist):
    navpoints.append(navpoint % (htmlitem, i+1, htmlitem, htmlitem))

tmpfile = file('tmp/OPS/content.ncx', 'w')
tmpfile.write(ncx % {
    'title': title,
    'creator': creator,
    'navpoints': '\n'.join(navpoints)})
tmpfile.close()

from zipfile import ZipFile
epubfile = ZipFile('book.epub', 'w')
os.chdir('tmp')
for d, ds, fs in os.walk('.'):
    for f in fs:
        epubfile.write(os.path.join(d, f))
epubfile.close()

shutil.rmtree("../tmp")

print ("Done")

说明:chapter1.html, chapter2.html 是我从“亲爱的安德烈.epub”中提取的两章,你也可以替换成其他的内容,若上述python代码存为simple_epub.py,将chapter1.htmlchapter2.html, simple_epub.py放在同一目录下, 通过python simple_epub.py 即可生成book.epub文件。

参考资料:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 这里是与 Kindle 电子书相关的工具软件。它们可以帮助我们解决在日常使用电子书时所可能遇到的问题,比如 kin...
    JosephDHF阅读 7,466评论 1 45
  • test
    阿曦阅读 284评论 0 1
  • 一九三七年 一个温和的夜晚 月光如牛奶 温柔地在山间浮沉 古寨里 风儿无声地飞 小河懒懒地流 一切都是祥和的模样 ...
    白童阅读 317评论 1 6
  • 【本文内容见S05E01】 【多图预警,使用流量时请慎点】 摩登家庭追到第五季,我一颗担心后继乏力的心终于放下。第...
    有个爱吾浅蓝阅读 719评论 1 1