1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kindle本(コミック系)を画像zipに完璧に変換する

Last updated at Posted at 2025-07-07

概要

コミック系のKindle本から画像ファイルを抽出しzip(cbz)に変換します。
(画像のみで構成されるKindle本のみが対象です。)
理由は公式Kindleソフトがマジで本当に使いにくいためです。
この文書は個人的メモ(特にバージョン)の位置付けです。

Calibreの導入

まずCalibreでKindle本を変換する環境を準備します。
具体的な手順は別記事、Kindle本をPDFに変換するを参照。

「本を変換」でzipに変換

  1. Calibre上で変換する本を選択
  2. 左上アイコン「本を変換」をクリック
  3. 変換画面が表示されるので左上の「入力形式」をKFXに設定
  4. 同画面の右上にある「出力形式」をZIPに設定
  5. 右下の「OK」をクリック

これでフォーマット変換されZIPファイルが出力されます。

画像自体はこの時点で抽出済み

生成されたZIPファイルは既に全ての画像が抽出され、格納されています。
画像ビューワソフト(例:NeeViewComicGlassなど)で開くと表示されます。
それで問題なく閲覧できるのであればこの時点で作業は完了です。

問題がある場合

しかし実際には「表示順が正しくない」という問題がある場合が多いです。
この対策はCalibre上では難しく、下記の手順が必要となります。

  1. ZIPを解凍し
  2. 画像ファイルを取り出し
  3. 正しい表示順になるようファイル名を変更し
  4. またZIPに戻す

対策

Pythonで上記手順を実行するようにしました。コードは下記。使い方は
python kindle2cbz.py <対象のZIPファイル>
で、生成されるファイル名は「本のタイトル.cbz」です。拡張子はcbzですが実体はzipです。(画像ビューワソフトで使われる拡張子です)

kindle2cbz.py
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>です。

conv-all.sh
#!/bin/bash

for f in "$@"; do
    python kindle2cbz.py "$f"
done
1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?