0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MarkdownからKindle用EPUBを自作してKDPに出した。Node.jsでの作り方と、XHTMLで引っかかった点

0
Posted at

技術書をMarkdownで書いていて、同じ原稿をKindle(KDP)にも出すことにしました。変換ツールは使わず、Node.jsで、EPUBを自作しました。作り方と、途中で引っかかった点、KDPの登録画面で迷った点をまとめます。

先に書いておくと、この記事は作り方の記録です。売れ行きや、収益の話は、まだ書けません(発売して、まだ日が浅いためです)。

材料

  • marked: MarkdownをHTMLに変換する
  • jszip: EPUB(中身はzip)を作る

EPUBは、決まった構造のzipファイルです。自作する場合に必要なファイルは、次のとおりです。

ファイル 役割
mimetype 中身はapplication/epub+zipだけ。zipの先頭に、無圧縮で入れる
META-INF/container.xml 本体(content.opf)の場所を指す
OEBPS/content.opf 書名、著者、章の一覧(spine)、表紙の指定
OEBPS/nav.xhtml と toc.ncx 目次(新しい形式と、古い形式の両方)
OEBPS/c001.xhtml … 章ごとの本文
OEBPS/style.css、cover.jpg 見た目と、表紙

zipの作り方で、守ること

const zip = new JSZip();
zip.file('mimetype', 'application/epub+zip', { compression: 'STORE' });
zip.file('META-INF/container.xml', containerXml);
zip.file('OEBPS/content.opf', opf);
// ... 本文、CSS、表紙
const buf = await zip.generateAsync({
  type: 'nodebuffer',
  compression: 'DEFLATE',
  mimeType: 'application/epub+zip',
});
  • mimetypeは、最初のエントリにして、圧縮しない(STORE)。EPUBの決まりです
  • 私の環境では、書き込んだあとに、zipの先頭のエントリがmimetypeになっていることを、確認しています

引っかかった点: チェックリストが、XHTMLとして不正になる

本文のMarkdownには、- [ ] ...というチェックリストがありました。markedは、これを<input type="checkbox" disabled="">に変換します。ところが、EPUBの本文はXHTMLです。XMLとして正しくなければいけません。閉じタグのない<input>は、そのままでは不正です。

const html = marked.parse(md, { gfm: true })
  .replace(/<br>/g, '<br/>')
  .replace(/<hr>/g, '<hr/>')
  .replace(/<input([^>]*?)\s*\/?>/g, '<input$1/>');

<br>や<hr>も、同じ理由で自己終了の形(<br/>)にしています。

XMLとして正しいか、機械で確かめる

KDPにアップロードする前に、すべての.xhtmlを、XMLパーサで読み込んで、エラーが出ないか確認しました。私は、@xmldom/xmldomを使いました。

const { DOMParser } = require('@xmldom/xmldom');
// zipの中の .xhtml / .opf / .ncx を1つずつ読み込む
new DOMParser({ onError: (lvl, msg) => { if (lvl !== 'warning') errors.push(msg); } })
  .parseFromString(xml, 'application/xhtml+xml');

最初の実行では、上のチェックリストの<input>で、1つ目のエラーが出ました。直したあとは、30ファイル、異常0になりました。

コードが多い本の注意: 折り返し

私の本は、コードの全文が多く載っています。Kindleの画面は幅が狭いので、preの中を折り返す設定を入れました。

pre {
  font-family: monospace;
  font-size: 0.72em;
  line-height: 1.5;
  white-space: pre-wrap;
  word-wrap: break-word;
  background: #f4f4f4;
  padding: 0.6em;
}

折り返さないと、長い行がはみ出して、読めなくなります。

表紙

KDPは、表紙を、縦横比1:1.6(推奨)の画像で受け付けます。私は、1600×2560のJPEGを作りました。AIモデルによる画像生成ではなく、プログラムで描画した図形と文字だけです(あとで、登録画面の「AI生成画像」の質問に答えるときに、その事実を使いました)。

KDPの登録画面で、迷った点

  • KDPセレクト: 同じ内容を、ほかの場所でも売っている場合は、登録しない。セレクトは、90日間、Amazonだけで売ることが条件です(日本のヘルプで、最終確認してください)
  • AI生成コンテンツ: 登録時に、AIで生成した内容かを、申告する画面があります。文章を、AIを使って書いて、人が確認した場合は、「AI生成テキストを含む」と申告しました。本の中にも、その旨を書いています
  • アクセシビリティ: 「情報を提供する画像に代替テキストがあるか」を聞かれます。私の本は、本文に画像がなく、表紙だけでした。表紙には、EPUBの中で、代替テキスト(alt="表紙")を付けています。事実に合う選択肢を、選びました
  • カテゴリー: 最初に、「Kindle本」を選び、そのあと、サブカテゴリー(コンピュータ・ITなど)の順に、絞っていく画面でした
  • 価格: ロイヤリティが70%になる価格帯は、250〜1,250円です(日本のストア)

まとめ

  • EPUBは、mimetype(先頭・無圧縮)、container.xml、content.opf、目次、本文のXHTMLがあれば、自作できる
  • markedの出力は、そのままではXHTMLにならないことがある。自己終了の形に直す
  • 提出前に、XMLパーサで全ファイルを機械的に確かめる
  • コードが多い本は、preを折り返す
  • 同じ内容をほかでも売るなら、KDPセレクトには入れない。AIの利用は、正直に申告する

この記事はZennにも投稿しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?