最近在寫的一個(gè)項(xiàng)目涉及到epub格式電子書的制作,借這個(gè)機(jī)會(huì)總結(jié)一下epub這個(gè)電子圖書標(biāo)準(zhǔn),并利用Python語言生成一本簡單的epub格式電子書。
EPub的前世今生
為什么會(huì)有EPub
我曾說過,電子書的閱讀越來越流行是未來閱讀發(fā)展的不可避免的趨勢。紙質(zhì)書籍是否會(huì)在歷史長河中消失我們無從知曉,但可以確定的是,數(shù)字化閱讀在未來至少十年中,會(huì)潤物細(xì)無聲般成為更多人的一種生活方式。
或許很多人都沒有察覺到,我們這一代經(jīng)歷的正是一場關(guān)于人類獲取信息,生產(chǎn)內(nèi)容方式的巨變。從因特網(wǎng)誕生,電子郵件、超鏈接、富文本的廣泛使用,再到現(xiàn)在所謂的"互聯(lián)網(wǎng)2.0",人們從“下載者”轉(zhuǎn)變?yōu)椤吧蟼髡摺保@場轉(zhuǎn)變的發(fā)展也不過是數(shù)十年而已,說到這,想起一張著名的圖片:
這張光盤能裝下的信息比下面所有紙能記錄下的都多
--比爾蓋茨,1994
隨著技術(shù)的發(fā)展,人們開始不滿足于簡單的文本書籍,于是富文本格式開始出現(xiàn)(就網(wǎng)頁瀏覽來說,可以理解為HTML是骨架,CSS是皮膚,JavaScript是動(dòng)作,Wold當(dāng)然也算,但不夠開放通用),這不僅僅是表現(xiàn)形式的變化,交互性也開始展現(xiàn)了。在這個(gè)過程中,EPub作為一種自由的電子書開放標(biāo)準(zhǔn),自然而然地孕育而生,也自然而然地進(jìn)化著。
我們?yōu)槭裁葱枰狤Pub?我想一篇文章中的一段比我說的更好:
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格式是一種電子書的標(biāo)準(zhǔn),事實(shí)上幾乎成為了行業(yè)標(biāo)準(zhǔn),注意觀察的話,幾乎所有的電子書閱讀器,從硬件到軟件,都支持EPub格式的電子書(Kindle是一朵奇葩,原生系統(tǒng)不支持EPub,因?yàn)樗谱约旱腗obi格式)。更具體的內(nèi)容可以查wikipedia-EPUB, 這里說個(gè)好玩的吧,EPub格式電子書采用zip壓縮格式來包裹書籍內(nèi)容以及格式控制的文件(因?yàn)樽裱璉DPF推出的OCF規(guī)范,而OCF規(guī)范遵循ZIP壓縮技術(shù)),所以我們可以把.epub改成.zip,然后解壓縮,直接閱讀書籍的內(nèi)容,這樣一來,在PC | Mac上,沒有EPub閱讀器,照樣可以打開EPub閱讀。
怎么構(gòu)建一本EPub格式的電子書
上面提到,EPub格式的電子書其實(shí)是一個(gè)壓縮包文件,里面有幾個(gè)按照規(guī)范定義的文件,所謂標(biāo)準(zhǔn),就是規(guī)范EPub文件中某些文件的格式、內(nèi)容和位置等等。因此,如果我們想要自己制作一本EPub格式的電子書,首先要了解要制作的內(nèi)容壓縮為zip文件前的文件結(jié)構(gòu)是什么,一個(gè)典型的EPub的文件結(jié)構(gòu)是這樣的:

其實(shí)結(jié)構(gòu)可以更簡單,下面給出我用Python語言構(gòu)建的EPub文件的文件結(jié)構(gòu):

對(duì)比一下可以看出來有些文件并不是必須的,下面簡單介紹一下EPub文件的目錄結(jié)構(gòu):
mimetype文件
這個(gè)內(nèi)容是固定的,就一行
application/epub+zip
表明可以被EPub工具打開或zip工具打開
META-INF文件夾
根據(jù)OCF(Open Container Format)標(biāo)準(zhǔn),該文件夾包含一個(gè)文件container.xml,內(nèi)容如下:
<?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的名字或者把它放在其他位置,應(yīng)該寫明完整的路徑。
OEBPS文件夾
OEBPS目錄用于存放OPS文檔、OPF文檔、CSS文檔、NCX文檔, OEBPS這個(gè)名字是可變的,可以根據(jù)containter.xml進(jìn)行配置。這里是OPS文件夾。
opf文件:
content.opf文件的內(nèi)容:
<?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>
這是一個(gè)標(biāo)準(zhǔn)的XML文件,遵循OPF規(guī)范,主要屬性有:
- metadata
包括dc-metadata和x-metadata,dc-metadata有:
<title>:題名
<creator>:責(zé)任者
<subject>:主題詞或關(guān)鍵詞
<description>:內(nèi)容描述
<contributor>:貢獻(xiàn)者或其它次要責(zé)任者
<date>:日期
<type>:類型
<format>:格式
<identifier>:標(biāo)識(shí)符
<source>:來源
<language>:語種
<relation>:相關(guān)信息
<coverage>:履蓋范圍
<rights>:權(quán)限描述
如果是未知屬性可以用x-metadata描述
- menifest
文件列表, 列出OEBPS文檔及相關(guān)的文檔,由一個(gè)子元素構(gòu)成,<item id="" href="" media-type="">,該元素由三個(gè)屬性構(gòu)成:
id:表示文件的ID號(hào)
href:文件的相對(duì)路徑
media-type:文件的媒體類型
- spine toc="ncx"
表明書籍的閱讀次序,其中有一個(gè)元素itemref idref="",idref是menifest中的id - opf還有很多其他屬性,實(shí)際中用的并不多,即使用到也是一目了然的,如有需要可以連猜帶蒙+搜索引擎。
ncx文件
content.ncx文件的內(nèi)容:
<?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>
該文件的作用是描述電子書的目錄結(jié)構(gòu),這里的content.ncx文件并沒有很明顯的體現(xiàn)。有興趣的話可以解壓一本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”中提取的兩章,你也可以替換成其他的內(nèi)容,若上述python代碼存為simple_epub.py,將chapter1.html,chapter2.html, simple_epub.py放在同一目錄下, 通過python simple_epub.py 即可生成book.epub文件。
參考資料: