EPub格式電子書的制作

最近在寫的一個(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)是這樣的:

IBM epub

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


my epub

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

參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容