Zenn本をKindle本に変換する方法
概要
この記事では、Zennで執筆した本をKindle出版に適した形式に変換する方法を紹介します。Zennの書籍をMarkdownで執筆している場合、pandoc
を使うことで簡単にEPUB形式の電子書籍に変換できます。また、カバー画像のリサイズなど、Kindleの要件に合わせた調整も行います。
前提
この記事で紹介する手順を進める前に、以下の条件が満たされていることを確認してください。
- GitHub連携でZennの本を執筆している
- 本のカバー画像が
cover.png
として保存されている -
pandoc
がインストールされている -
Python
環境が整備されている
必要なパッケージ
-
Pillow:
画像の読み込み、リサイズ、形式変換(PNGからJPGなど)を行うためのライブラリ。- 用途: 表紙画像のリサイズと形式変換。
-
PyYAML:
YAMLファイル(書籍の設定など)を読み込み、解析するためのライブラリ。- 用途:
config.yaml
などの書籍情報を扱うため。
- 用途:
完成するもの
このプロセスを経て、以下のファイルが生成されます。
- EPUB形式の電子書籍ファイル
- JPEG形式のカバー画像
これらをそのままKindle出版にアップロードすることができます。
URLの変換
Zennでは、URL単体を記載するとカード形式で表示されます。しかし、Kindleではその形式を利用できないため、本文中のURLはすべてタイトル付きリンクに変換する必要があります。これには、Pythonスクリプトを使って自動的に行います。
import re
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse, parse_qs, urlunparse, urlencode
import time
def get_page_title(url):
"""
指定されたURLのページタイトルを取得する
:param url: タイトルを取得したいページのURL
:return: ページのタイトル
"""
try:
time.sleep(1) # サーバーへの負荷を減らすために1秒待つ
response = requests.get(url)
if response.status_code == 200:
soup = BeautifulSoup(response.content, 'html.parser')
return soup.title.string.strip()
else:
print(f"Failed to retrieve the page: {url}")
return url # タイトルが取得できない場合はURLをそのまま返す
except Exception as e:
print(f"Error fetching title for {url}: {e}")
return url
def expand_amazon_short_url(url):
"""
Amazonの短縮URL (https://amzn.to/) を展開し、アソシエイトタグを除去する
:param url: Amazonの短縮URL
:return: 展開されたURLからアソシエイトタグを除去したURL
"""
try:
# 短縮URLを展開
response = requests.head(url, allow_redirects=True)
full_url = response.url
# アソシエイトタグ(tag=xxxx)をURLから除去
parsed_url = urlparse(full_url)
query_params = parse_qs(parsed_url.query)
# 'tag'パラメータを削除
if 'tag' in query_params:
del query_params['tag']
# 新しいクエリを構築
new_query = urlencode(query_params, doseq=True)
new_url = urlunparse(parsed_url._replace(query=new_query))
return new_url
except Exception as e:
print(f"Error expanding or cleaning Amazon URL {url}: {e}")
return url
def replace_urls_in_text(content):
"""
テキスト内でURLだけが記載された行に対して、タイトル付きリンクに変換し、
Amazonアソシエイトリンクの場合は展開してタグを除去する
:param content: Markdownの内容
:return: タイトル付きリンクに変換されたMarkdown内容
"""
# 行ごとに分割して処理
lines = content.splitlines()
url_pattern = re.compile(r'^(https?://[^\s]+)$') # URLだけが書かれた行を検出
for i, line in enumerate(lines):
match = url_pattern.match(line)
if match:
url = match.group(1)
# Amazonの短縮URLの場合は展開してタグを外す
if url.startswith('https://amzn.to/'):
clean_url = expand_amazon_short_url(url)
else:
clean_url = url
# URLからタイトルを取得
title = get_page_title(clean_url)
# Markdownのリンク形式に変換
markdown_link = f'[{title}]({clean_url})'
# 行をタイトル付きリンクに置換
lines[i] = markdown_link
return '\n'.join(lines)
ファイルの読み込み
Zennの本の設定ファイルや章ごとのMarkdownファイルを読み込むための関数です。
import yaml
def load_config(slug):
config_path = f'books/{slug}/config.yaml'
with open(config_path, 'r', encoding='utf-8') as file:
config = yaml.safe_load(file)
return config
def load_chapter(slug, chapter_name):
chapter_path = f'books/{slug}/{chapter_name}.md'
with open(chapter_path, 'r', encoding='utf-8') as file:
content = file.read()
return content
ファイルの結合と見出しの階層修正
Zennの本は章ごとに分かれていますが、Kindle用のファイルとしては全体を1つに結合する必要があります。さらに、見出しの階層がばらついている場合、これを統一します。
import re
import os
def find_min_heading_level(content):
headings = re.findall(r'^(#+) ', content, re.MULTILINE)
if headings:
return min(len(h) for h in headings)
return None
def adjust_heading_levels(content, min_level, base_level=2):
if min_level is None:
return content
level_diff = base_level - min_level
def adjust(match):
heading = match.group(1)
text = match.group(2)
return '#' * (len(heading) + level_diff) + ' ' + text
adjusted_content = re.sub(r'^(#+)(\s+.*)', adjust, content, flags=re.MULTILINE)
return adjusted_content
def create_combined_markdown(slug, config):
os.makedirs(f'kindle/{slug}', exist_ok=True)
combined_path = f'kindle/{slug}/all.md'
with open(combined_path, 'w', encoding='utf-8') as outfile:
for chapter in config['chapters']:
chapter_content = load_chapter(slug, chapter)
title_match = re.search(r'title: "(.+)"', chapter_content)
chapter_title = title_match.group(1) if title_match else chapter
chapter_title = chapter_title.replace('"', '')
outfile.write(f'# {chapter_title}\n\n')
body = extract_body(chapter_content)
min_level = find_min_heading_level(body)
adjusted_body = adjust_heading_levels(body, min_level, base_level=2)
outfile.write(adjusted_body + '\n\n')
画像パスの修正
画像のパスを修正し、必要な画像を適切な場所にコピーします。
import re
def copy_images(slug, config):
combined_path = f'kindle/{slug}/all.md'
with open(combined_path, 'r+', encoding='utf-8') as file:
content = file.read()
# 画像パス中のサイズ指定を削除 (例: `=400x` を除去)
updated_content = re.sub(r'(\s*=\d+x\d*)', '', content)
# 画像パスを/images → ./imagesに変更
updated_content = re.sub(r'/images', "./images", updated_content)
# ファイルに修正したMarkdownを再度書き込み
file.seek(0)
file.write(updated_content)
file.truncate()
カバー画像のリサイズと変換
Kindleで利用するカバー画像は特定のサイズと形式に準拠している必要があります。Pillow
を使って、画像を自動的に調整します。
from PIL import Image
def resize_and_convert_cover(cover_source, cover_dest_jpg):
MIN_WIDTH = 625
MIN_HEIGHT = 1000
MAX_SIZE = 10000
with Image.open(cover_source) as img:
width, height = img.size
if width < MIN_WIDTH or height < MIN_HEIGHT:
img
.thumbnail((MAX_SIZE, MAX_SIZE), Image.Resampling.LANCZOS)
new_width = max(MIN_WIDTH, width)
new_height = max(MIN_HEIGHT, height)
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
img.thumbnail((MAX_SIZE, MAX_SIZE), Image.Resampling.LANCZOS)
rgb_img = img.convert('RGB')
rgb_img.save(cover_dest_jpg, 'JPEG')
メタデータの生成
書籍のメタデータを作成します。
import shutil
import yaml
def create_metadata(slug, config, author='Unknown'):
metadata_path = f'kindle/{slug}/metadata.yaml'
cover_source = f'books/{slug}/cover.png'
cover_dest = f'kindle/{slug}/cover.png'
cover_dest_jpg = f'kindle/{slug}/cover.jpg'
if os.path.exists(cover_source):
shutil.copy(cover_source, cover_dest)
resize_and_convert_cover(cover_source, cover_dest_jpg)
else:
cover_dest = None # カバーがない場合の処理
metadata = {
'title': config['title'],
'author': author,
'cover_image': './cover.png' if cover_dest else None
}
with open(metadata_path, 'w', encoding='utf-8') as file:
yaml.dump(metadata, file, default_flow_style=False)
GitHub Markdown CSSのダウンロード
GitHubのMarkdown用CSSをダウンロードして、EPUBファイルに適用します。
import requests
def download_css(css_url, output_path):
"""
指定されたURLからCSSファイルをダウンロードし、指定された場所に保存する
:param css_url: CSSファイルのURL
:param output_path: 保存先のパス
"""
try:
response = requests.get(css_url)
response.raise_for_status() # HTTPエラーがあれば例外を発生させる
with open(output_path, 'wb') as css_file:
css_file.write(response.content)
print(f"Downloaded CSS from {css_url} and saved to {output_path}")
except requests.exceptions.RequestException as e:
print(f"Failed to download CSS: {e}")
EPUBファイルの生成
ZennのMarkdownファイルとカバー画像を用意したら、pandoc
を使ってEPUB形式の電子書籍に変換します。
import subprocess
def convert_to_epub(slug):
output_epub = f'kindle/{slug}/book.epub'
markdown_file = f'kindle/{slug}/all.md'
metadata_file = f'kindle/{slug}/metadata.yaml'
css_file = f'kindle/{slug}/github-markdown.css'
cover_image = f'kindle/{slug}/cover.jpg'
subprocess.run([
'pandoc',
markdown_file,
'--metadata-file', metadata_file,
'--css', css_file,
'--epub-cover-image', cover_image,
'--toc',
'--toc-depth=1',
'--highlight-style', 'tango',
'-o', output_epub
])
toc-depth
は目次の階層を指定します。必要に応じて変更してください。
全体の処理
最後に、Zenn本をKindle用に変換するためのすべての手順をまとめて実行します。
from replace_urls import replace_urls_in_text
GITHUB_MARKDOWN_CSS_URL = 'https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css'
def convert_zenn_to_kindle(slug, author='Unknown'):
config = load_config(slug)
create_combined_markdown(slug, config)
copy_images(slug, config)
combined_path = f'kindle/{slug}/all.md'
with open(combined_path, 'r', encoding='utf-8') as file:
content = file.read()
updated_content = replace_urls_in_text(content)
with open(combined_path, 'w', encoding='utf-8') as file:
file.write(updated_content)
create_metadata(slug, config, author)
css_output_path = f'kindle/{slug}/github-markdown.css'
download_css(GITHUB_MARKDOWN_CSS_URL, css_output_path)
convert_to_epub(slug)
コマンドライン引数の処理
コマンドライン引数で書籍のslugと著者名を指定して実行します。
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Zenn書籍をKindle用に変換するプログラム')
parser.add_argument('slug', type=str, help='Zenn書籍のスラッグ(ディレクトリ名)')
parser.add_argument('--author', type=str, default='Unknown', help='著者名(オプション)')
args = parser.parse_args()
convert_zenn_to_kindle(args.slug, args.author)
実行方法
以下のコマンドを実行することで、Zenn書籍をKindle用に変換できます。
python main.py slug --author "著者名"
これで、指定したslug
に対応する書籍がkindle/slug
ディレクトリに出力され、book.epub
とcover.jpg
が生成されます。これらをKindle出版にアップロードしてください。
Discussion