Sahara3のAI副業

AI副業でどこまでいけるのか?

AI副業:PDF編集ツールのソース解説

「さはら3」です。

AI副業でどこまでいけるのか?をテーマに頑張っていきたいと思います。



本編

前回の記事ので作成したPDF簡単編集ツールソースコードの主要な部分の解説となります。

ai-sahara-navi.com


スクリプト全容

import tkinter as tk
from tkinter import filedialog, messagebox
import os
import PyPDF2

def rotate_pdf(filepath, rotation, output_filepath):
    """指定した角度でPDFを回転させ、新しいファイルとして保存します。"""
    with open(filepath, 'rb') as file:
        reader = PyPDF2.PdfReader(file)
        writer = PyPDF2.PdfWriter()

        for page in reader.pages:
            page.rotate(rotation)
            writer.add_page(page)

        with open(output_filepath, 'wb') as output_file:
            writer.write(output_file)

def split_pdf(input_filepath):
    """PDFをページごとに分割し、各ページを新しいファイルとして保存します。"""
    output_filepaths = []
    with open(input_filepath, "rb") as file:
        reader = PyPDF2.PdfReader(file)

        for i, page in enumerate(reader.pages):
            writer = PyPDF2.PdfWriter()
            writer.add_page(page)

            base_name = os.path.basename(input_filepath)
            name_without_extension = os.path.splitext(base_name)[0]
            output_filepath = os.path.join(os.path.dirname(input_filepath), f"{name_without_extension}_page_{i+1}.pdf")

            with open(output_filepath, "wb") as output_file:
                writer.write(output_file)

            output_filepaths.append(output_filepath)

    return output_filepaths

def merge_pdfs(filepaths, output_filepath):
    """複数のPDFファイルを1つのファイルに結合します。"""
    writer = PyPDF2.PdfWriter()
    for filepath in filepaths:
        with open(filepath, 'rb') as file:
            reader = PyPDF2.PdfReader(file)
            for page in reader.pages:
                writer.add_page(page)
    with open(output_filepath, 'wb') as output_file:
        writer.write(output_file)

def on_action_click(action):
    """アクションボタンがクリックされたときの処理を行います。"""
    if not selected_files_list:
        messagebox.showwarning("警告", "PDFファイルを選択してください。")
        return

    if action == "merge":
        base_name = os.path.basename(selected_files_list[0])
        name_without_extension = os.path.splitext(base_name)[0]
        output_filepath = os.path.join(os.path.dirname(selected_files_list[0]), f"{name_without_extension}_merged.pdf")
        merge_pdfs(selected_files_list, output_filepath)
        messagebox.showinfo("情報", f"PDFファイルを結合しました:{output_filepath}")
    elif action == "split":
        for filepath in selected_files_list:
            split_pdf(filepath)
        messagebox.showinfo("情報", "PDFファイルを分割しました。")
    elif action in ["rotate_90", "rotate_180", "rotate_270"]:
        rotation_map = {"rotate_90": 90, "rotate_180": 180, "rotate_270": 270}
        for filepath in selected_files_list:
            base_name = os.path.basename(filepath)
            name_without_extension = os.path.splitext(base_name)[0]
            output_filepath = os.path.join(os.path.dirname(filepath), f"{name_without_extension}_rotated_{rotation_map[action]}.pdf")
            rotate_pdf(filepath, rotation_map[action], output_filepath)
        messagebox.showinfo("情報", f"PDFファイルを{rotation_map[action]}度回転しました。")

def open_file_dialog():
    """ファイル選択ダイアログを開き、PDFファイルを選択します。"""
    listbox.delete(0, tk.END)
    
    # グローバルリストとラベルをクリアします
    selected_files_list.clear()
    selected_files_label.config(text="")
    
    filepaths = filedialog.askopenfilenames(title="PDFファイルを開く", filetypes=[("PDFファイル", "*.pdf")])
    for filepath in filepaths:
        listbox.insert(tk.END, filepath)

selected_files_list = []  # 選択されたファイルのリストを保持するグローバル変数

def on_select(event):
    """リストボックスの選択イベントを処理します。"""
    # リストボックスから選択されたアイテムを取得します
    selected_indices = event.widget.curselection()
    selected_items = [event.widget.get(i) for i in selected_indices]

    # グローバルリストから選択されていないアイテムを削除し、ラベルを更新します
    for item in selected_files_list[:]:  # リストのコピーを作成してイテレーションします
        if item not in selected_items:
            selected_files_list.remove(item)
    
    # グローバルリストに新しく選択されたアイテムを追加します
    for item in selected_items:
        if item not in selected_files_list:
            selected_files_list.append(item)

    # グローバルリストにあるアイテムでラベルを更新します
    selected_files_label.config(text="\n".join(selected_files_list))

root = tk.Tk()
root.title("PDFツール")
root.geometry("600x500")

# ルートウィンドウの幅を取得します
window_width = root.winfo_screenwidth()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)

# リストボックスの幅を文字数で計算します(各文字が10ピクセル幅と仮定)
listbox_width = window_width // 10

# リストボックスとそのスクロールバーのフレームを作成します
frame1 = tk.Frame(root)
frame1.grid(row=0, column=0, sticky="nsew")

# 水平および垂直スクロールバーを作成します
xscrollbar1 = tk.Scrollbar(frame1, orient=tk.HORIZONTAL)
xscrollbar1.grid(row=1, column=0, sticky="ew")
yscrollbar1 = tk.Scrollbar(frame1)
yscrollbar1.grid(row=0, column=1, sticky="ns")

# スクロールバー付きのリストボックスを作成します
listbox = tk.Listbox(frame1, selectmode=tk.MULTIPLE, width=listbox_width, height=10,xscrollcommand=xscrollbar1.set, yscrollcommand=yscrollbar1.set)
listbox.grid(row=0, column=0, sticky="nsew")

# スクロールバーをリストボックスにスクロールさせるように設定します
xscrollbar1.config(command=listbox.xview)
yscrollbar1.config(command=listbox.yview)

# frame1の行と列の設定を追加します
frame1.grid_rowconfigure(0, weight=1)
frame1.grid_columnconfigure(0, weight=1)

BUTTON_WIDTH = 180  # ボタンの幅をピクセル単位で設定します

# ファイルダイアログを開くボタンを作成します
frame_open_button = tk.Frame(root, width=BUTTON_WIDTH, height=30)
frame_open_button.grid_propagate(False)  # サイズ変更を無効にします
frame_open_button.grid(row=1, column=0, pady=10)
open_button = tk.Button(frame_open_button, text="ファイルを選択", command=open_file_dialog)
open_button.pack(fill=tk.BOTH, expand=True)

# アクションボタンを作成します
actions = [("90度回転", "rotate_90"), ("180度回転", "rotate_180"), ("270度回転", "rotate_270"), ("ページ分割", "split"), ("PDF結合", "merge")]
for index, (button_text, action) in enumerate(actions):
    frame_button = tk.Frame(root, width=BUTTON_WIDTH, height=30)
    frame_button.grid_propagate(False)  # サイズ変更を無効にします
    frame_button.grid(row=2+index, column=0, pady=5)
    button = tk.Button(frame_button, text=button_text, command=lambda act=action: on_action_click(act))
    button.pack(fill=tk.BOTH, expand=True)

# 選択されたファイルを表示するラベルを作成します
List_label = tk.Label(root, text="処理対象ファイル", justify=tk.LEFT)
List_label.grid(row=2+len(actions), column=0, pady=10, sticky="nsew")

# 選択されたファイルを表示するラベルを作成します
selected_files_label = tk.Label(root, text="", justify=tk.LEFT)
selected_files_label.grid(row=3+len(actions), column=0, pady=10, sticky="nsew")

# 両方のリストボックスに<<ListboxSelect>>イベントをバインドします
listbox.bind('<<ListboxSelect>>', on_select)

root.mainloop()


必要なモジュールのインポート

  • 今回は、TkinterとPyPDF2がメインとなります。
import tkinter as tk
from tkinter import filedialog, messagebox
import os
import PyPDF2

ページの回転処理

def rotate_pdf(filepath, rotation, output_filepath):
    """指定された角度でPDFを回転させ、新しいファイルとして保存します。"""
    with open(filepath, 'rb') as file:
        reader = PyPDF2.PdfReader(file)
        writer = PyPDF2.PdfWriter()

        for page in reader.pages:
            page.rotate(rotation)
            writer.add_page(page)

        with open(output_filepath, 'wb') as output_file:
            writer.write(output_file)

ページの分割処理

def split_pdf(input_filepath):
    """PDFを個別のページに分割し、各ページを新しいファイルとして保存します。"""
    output_filepaths = []
    with open(input_filepath, "rb") as file:
        reader = PyPDF2.PdfReader(file)

        for i, page in enumerate(reader.pages):
            writer = PyPDF2.PdfWriter()
            writer.add_page(page)

            base_name = os.path.basename(input_filepath)
            name_without_extension = os.path.splitext(base_name)[0]
            output_filepath = os.path.join(os.path.dirname(input_filepath), f"{name_without_extension}_page_{i+1}.pdf")

            with open(output_filepath, "wb") as output_file:
                writer.write(output_file)

            output_filepaths.append(output_filepath)

    return output_filepaths

ページの結合処理

def merge_pdfs(filepaths, output_filepath):
    """複数のPDFファイルを1つのファイルに結合します。"""
    writer = PyPDF2.PdfWriter()
    for filepath in filepaths:
        with open(filepath, 'rb') as file:
            reader = PyPDF2.PdfReader(file)
            for page in reader.pages:
                writer.add_page(page)
    with open(output_filepath, 'wb') as output_file:
        writer.write(output_file)

各ボタンが押された際の処理

def on_action_click(action):
    """アクションボタンがクリックされたときの処理を行います。"""
    if not selected_files_list:
        messagebox.showwarning("警告", "PDFファイルを選択してください。")
        return

    if action == "merge":
        base_name = os.path.basename(selected_files_list[0])
        name_without_extension = os.path.splitext(base_name)[0]
        output_filepath = os.path.join(os.path.dirname(selected_files_list[0]), f"{name_without_extension}_merged.pdf")
        merge_pdfs(selected_files_list, output_filepath)
        messagebox.showinfo("情報", f"PDFファイルを結合しました:{output_filepath}")
    elif action == "split":
        for filepath in selected_files_list:
            split_pdf(filepath)
        messagebox.showinfo("情報", "PDFファイルを分割しました。")
    elif action in ["rotate_90", "rotate_180", "rotate_270"]:
        rotation_map = {"rotate_90": 90, "rotate_180": 180, "rotate_270": 270}
        for filepath in selected_files_list:
            base_name = os.path.basename(filepath)
            name_without_extension = os.path.splitext(base_name)[0]
            output_filepath = os.path.join(os.path.dirname(filepath), f"{name_without_extension}_rotated_{rotation_map[action]}.pdf")
            rotate_pdf(filepath, rotation_map[action], output_filepath)
        messagebox.showinfo("情報", f"PDFファイルを{rotation_map[action]}度回転しました。")

ファイル選択ダイアログ

def open_file_dialog():
    """ファイル選択ダイアログを開き、PDFファイルを選択します。"""
    listbox.delete(0, tk.END)
    
    # グローバルリストとラベルをクリアします
    selected_files_list.clear()
    selected_files_label.config(text="")
    
    filepaths = filedialog.askopenfilenames(title="PDFファイルを開く", filetypes=[("PDFファイル", "*.pdf")])
    for filepath in filepaths:
        listbox.insert(tk.END, filepath)

selected_files_list = []  # 選択されたファイルのリストを保持するグローバル変数

リストボックスのファイルを選択・解除したイベントの処理

  • 今回の一番のポイントです。
def on_select(event):
    """リストボックスの選択イベントを処理します。"""
    # リストボックスから選択されたアイテムを取得します
    selected_indices = event.widget.curselection()
    selected_items = [event.widget.get(i) for i in selected_indices]

    # グローバルリストから選択されていないアイテムを削除し、ラベルを更新します
    for item in selected_files_list[:]:  # リストのコピーを作成してイテレーションします
        if item not in selected_items:
            selected_files_list.remove(item)
    
    # グローバルリストに新しく選択されたアイテムを追加します
    for item in selected_items:
        if item not in selected_files_list:
            selected_files_list.append(item)

    # グローバルリストにあるアイテムでラベルを更新します
    selected_files_label.config(text="\n".join(selected_files_list))

アプリのレイアウト等の処理

root = tk.Tk()
root.title("PDFツール")
root.geometry("600x500")

# ルートウィンドウの幅を取得します
window_width = root.winfo_screenwidth()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)

# リストボックスの幅を文字数で計算します(各文字が10ピクセル幅と仮定)
listbox_width = window_width // 10

# リストボックスとそのスクロールバーのフレームを作成します
frame1 = tk.Frame(root)
frame1.grid(row=0, column=0, sticky="nsew")

# 水平および垂直スクロールバーを作成します
xscrollbar1 = tk.Scrollbar(frame1, orient=tk.HORIZONTAL)
xscrollbar1.grid(row=1, column=0, sticky="ew")
yscrollbar1 = tk.Scrollbar(frame1)
yscrollbar1.grid(row=0, column=1, sticky="ns")

# スクロールバー付きのリストボックスを作成します
listbox = tk.Listbox(frame1, selectmode=tk.MULTIPLE, width=listbox_width, height=10,xscrollcommand=xscrollbar1.set, yscrollcommand=yscrollbar1.set)
listbox.grid(row=0, column=0, sticky="nsew")

# スクロールバーをリストボックスにスクロールさせるように設定します
xscrollbar1.config(command=listbox.xview)
yscrollbar1.config(command=listbox.yview)

# frame1の行と列の設定を追加します
frame1.grid_rowconfigure(0, weight=1)
frame1.grid_columnconfigure(0, weight=1)

BUTTON_WIDTH = 180  # ボタンの幅をピクセル単位で設定します

# ファイルダイアログを開くボタンを作成します
frame_open_button = tk.Frame(root, width=BUTTON_WIDTH, height=30)
frame_open_button.grid_propagate(False)  # サイズ変更を無効にします
frame_open_button.grid(row=1, column=0, pady=10)
open_button = tk.Button(frame_open_button, text="ファイルを選択", command=open_file_dialog)
open_button.pack(fill=tk.BOTH, expand=True)

# アクションボタンを作成します
actions = [("90度回転", "rotate_90"), ("180度回転", "rotate_180"), ("270度回転", "rotate_270"), ("ページ分割", "split"), ("PDF結合", "merge")]
for index, (button_text, action) in enumerate(actions):
    frame_button = tk.Frame(root, width=BUTTON_WIDTH, height=30)
    frame_button.grid_propagate(False)  # サイズ変更を無効にします
    frame_button.grid(row=2+index, column=0, pady=5)
    button = tk.Button(frame_button, text=button_text, command=lambda act=action: on_action_click(act))
    button.pack(fill=tk.BOTH, expand=True)

# 選択されたファイルを表示するラベルを作成します
List_label = tk.Label(root, text="処理対象ファイル", justify=tk.LEFT)
List_label.grid(row=2+len(actions), column=0, pady=10, sticky="nsew")

# 選択されたファイルを表示するラベルを作成します
selected_files_label = tk.Label(root, text="", justify=tk.LEFT)
selected_files_label.grid(row=3+len(actions), column=0, pady=10, sticky="nsew")

# 両方のリストボックスに<<ListboxSelect>>イベントをバインドします
listbox.bind('<<ListboxSelect>>', on_select)

root.mainloop()


いかがでしたでしょうか?

今回のポイントは、途中でも記載しましたが、リストの選択・解除で処理するファイルを指定した事です。

何度も何度もChatGPTさんとやり取りして、実装できました。

是非、実行してみてください。


AI関連は日進月歩、日々之精進でございます。

最後まで読んで頂きありがとうございました。

AIさはら


本日のAI着物美女

AI着物美女
Instagram

良かったらInstagramのフォローをお願いします。

https://www.instagram.com/ai_kimono_bijo/

非アダルトで運営しておりますので、職場でも安心して堪能いただけます。