Python
TeX
Markdown
Kindle
電子書籍

Kindleで技術書の電子書籍を公開するまで

Kindle Direct Publishingを利用して数式やコード、図などが入った電子書籍を執筆し、公開しましたので途中気づいた点などを共有したいと思います。ちなみに今回執筆したのは以下の書籍です。

PyTorchで学ぶニューラルネットワークと深層学習
https://www.amazon.co.jp/dp/B078WK5CPK/

KDPとは

KDP(Kindle Direct Publishing)は出版社を通さずに直接AmazonのKindleストアで電子書籍を販売するサービスです。出版社を通さないため、最大70%という驚くべき印税がもらえまし、何より執筆開始から出版までの必要な時間が大幅に短縮されます。その代わり、本当にすべてのことを自分でやる必要があります。

KDPにログイン

以下のURLにアクセスし、Amazonのアカウントでログインできます。
https://kdp.amazon.co.jp/

初めてログインした場合は著者情報や支払先情報、税のインタビューなどに回答する必要があります。
支払先の銀行口座の登録など特にややこしい物はないと思いますが、唯一アメリカの源泉徴収に関する税金のインタビューのみ初見だとなんのことかわからないかもしれません。基本的にはAmazon.comでの売上のアメリカでの源泉徴収の話ですので英語で書いてアメリカでも売ろうと思わなければあまり気にしなくても構いません。自分は日本に住んでいてアメリカ人ではないと答えれば問題ありません。以下の記事が参考になります。

KDP:税に関する情報の登録方法|Amazon Kindle Direct Publishing 源泉徴収
https://hatamoto.biz/kdp-tin-set/

書籍の公開手順

支払先情報などの登録が完了し、画面上部の本棚をクリックしますと以下のような画面になります。

Screenshot-2018-1-23 電子出版なら Amazon Kindle ダイレクト・パブリッシング.png

ここで新しい本の作成を押します。本の公開には次の3つ画面での作業を行う必要があります。

  1. 本の詳細の入力: 著者やタイトル、本の説明やキーワードなどを書きます。本の説明やキーワードは検索されやすさや購入されやすさに関係すると思いますのでよく考えて書いたほうがいいでしょう。
  2. 本のコンテンツのアップロード: 表紙のJPEGと本文のEPUBをアップロードします。本の見栄えのプレビューもここで確認できます。
  3. 本の価格設定: 本の代金や印税の割合、KDPセレクトへの登録などを行えます。

KDPセレクトへ登録するとAmazonでの独占販売と引き換えに70%という印税率やさまざまなキャンペーンを利用することができます。紙の出版は自由であり、日本の電子書籍市場はほとんどAmazonであることを考えると基本的には登録するのがいいと思います。

また、登録後はいきなり公開にせず、予約受付状態にするのがいいでしょう。自分の場合は一通り書き終えたあと、1ヶ月後を販売予定日として予約受付状態にしました。予約中は発売日の3日前まででしたら何回でも原稿や表紙を再アップロードできます。ある程度期限を区切るという意味でも予約状態を利用しましょう。また、この時からAmazonにページが作成されますのでSNSなどを通じて宣伝をすることができます。出版社経由で売る場合は出版社が宣伝やマーケティングもやってくれますが、KDPの場合は自分でやる必要があります。Facebook、Twitter、職場の同僚、勉強会でのLTなどあらゆる手を通じて宣伝してください。日頃から業界のインフルエンサーと仲良くしておくとこういう時に役に立ちます。

本文の執筆

さて、何をすればいいか大枠でわかりましたので実際に電子書籍(特に技術書)の執筆方法と環境を説明します。

執筆環境

以下のツールやサービスを使用しました。

執筆方法

原稿はMarkdownで章ごとに別ファイルに分割して書きました。メインエディタはTyporaを使用しました。TyporaはMarkdownで文章を書くことに特化したエディタで、コードのハイライト、TeXによる数式、画像の挿入などをリアルタイムで確認できるすぐれものです。ただ、Typoraは複数ファイルを開くことができず、いちいち保存を促してくるので補助としてVSCodeも使用しました。複数のファイルにわたって細かい修正をし、Gitで管理するときにはVSCodeのほうが都合がいいです。図はLibreOfficeやGIMPなどを使用して作成しました。GitのレポジトリはGitlabでprivate repositoryを作成してそこにpushします。最後に複数のMarkdownファイル(.md)を順番にcatし、pandocでEPUBに変換します。この際、Kindleは数式を表示するためのMathMLという仕様に対応しておらず、仕方がないのでPNGに変換してMarkdown内の数式タグを画像挿入タグで置換します。この部分はPythonのスクリプトとNode.jsのMathJaxレンダリングサーバーで対処しました。詳しくは後述します。

数式の画像化

KindleではMathMLという数式をHTMLやEPUBで表示する仕様に対応していないため、以下の手順で数式をPNGにレンダリングし、本文に挿入することにします。

  1. 本文中の数式タグを正規表現で抜き出す
  2. 抜き出した数式タグをレンダリングサーバーにPOSTし、SVGの画像を得る
  3. cariosvgを使用してSVGをPNGに変換する
  4. もともとの数式タグをPNGの挿入タグに置き換える
  5. 全ての数式タグを置き換えるまで1に戻って繰り返す

レンダリングサーバーはMathJax-NodeというMathJaxのライブラリをラップした MathJaxServer を使用しました。Dockerのimageも公開されていますのでサクッと立ち上げられます。

$ docker run -d -p 6174:6174 --name=mathjax-server lawrencetaylor/mathjaxserve

1-5は以下のようにPythonで実装しました。正規表現で数式タグを抜き取り、requestsを使用してMathJaxServerにPOSTし、返ってきたSVGをcariosvgでPNGに変換して、数式タグをPNG挿入タグに置換しています。

convert.py
import re
from hashlib import md5

import cairosvg
import requests

reg_display_math = re.compile("\$\$([^$]+)\$\$")


def image_tag(img_path):
    return "\n![]({})\n".format(img_path)


def get_math_png(latex_code, api_url="http://127.0.0.1:6174", **kwds):
    """
    please use lawrencetaylor/mathjaxserver docker image as rendering server
    """
    resp = requests.post(api_url, json={"math": latex_code})
    svg_txt = resp.text
    png_data = cairosvg.svg2png(svg_txt, **kwds)
    return png_data


def replace_math(data, regex, dir_path="/tmp/fig", **kwds):
    m = regex.search(data)
    if m is None:
        return data, True
    latex_code = m.groups()[0]
    latex_md5 = md5(latex_code.encode("utf8")).hexdigest()
    png_file_path = pathlib.Path(dir_path).joinpath(latex_md5 + ".png")
    png_data = get_math_png(latex_code, **kwds)
    with png_file_path.open("wb") as fp:
        fp.write(png_data)
    replaced_text = image_tag(str(png_file_path))
    s, e = m.span()
    return data[:s] + replaced_text + data[e:], False


def convert_document(data, regex, dir_path, **kwds):
    while True:
        data, end = replace_math(data, reg_display_math, dir_path, **kwds)
        if end:
            break
    return data


parser = argparse.ArgumentParser()
parser.add_argument('input_file', metavar='INPUT_FILE')
parser.add_argument("--dpi", type=int, default=100)
parser.add_argument("--tmp-dir", default="/tmp")
parser.add_argument("--mathjax-server", default="http://127.0.0.1:6174")


args = parser.parse_args()
data = open(args.input_file).read()
new_data = convert_document(data, reg_display_math, args.tmp_dir, dpi=args.dpi)
output_path = args.input_file[:-3] + ".editted.md"

with open(output_path, "w") as fp:
    fp.write(new_data)

PandocでEPUBへ変換

pandocでMarkdownをEPUB(ver.3)に変換するには-f markdown -t epub3とします。この際、スタイルシートCSSやメタデータのXMLを用意する必要があります。スタイルシートについては以下の記事を参考にしてgistで公開されているものを使用しました。
記事: http://d.hatena.ne.jp/unk_pizza/20141118/p1
gist: https://gist.github.com/andyferra/2554919

メタデータのXMLにはタイトルや著者情報などを書きます。

metadata.xml
<dc:title>タイトル</dc:title>
<dc:creator>著者</dc:creator>
<dc:language>ja</dc:language> 
<dc:rights>Copyright ©2017 by 著者</dc:rights>

また、`--toc --toc-depth=2を付けておくと2段階目までの見出しを反映した目次を生成してくれます。

最終的には以下の手順でEPUBファイルを作れます。

$ cat a.md b.md c.md > all.md
$ python convert.py --dpi=120 --tmp-dir=/tmp  all.md
$ pandoc -f markdown -t epub3 \
         --highlight-style=pygments \
         --epub-stylesheet=github.css \
         --epub-metadata=metadata.xml \
         --toc --toc-depth=2 \
         -o book.epub all.editted.md

最後の微修正

Pandocの--tocで生成した目次は自動で章番号がふってありますが、自分の場合は手動で番号を管理していたのでこれを消す必要がありました。EPUBは実はzipファイルなのでbook.epubbook.zipにリネームするとそのまま解凍することができます。解凍するとnav.xhtmlというファイルがあります。これの<ol class="toc">より下の要素が目次の一覧なので、ここにある<li>タグをすべて<ul>タグに置換してあげればOKです。また、目次に含めたくない章などは対応する要素を消してしうことで対処できます。最後に再びディレクトリをまるごとzipに圧縮し、book.epubにリネームし直せば完了です。

所感

過去にも出版社を通して紙の本を出版したことがありますが、それと比べてとにかくスピード感が違いますし、とても敷居が低いです。
電子書籍はイメージとしては紙の書籍とブログのちょうど中間という位置づけに感じます。あとから修正が容易なので紙の書籍ほどガチガチに書かなくてもいいけどブログよりはちゃんと構成を考え、まとまった内容にする必要があります。そのため、流行り廃りの速い技術系の書籍を書くにはうってつけの形態だと思います。ブログなどで書くのとは異なり、自分のプロフィールの充実になる点もエンジニアとしては注目するべきポイントです。Qiitaやブログなどで複数回に渡る記事を書いている方がいましたらぜひ本記事を参考にして電子書籍としてKindleに出展してみることをおすすめします。