コンタクトシート作成ツール『浄玻璃鏡』 ソースコード

以下は『浄玻璃鏡』のソースコード全文です:


        import os
        import tkinter as tk
        from tkinter import filedialog, ttk
        from PIL import Image, ImageTk
        from reportlab.lib.pagesizes import A4, landscape, portrait
        from reportlab.pdfgen import canvas
        from reportlab.lib.utils import ImageReader
        
        class ContactSheetApp:
            def __init__(self, root):
                self.root = root
                self.root.title("コンタクトシート作成ツール - 浄玻璃鏡")
        
                # 初期変数
                self.image_list = []
                self.folder_path = ""
                self.rows = tk.IntVar(value=4)
                self.cols = tk.IntVar(value=3)
                self.orientation = tk.StringVar(value="portrait")
        
                # GUI構築
                self.build_gui()
        
            def build_gui(self):
                frame = ttk.Frame(self.root, padding=10)
                frame.grid()
        
                # フォルダ選択
                ttk.Button(frame, text="フォルダ選択", command=self.select_folder).grid(row=0, column=0, columnspan=2, sticky="ew")
        
                # 行列選択
                ttk.Label(frame, text="行数:").grid(row=1, column=0, sticky="e")
                ttk.Combobox(frame, textvariable=self.rows, values=list(range(1, 11)), width=5).grid(row=1, column=1)
        
                ttk.Label(frame, text="列数:").grid(row=2, column=0, sticky="e")
                ttk.Combobox(frame, textvariable=self.cols, values=list(range(1, 11)), width=5).grid(row=2, column=1)
        
                # 向き選択
                ttk.Label(frame, text="用紙の向き:").grid(row=3, column=0, sticky="e")
                ttk.Radiobutton(frame, text="縦 (A4)", variable=self.orientation, value="portrait").grid(row=3, column=1, sticky="w")
                ttk.Radiobutton(frame, text="横 (A4)", variable=self.orientation, value="landscape").grid(row=4, column=1, sticky="w")
        
                # プレビューと作成ボタン
                ttk.Button(frame, text="プレビュー表示", command=self.preview).grid(row=5, column=0, sticky="ew")
                ttk.Button(frame, text="PDF作成", command=self.generate_pdf).grid(row=5, column=1, sticky="ew")
        
                # キャンバス
                self.canvas_frame = ttk.Frame(self.root)
                self.canvas_frame.grid(row=0, column=1)
                self.canvas_label = ttk.Label(self.canvas_frame)
                self.canvas_label.pack()
        
            def select_folder(self):
                folder = filedialog.askdirectory()
                if folder:
                    self.folder_path = folder
                    self.image_list = [f for f in os.listdir(folder) if f.lower().endswith((".png", ".jpg", ".jpeg", ".bmp", ".gif"))]
                    self.image_list.sort()
        
            def preview(self):
                if not self.folder_path or not self.image_list:
                    return
                preview_img = self.create_contact_sheet_image()
                preview_img.thumbnail((600, 800))
                img = ImageTk.PhotoImage(preview_img)
                self.canvas_label.configure(image=img)
                self.canvas_label.image = img
        
            def generate_pdf(self):
                if not self.folder_path or not self.image_list:
                    return
                output_path = filedialog.asksaveasfilename(defaultextension=".pdf", filetypes=[("PDFファイル", "*.pdf")])
                if not output_path:
                    return
        
                page_size = portrait(A4) if self.orientation.get() == "portrait" else landscape(A4)
                c = canvas.Canvas(output_path, pagesize=page_size)
        
                width, height = page_size
                rows, cols = self.rows.get(), self.cols.get()
                cell_width = width / cols
                cell_height = height / rows
        
                images_per_page = rows * cols
                for i, image_name in enumerate(self.image_list):
                    if i % images_per_page == 0 and i != 0:
                        c.showPage()
        
                    img_path = os.path.join(self.folder_path, image_name)
                    img = Image.open(img_path)
                    img.thumbnail((cell_width - 10, cell_height - 30))
        
                    row = (i % images_per_page) // cols
                    col = (i % images_per_page) % cols
                    x = col * cell_width + 5
                    y = height - ((row + 1) * cell_height) + 30
        
                    img_temp_path = "temp_img.jpg"
                    img.convert("RGB").save(img_temp_path)
                    c.drawImage(ImageReader(img), x, y, width=img.width, height=img.height)
        
                    c.setFont("Helvetica", 8)
                    c.drawString(x, y - 10, image_name)
        
                c.save()
                os.remove("temp_img.jpg")
        
            def create_contact_sheet_image(self):
                page_size = portrait(A4) if self.orientation.get() == "portrait" else landscape(A4)
                width, height = [int(x) for x in page_size]
        
                rows, cols = self.rows.get(), self.cols.get()
                cell_width = width // cols
                cell_height = height // rows
        
                sheet = Image.new("RGB", (width, height), "white")
        
                for i, image_name in enumerate(self.image_list[:rows * cols]):
                    img_path = os.path.join(self.folder_path, image_name)
                    img = Image.open(img_path)
                    img.thumbnail((cell_width - 10, cell_height - 30))
                    thumb = Image.new("RGB", (cell_width, cell_height), "white")
                    thumb.paste(img, ((cell_width - img.width) // 2, 5))
        
                    # ファイル名追加
                    from PIL import ImageDraw, ImageFont
                    draw_ctx = ImageDraw.Draw(thumb)
                    font = ImageFont.load_default()
                    draw_ctx.text((5, cell_height - 20), image_name, font=font, fill="black")
        
                    row = i // cols
                    col = i % cols
                    sheet.paste(thumb, (col * cell_width, row * cell_height))
        
                return sheet
        
        if __name__ == "__main__":
            root = tk.Tk()
            app = ContactSheetApp(root)
            root.mainloop()