📝

Pythonで実装するKindleのPDF化ー③PDF変換編

2024/12/30に公開

はじめに

Kindleの電子書籍をPDF化したいと思ったことはありませんか。
私は教科書など、文字を書き込みながら読みたい本は今回紹介する方法を使ってPDF化し、タブレットに読み込んで閲覧しています。

KindleのPDF化は大きく分けて以下の3つの作業に分かれます。

  1. すべてのページのスクリーンショットを撮る。
  2. 適切に余白を削除して整える。
  3. 整えた画像ファイルを一つのPDFに変換する。

以下のページでそれぞれのやり方を説明しているので、ご興味のある方は参考にしてみてください。(当たり前ですが、PDF化したものの著作権にはお気をつけください。)

Pythonで実装するKindleのPDF化ー①スクリーンショット編
Pythonで実装するKindleのPDF化ー②トリミング編
Pythonで実装するKindleのPDF化ー③PDF変換編

なお、今回紹介するコードは以下の記事でご紹介いただいたものに、自分なりにアレンジを加えたものになります。
Kindle for PCのスクショを撮る #Python - Qiita
本家様の記事のコードの方が完成度は高いので、ぜひそちらもご覧ください。
作成者様ありがとうございました。

PDF変換編

前回までの記事で、Kindle PCアプリケーションからスクリーンショットを撮影し、不要な余白を削除する方法をご紹介しました。今回は最後のステップとして、トリミング済みの画像ファイルをまとめてPDFに変換する方法について解説します。

このプログラムは、複数の画像ファイルを1つのPDFファイルにまとめるためのGUIアプリケーションで、以下のような特徴があります。

  • シンプルで直感的なインターフェース
  • 進捗状況のリアルタイム表示
  • マルチスレッドによる応答性の確保
  • エラーハンドリングによる安定性

必要な環境とライブラリ

プログラムを実行する前に、以下のライブラリをインストールする必要があります。

pip install Pillow
pip install reportlab

各ライブラリの役割は以下の通りです。

  • tkinter: GUIを作成するための標準ライブラリです。フォルダ選択やプログレスバーの表示に使用します。
  • PIL (Python Imaging Library): 画像ファイルの読み込みと処理を行うためのライブラリです。
  • reportlab: PDFファイルの生成を行うためのライブラリです。
  • threading: 並行処理を実現するためのライブラリです。

プログラムの実装解説

1. 画像のPDF変換機能

画像からPDFへの変換処理を行うimage_to_pdf関数は、プログラムの中核となる機能です。

def image_to_pdf(folder_path, output_folder, output_filename, progress_var, status_var):
    """
    指定フォルダ内の画像をPDFに変換する関数
    """
    image_files = [f for f in os.listdir(folder_path) 
                   if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
    image_files.sort()

この関数は以下の手順で処理を行います。

  1. フォルダ内の画像ファイルの取得

    • 対応する拡張子(.png, .jpg, .jpeg)を持つファイルを選択
    • ファイル名でソートして順序を確定
  2. PDFファイルの作成

    • reportlabライブラリを使用してPDFを生成
    • 各画像のサイズに合わせてページサイズを設定
    • 画像を順番にPDFページとして追加
  3. 進捗状況の管理

    • プログレスバーによる進行状況の表示
    • ステータスメッセージによる処理状況の通知

2. 変換処理の実行管理

変換処理全体を管理するrun_conversion関数は、以下の重要な処理を行います。

def run_conversion():
    """変換処理を実行する関数"""
    folder_path = folder_var.get()
    output_folder = output_folder_var.get()
    output_filename = output_filename_var.get()

この関数の主な役割は以下の通りです。

  1. 入力値の検証

    • フォルダパスの存在確認
    • 必要な情報がすべて入力されているか確認
    • ファイル名の拡張子チェック
  2. UI状態の管理

    • 変換中のボタンの無効化
    • プログレスバーの初期化
    • ステータス表示の更新
  3. マルチスレッド処理

    • 別スレッドでの変換処理実行
    • UIのフリーズ防止
    • 処理完了後の適切なUI更新

3. GUIレイアウトの実装

最後に、使いやすいGUIレイアウトを実装します。

# GUIの設定
root = tk.Tk()
root.title("Image to PDF Converter")

# 変数の初期化
folder_var = tk.StringVar()
output_folder_var = tk.StringVar()
output_filename_var = tk.StringVar()

GUIは以下の要素で構成されています。

  1. フォルダ選択部分

    • 入力フォルダの選択(画像ファイルの場所)
    • 出力フォルダの選択(PDFの保存先)
    • フォルダ参照ボタン
  2. ファイル名設定部分

    • 出力ファイル名の入力フィールド
    • .pdf拡張子の自動追加機能
  3. 操作・表示部分

    • 変換開始ボタン
    • プログレスバー
    • ステータスメッセージラベル

使用方法

このプログラムは、以下の手順で使用します。

  1. プログラムの起動

    • プログラムを実行すると、GUIウィンドウが表示されます。
  2. 入力設定

    • 「画像があるフォルダを選択」で、前回トリミングした画像が保存されているフォルダを選択します。
    • 「PDFファイルの出力先フォルダを選択」で、生成するPDFファイルの保存先を選択します。
    • 「出力ファイル名」に、作成するPDFファイルの名前を入力します。
  3. 変換実行

    • 「変換」ボタンをクリックすると、処理が開始されます。
    • プログレスバーで進行状況を確認できます。
    • 変換が完了すると、完了メッセージが表示されます。

実行例

それでは、前回トリミングした画像をPDFに変換したいと思います。
コードを実行するとこのような画面が表示されます。

画像があるフォルダ、出力先フォルダ、出力ファイル名をそれぞれ設定します。

「変換」をクリックするとPDF化が実行されます。
これもそこまで時間はかからず、300枚程度なら1,2分で完了すると思います。

実行が完了すると、このようにPDFファイルが作成されます。

PDFを開くと、きちんとPDF化されています。(画像は見開き表示にしています)

注意点とカスタマイズ

プログラムの使用とカスタマイズに関する注意点は以下の通りです。

対応画像形式

プログラムは現在、PNG、JPEG形式の画像ファイルに対応しています。他の形式を追加する場合は、image_to_pdf関数内の拡張子リストを修正する必要があります。

ファイル名のソート

PDF内の画像順序は、ファイル名の昇順で決定されます。異なるソート順が必要な場合は、image_files.sort()の部分を修正してください。

エラーハンドリング

プログラムには基本的なエラーハンドリングが実装されていますが、特殊なケースに対応する場合は、追加の例外処理が必要になる可能性があります。

まとめ

このプログラムを使用することで、前回までの手順で作成した画像ファイルを1つのPDFファイルにまとめることができます。
以上で、KindleのPDF化は完了です。
ご自身の用途に合わせてカスタマイズしてみてください。

Pythonで実装するKindleのPDF化ー①スクリーンショット編
Pythonで実装するKindleのPDF化ー②トリミング編
Pythonで実装するKindleのPDF化ー③PDF変換編

コード全文

import os
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image
from reportlab.pdfgen import canvas
import threading

def select_folder():
    """フォルダ選択ダイアログを表示する関数"""
    return filedialog.askdirectory()

def image_to_pdf(folder_path, output_folder, output_filename, progress_var, status_var):
    """
    指定フォルダ内の画像をPDFに変換する関数
    
    Parameters:
        folder_path (str): 入力画像のフォルダパス
        output_folder (str): 出力先フォルダパス
        output_filename (str): 出力PDFのファイル名
        progress_var (tk.DoubleVar): 進捗バー用の変数
        status_var (tk.StringVar): ステータス表示用の変数
        
    Returns:
        bool: 変換が成功した場合はTrue、失敗した場合はFalse
    """
    # 画像ファイルの取得とソート
    image_files = [f for f in os.listdir(folder_path) 
                   if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
    image_files.sort()

    if not image_files:
        messagebox.showerror("エラー", "指定されたフォルダに画像ファイルが見つかりません。")
        return False

    # PDFの作成
    output_pdf = os.path.join(output_folder, output_filename)
    c = canvas.Canvas(output_pdf)
    total_files = len(image_files)

    for i, image_file in enumerate(image_files, 1):
        # 画像の読み込みとPDFページの作成
        full_path = os.path.join(folder_path, image_file)
        img = Image.open(full_path)
        width, height = img.size
        c.setPageSize((width, height))
        c.drawImage(full_path, 0, 0, width, height)
        c.showPage()

        # 進捗状況の更新
        progress = (i / total_files) * 100
        progress_var.set(progress)
        status_var.set(f"処理中... {i}/{total_files} ファイル")
        root.update_idletasks()

    c.save()
    progress_var.set(100)
    status_var.set("完了")
    return True

def run_conversion():
    """変換処理を実行する関数"""
    # 入力値の取得と検証
    folder_path = folder_var.get()
    output_folder = output_folder_var.get()
    output_filename = output_filename_var.get()
    
    if not folder_path or not output_folder or not output_filename:
        messagebox.showerror("エラー", 
                           "入力フォルダ、出力フォルダ、ファイル名をすべて指定してください。")
        return
    
    # 拡張子の確認と追加
    if not output_filename.lower().endswith('.pdf'):
        output_filename += '.pdf'
    
    # UI状態の初期化
    progress_var.set(0)
    status_var.set("開始中...")
    convert_button.config(state=tk.DISABLED)

    def conversion_thread():
        """変換処理を実行するスレッド"""
        success = image_to_pdf(folder_path, output_folder, output_filename, 
                             progress_var, status_var)
        if success:
            messagebox.showinfo("完了", 
                              f"PDFファイルが作成されました: {os.path.join(output_folder, output_filename)}")
            convert_button.config(text="終了", command=root.quit)
        convert_button.config(state=tk.NORMAL)

    # 別スレッドで変換処理を実行
    thread = threading.Thread(target=conversion_thread)
    thread.start()

# GUIの設定
root = tk.Tk()
root.title("Image to PDF Converter")

# 変数の初期化
folder_var = tk.StringVar()
output_folder_var = tk.StringVar()
output_filename_var = tk.StringVar()
progress_var = tk.DoubleVar()
status_var = tk.StringVar()

# フォルダ選択部分のUI
tk.Label(root, text="画像があるフォルダを選択:").grid(row=0, column=0, sticky="e", padx=5, pady=5)
tk.Entry(root, textvariable=folder_var, width=50).grid(row=0, column=1, padx=5, pady=5)
tk.Button(root, text="参照", 
         command=lambda: folder_var.set(select_folder())).grid(row=0, column=2, padx=5, pady=5)

tk.Label(root, text="PDFファイルの出力先フォルダを選択:").grid(row=1, column=0, sticky="e", padx=5, pady=5)
tk.Entry(root, textvariable=output_folder_var, width=50).grid(row=1, column=1, padx=5, pady=5)
tk.Button(root, text="参照", 
         command=lambda: output_folder_var.set(select_folder())).grid(row=1, column=2, padx=5, pady=5)

# ファイル名入力部分のUI
tk.Label(root, text="出力ファイル名:").grid(row=2, column=0, sticky="e", padx=5, pady=5)
tk.Entry(root, textvariable=output_filename_var, width=50).grid(row=2, column=1, padx=5, pady=5)

# 変換ボタン
convert_button = tk.Button(root, text="変換", command=run_conversion)
convert_button.grid(row=3, column=0, columnspan=3, pady=10)

# プログレスバーとステータス表示
progress_bar = ttk.Progressbar(root, variable=progress_var, maximum=100)
progress_bar.grid(row=4, column=0, columnspan=3, sticky="ew", padx=5, pady=5)
status_label = tk.Label(root, textvariable=status_var)
status_label.grid(row=5, column=0, columnspan=3, pady=5)

root.mainloop()

Discussion