この前の記事では、Epubがどのような構造になっているかを解説しました。今回はその中でもstandard.opfファイルについてメスを入れていきたいと思います。
standard.opfの軽いおさらい
"opfファイル"という名前ですが、中身は単純なxmlです。基本的なデータ構造はこのようになっています。
<?xml version="1.0" encoding="UTF-8"?>
<package>
<maindata>
<dc:title>...</dc:title>
<meta>...</meta>
</maindata>
<manifest>
<item .../>
</manifest>
<spine>
<itemref .../>
</spine>
</package>
今回の題である電子書籍の基本情報とは、いわゆる「タイトル」や「著者」などの基本情報のことで、これらの情報はstandard.opfのmaindataの中に格納されています。今回はこのデータをプログラムで読み込む所までをやりたいと思います。
そもそも、このファイルを読み込むためには.epubファイルの中身にアクセスする必要があり、1からやろうとするととても面倒なので、Pythonのとても便利なライブラリ"ebooklib"というのがあるので、それを使っていこうと思います。
今回使用するライブラリのgithubリンクです。
また、今回はサンプルデータとして、"冴えない彼女の育てかた"を使用していきます。
import ebooklib
from ebooklib import epub
book = epub.read_epub("冴えない彼女の育てかた1.epub") #ファイルまでのパス
実は、これだけでプログラム上はデータを受け取れています。試しに出力させてみると、
>>> book.get_metadata("DC", "creator")
[('丸戸史明', {'id': 'creator01'})]
このように、著者の情報が配列として出力されます。
ちゃんと表示ができたので今回はここまで
・・・という訳にはいかないので、一応この関数の意味と内容について解説を入れようと思います。
ebooklib内のget_metadata関数は、以下のように書かれています。
#epub.py
def get_metadata(self, namespace, name):
"Retrieve metadata"
if namespace in NAMESPACES:
namespace = NAMESPACES[namespace]
return self.metadata[namespace].get(name, [])
ここを切り取っても何も意味が分からないので、ebooklibがどのようにデータを保存しているのかを見ていきます。
book = epub.read_epub("冴えない彼女の育てかた1.epub") #ファイルまでのパス
これを実行することによって、ebooklib内では、epubを解凍、standard.opfの中身を読み取るという動作を行っています。
これらのデータは、bookクラスのmetadataという連想配列に保存されています。
>>> book.metadata
{
'http://purl.org/dc/elements/1.1/': {
'title': [('冴えない彼女の育てかた', {})],
'creator': [('丸戸史明', {'id': 'creator01'})],
'publisher': [('富士見書房', {})],
'language': [('ja', {})],
'description': [('桜舞い散る通学路で運命の出会いをした……ハズの俺。だが、相手
はキャラが全く立っていないクラスメートだった!?\u3000「ならば、俺がお前をメインヒロインにしてやる!」丸戸史明のヒロイン育成ラブコメ、開幕!', {})]},
'http://www.idpf.org/2007/opf': {
None: [
('サエナイヒロインノソダテカタ01', {'refines': '#title', 'property': 'file-as'}),
('aut', {'refines': '#creator01', 'property': 'role', 'scheme': 'marc:relators'}),
('マルトフミアキ', {'refines': '#creator01', 'property': 'file-as'}),
('1', {'refines': '#creator01', 'property': 'display-seq'}),
('2012-09-13T17:21:18Z', {'property': 'dcterms:modified'}),
('1.1.2', {'property': 'ebpaj:guide-version'})]}
}
この配列をみると、ちゃんと作者のデータが見つかりますね。
辞書のhttp://purl.org/dc/elements/1.1/
内のさらに'creator'という所に保存されています。この配列を呼ぶためにget_metadata関数というものが定義されています。
第一引数のnamespaceですが、これは二通りの書き方が存在します。
最初からhttp://purl.org/dc/elements/1.1/
を指定することでも、当然metadataの辞書内を検索することができます。
しかし、毎回これを書くと長くて冗長になるので、
NAMESPACES["DC"]='http://purl.org/dc/elements/1.1/'
と定義して、第一引数はこの略記である"DC"としても中身を検索できるようになっています。開発者の粋な計らいですね
第二引数は第一引数で確定した辞書内の検索設定です。title,creator,publisherなどを指定することで、辞書検索されて配列が渡されているというわけですね。
ただし、一つだけ問題があります。
http://www.idpf.org/2007/opf
の辞書の中にもデータがありますよね?丸戸先生の読み仮名のデータがなぜかこっちの辞書に保存されています。
実は、これを読み取る関数は実装されていません。なので、自分で実装しましょう。
def get_refinedata(self, refines, prop=None):
namespace = 'http://www.idpf.org/2007/opf'
metadatas = self.metadata[namespace].get(None, [])
refine = []
for metadata in metadatas:
data = metadata[1].get('refines',[])
if data != []:
data = data.strip("#")
if data == refines:
if prop and metadata[1].get("property") == prop:
refine.append(metadata)
elif not prop:
refine.append(metadata)
return refine
これをepub.py(ebooklib本体)のclass EpubBookの中に書くことで使用できます。
まあこの状態だと、丸戸先生のデータが知りたいときは
>>> book.get_refinedata("creator01")
[('aut', {'refines': '#creator01', 'property': 'role', 'scheme': 'marc:relators'}), ('マルトフミアキ', {'refines': '#creator01', 'property': 'file-as'}), ('1', {'refines': '#creator01', 'property': 'display-seq'})]
>>> book.get_refinedata("丸戸史明")
[]
のように、著者ではなく著者に紐づいたidで検索しなければいけないのがちょっと面倒ですが、これでちゃんと検索ができましたね。
今回の記事はここまでにしたいと思います。
後編ではここからbook.metadataを変更したり、それをepubに保存できるように関数を書いていきます。
終わりに
思っていた以上にebooklibって便利ですね。epubの中身を変える時は毎回解凍して変更して圧縮する作業をしていたのでめっちゃ作業が楽になりました。
ここに、自分が作成したツールを公開しておきます。