概要
コミック系のKindle本から画像ファイルを抽出しzip(cbz)に変換します。
(画像のみで構成されるKindle本のみが対象です。)
理由は公式Kindleソフトがマジで本当に使いにくいためです。
この文書は個人的メモ(特にバージョン)の位置付けです。
Calibreの導入
まずCalibreでKindle本を変換する環境を準備します。
具体的な手順は別記事、Kindle本をPDFに変換するを参照。
「本を変換」でzipに変換
- Calibre上で変換する本を選択
- 左上アイコン「本を変換」をクリック
- 変換画面が表示されるので左上の「入力形式」をKFXに設定
- 同画面の右上にある「出力形式」をZIPに設定
- 右下の「OK」をクリック
これでフォーマット変換されZIPファイルが出力されます。
画像自体はこの時点で抽出済み
生成されたZIPファイルは既に全ての画像が抽出され、格納されています。
画像ビューワソフト(例:NeeView、ComicGlassなど)で開くと表示されます。
それで問題なく閲覧できるのであればこの時点で作業は完了です。
問題がある場合
しかし実際には「表示順が正しくない」という問題がある場合が多いです。
この対策はCalibre上では難しく、下記の手順が必要となります。
- ZIPを解凍し
- 画像ファイルを取り出し
- 正しい表示順になるようファイル名を変更し
- またZIPに戻す
対策
Pythonで上記手順を実行するようにしました。コードは下記。使い方は
python kindle2cbz.py <対象のZIPファイル>
で、生成されるファイル名は「本のタイトル.cbz」です。拡張子はcbzですが実体はzipです。(画像ビューワソフトで使われる拡張子です)
import os
import re
import sys
import tempfile
import shutil
import zipfile
from pathlib import Path
from bs4 import BeautifulSoup
def get_first_html(files):
"""最初に参照するhtml, xhtmlを探す"""
html_path = find_list(".html", files)
if html_path is None:
htmls = find_list(".xhtml", files)
html_path = htmls[0] if htmls is not None else None
return html_path
def get_title_from_html(html):
"""htmlファイルから <meta name="DC.title" content="title"> を抽出"""
content = html.read_text(encoding="utf-8")
soup = BeautifulSoup(content, "html.parser")
tag = soup.find("meta", attrs={"name": "DC.title"})
title = tag.get("content") if tag else "None title"
return sanitize_filename(title)
def sanitize_filename(filename):
"""ファイル名に使えない文字を置換する"""
table = str.maketrans({
'/': '/',
'\\': '¥',
})
return filename.translate(table)
def analyze_html(html):
"""htmlから次ページのhrefと現在ページのimgを抽出"""
content = html.read_text(encoding="utf-8")
soup = BeautifulSoup(content, "html.parser")
# <a href="---" class="calibreANext">
next_a = soup.find("a", class_="calibreANext")
next_href = next_a.get("href") if next_a else None
# <img src="---">
img = soup.find("img")
img_src = img.get("src") if img else None
return next_href, img_src
def find_list(x, lists):
"""listsからxを含む項目lを取り出す"""
for l in lists:
if x in str(l):
return l
return None
def convert2cbz(input_path, output_ext="cbz"):
"""変換本体"""
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir = Path(tmpdir)
# 解凍
with zipfile.ZipFile(input_path, 'r') as zip_ref:
zip_ref.extractall(tmpdir)
# ファイルリストを取得
files = list(tmpdir.glob("**/*"))
# HTMLからタイトル抽出
first_html = get_first_html(files)
output_title = get_title_from_html(first_html)
# HTMLのnext pageを辿ってimgをoutput_listに追加
output_list = []
prefix, midstr = "image", "-"
cnt = 1 # 連番用カウンター
cnt_len = len(str(len(files))) # 連番桁数=ファイル数の桁数
next_html, img_name = analyze_html(first_html)
while True:
# get current html_path, img_path & next_html name
html_path = find_list(next_html, files)
next_html, img_name = analyze_html(html_path)
ext = os.path.splitext(img_name)[1]
img_path = find_list(img_name, files)
# set new_path for img
postfix = str(cnt)
postfix = postfix.zfill(cnt_len)
new_name = prefix + midstr + postfix + ext
new_path = tmpdir / new_name
# copy & rename img file
shutil.copy(img_path, new_path)
output_list.append(new_path)
if next_html is None:
break
cnt += 1
# cover(表紙)は別枠で追加
for f in files:
if "cover" in f.name.lower():
cover_path = tmpdir / os.path.basename(f)
shutil.copy(f, cover_path)
output_list.insert(0, cover_path)
# zip/cbz作成
output_path = f"{output_title}.{output_ext}"
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as out_zip:
for f in output_list:
out_zip.write(f, f.name)
print(f"✅ 出力完了: {output_path}")
# 実行部
if __name__ == "__main__":
if len(sys.argv) < 2:
print("usage: python conv_kindle_cbz.py <input.epub|zip> [cbz|zip]")
sys.exit(1)
input_file = sys.argv[1]
ext = sys.argv[2] if len(sys.argv) > 2 else "cbz"
if not os.path.isfile(input_file):
print(f"指定されたファイル {input_file} が見つかりません。")
sys.exit(1)
convert2cbz(input_file, ext)
おまけ
kindle2cbz.py
を複数ファイルで実行するシェルスクリプトが下記です。使い方は
conv-all.sh <zipファイル列挙 or *.zip>
です。
#!/bin/bash
for f in "$@"; do
python kindle2cbz.py "$f"
done