最近在寫(xiě)的一個(gè)項(xiàng)目涉及到epub格式電子書(shū)的制作软瞎,借這個(gè)機(jī)會(huì)總結(jié)一下epub這個(gè)電子圖書(shū)標(biāo)準(zhǔn),并利用Python語(yǔ)言生成一本簡(jiǎn)單的epub格式電子書(shū)拉讯。
EPub的前世今生
為什么會(huì)有EPub
我曾說(shuō)過(guò)涤浇,電子書(shū)的閱讀越來(lái)越流行是未來(lái)閱讀發(fā)展的不可避免的趨勢(shì)。紙質(zhì)書(shū)籍是否會(huì)在歷史長(zhǎng)河中消失我們無(wú)從知曉魔慷,但可以確定的是只锭,數(shù)字化閱讀在未來(lái)至少十年中,會(huì)潤(rùn)物細(xì)無(wú)聲般成為更多人的一種生活方式院尔。
或許很多人都沒(méi)有察覺(jué)到蜻展,我們這一代經(jīng)歷的正是一場(chǎng)關(guān)于人類獲取信息,生產(chǎn)內(nèi)容方式的巨變邀摆。從因特網(wǎng)誕生纵顾,電子郵件、超鏈接栋盹、富文本的廣泛使用施逾,再到現(xiàn)在所謂的"互聯(lián)網(wǎng)2.0",人們從“下載者”轉(zhuǎn)變?yōu)椤吧蟼髡摺保@場(chǎng)轉(zhuǎn)變的發(fā)展也不過(guò)是數(shù)十年而已汉额,說(shuō)到這曹仗,想起一張著名的圖片:
這張光盤(pán)能裝下的信息比下面所有紙能記錄下的都多
--比爾蓋茨,1994
隨著技術(shù)的發(fā)展蠕搜,人們開(kāi)始不滿足于簡(jiǎn)單的文本書(shū)籍怎茫,于是富文本格式開(kāi)始出現(xiàn)(就網(wǎng)頁(yè)瀏覽來(lái)說(shuō),可以理解為HTML是骨架妓灌,CSS是皮膚遭居,JavaScript是動(dòng)作,Wold當(dāng)然也算旬渠,但不夠開(kāi)放通用)俱萍,這不僅僅是表現(xiàn)形式的變化,交互性也開(kāi)始展現(xiàn)了告丢。在這個(gè)過(guò)程中枪蘑,EPub作為一種自由的電子書(shū)開(kāi)放標(biāo)準(zhǔn),自然而然地孕育而生岖免,也自然而然地進(jìn)化著岳颇。
我們?yōu)槭裁葱枰狤Pub?我想一篇文章中的一段比我說(shuō)的更好:
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.
來(lái)自epubzone上的一篇文章
所以颅湘,EPub是什么
簡(jiǎn)單來(lái)說(shuō)话侧, EPub格式是一種電子書(shū)的標(biāo)準(zhǔn),事實(shí)上幾乎成為了行業(yè)標(biāo)準(zhǔn)闯参,注意觀察的話瞻鹏,幾乎所有的電子書(shū)閱讀器,從硬件到軟件鹿寨,都支持EPub格式的電子書(shū)(Kindle是一朵奇葩新博,原生系統(tǒng)不支持EPub,因?yàn)樗谱约旱腗obi格式)脚草。更具體的內(nèi)容可以查wikipedia-EPUB赫悄, 這里說(shuō)個(gè)好玩的吧,EPub格式電子書(shū)采用zip壓縮格式來(lái)包裹書(shū)籍內(nèi)容以及格式控制的文件(因?yàn)樽裱璉DPF推出的OCF規(guī)范馏慨,而OCF規(guī)范遵循ZIP壓縮技術(shù))埂淮,所以我們可以把.epub改成.zip,然后解壓縮写隶,直接閱讀書(shū)籍的內(nèi)容倔撞,這樣一來(lái),在PC | Mac上樟澜,沒(méi)有EPub閱讀器误窖,照樣可以打開(kāi)EPub閱讀叮盘。
怎么構(gòu)建一本EPub格式的電子書(shū)
上面提到,EPub格式的電子書(shū)其實(shí)是一個(gè)壓縮包文件霹俺,里面有幾個(gè)按照規(guī)范定義的文件柔吼,所謂標(biāo)準(zhǔn),就是規(guī)范EPub文件中某些文件的格式丙唧、內(nèi)容和位置等等愈魏。因此,如果我們想要自己制作一本EPub格式的電子書(shū)想际,首先要了解要制作的內(nèi)容壓縮為zip文件前的文件結(jié)構(gòu)是什么培漏,一個(gè)典型的EPub的文件結(jié)構(gòu)是這樣的:
其實(shí)結(jié)構(gòu)可以更簡(jiǎn)單,下面給出我用Python語(yǔ)言構(gòu)建的EPub文件的文件結(jié)構(gòu):
對(duì)比一下可以看出來(lái)有些文件并不是必須的胡本,下面簡(jiǎn)單介紹一下EPub文件的目錄結(jié)構(gòu):
mimetype文件
這個(gè)內(nèi)容是固定的牌柄,就一行
application/epub+zip
表明可以被EPub工具打開(kāi)或zip工具打開(kāi)
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>
它的功能是告訴閱讀器電子書(shū)根文件路徑以及打開(kāi)方式珊佣,如果你修改了content.opf的名字或者把它放在其他位置,應(yīng)該寫(xiě)明完整的路徑披粟。
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>:來(lái)源
<language>:語(yǔ)種
<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"
表明書(shū)籍的閱讀次序甜奄,其中有一個(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>
該文件的作用是描述電子書(shū)的目錄結(jié)構(gòu)烟阐,這里的content.ncx文件并沒(méi)有很明顯的體現(xiàn)搬俊。有興趣的話可以解壓一本EPub格式的電子書(shū)看一看紊扬。
最后,給出利用Python制作簡(jiǎn)單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'] # 來(lái)自《親愛(ài)的安德烈》中的兩章
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, 用來(lái)制作封面
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")
說(shuō)明:chapter1.html, chapter2.html 是我從“親愛(ài)的安德烈.epub”中提取的兩章唉擂,你也可以替換成其他的內(nèi)容餐屎,若上述python代碼存為simple_epub.py,將chapter1.html玩祟,chapter2.html, simple_epub.py放在同一目錄下腹缩, 通過(guò)python simple_epub.py
即可生成book.epub文件。
參考資料: