EPub格式電子書(shū)的制作

最近在寫(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)

這張光盤(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)是這樣的:

IBM epub

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


my epub

對(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文件。

參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末空扎,一起剝皮案震驚了整個(gè)濱河市藏鹊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌转锈,老刑警劉巖盘寡,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異撮慨,居然都是意外死亡宴抚,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)甫煞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)菇曲,“玉大人,你說(shuō)我怎么就攤上這事抚吠〕3保” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵楷力,是天一觀的道長(zhǎng)喊式。 經(jīng)常有香客問(wèn)我,道長(zhǎng)萧朝,這世上最難降的妖魔是什么岔留? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮检柬,結(jié)果婚禮上献联,老公的妹妹穿的比我還像新娘。我一直安慰自己何址,他們只是感情好里逆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著用爪,像睡著了一般原押。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上偎血,一...
    開(kāi)封第一講書(shū)人閱讀 49,929評(píng)論 1 290
  • 那天诸衔,我揣著相機(jī)與錄音盯漂,去河邊找鬼。 笑死笨农,一個(gè)胖子當(dāng)著我的面吹牛就缆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播磁餐,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼违崇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了诊霹?” 一聲冷哼從身側(cè)響起羞延,我...
    開(kāi)封第一講書(shū)人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脾还,沒(méi)想到半個(gè)月后伴箩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鄙漏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年嗤谚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怔蚌。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡巩步,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出桦踊,到底是詐尸還是另有隱情椅野,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布籍胯,位于F島的核電站竟闪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏杖狼。R本人自食惡果不足惜炼蛤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蝶涩。 院中可真熱鬧理朋,春花似錦、人聲如沸子寓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)斜友。三九已至,卻和暖如春垃它,著一層夾襖步出監(jiān)牢的瞬間鲜屏,已是汗流浹背烹看。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洛史,地道東北人惯殊。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像也殖,于是被迫代替她去往敵國(guó)和親土思。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理忆嗜,服務(wù)發(fā)現(xiàn)己儒,斷路器,智...
    卡卡羅2017閱讀 134,637評(píng)論 18 139
  • 這里是與 Kindle 電子書(shū)相關(guān)的工具軟件捆毫。它們可以幫助我們解決在日常使用電子書(shū)時(shí)所可能遇到的問(wèn)題闪湾,比如 kin...
    JosephDHF閱讀 7,506評(píng)論 1 45
  • test
    阿曦閱讀 285評(píng)論 0 1
  • 一九三七年 一個(gè)溫和的夜晚 月光如牛奶 溫柔地在山間浮沉 古寨里 風(fēng)兒無(wú)聲地飛 小河懶懶地流 一切都是祥和的模樣 ...
    白童閱讀 319評(píng)論 1 6
  • 【本文內(nèi)容見(jiàn)S05E01】 【多圖預(yù)警,使用流量時(shí)請(qǐng)慎點(diǎn)】 摩登家庭追到第五季绩卤,我一顆擔(dān)心后繼乏力的心終于放下途样。第...
    有個(gè)愛(ài)吾淺藍(lán)閱讀 725評(píng)論 1 1