PDF→TXT変換ツール『VerTextrum』 ソースコード

『VerTextrum』のソースコード全文です:

 
        import tkinter as tk
        from tkinter import filedialog, messagebox
        from PIL import Image, ImageTk
        import fitz  # PyMuPDF
        import os
        import io
        
        class PDFConverterApp:
            def __init__(self, root):
                self.root = root
                self.root.title("PDF→TXT変換 - VerTextrum")
        
                # 状態変数
                self.pdf_path = ""
                self.doc = None
                self.current_page = 0
                self.total_pages = 0
        
                self.create_widgets()
        
                # ウィンドウを閉じる際にPDFを解放
                self.root.protocol("WM_DELETE_WINDOW", self.on_close)
        
            def create_widgets(self):
                # PDF選択
                tk.Button(self.root, text="PDFファイル選択", command=self.select_pdf).grid(row=0, column=0, pady=5)
                self.file_label = tk.Label(self.root, text="ファイル未選択")
                self.file_label.grid(row=0, column=1, columnspan=3)
        
                # サムネイル表示
                self.canvas = tk.Canvas(self.root, width=400, height=500)
                self.canvas.grid(row=1, column=0, columnspan=4, padx=10)
        
                # ページインジケータ
                self.page_label = tk.Label(self.root, text="")
                self.page_label.grid(row=2, column=1)
        
                # 操作ボタン
                tk.Button(self.root, text="<", width=4, command=self.prev_page).grid(row=2, column=0)
                tk.Button(self.root, text=">", width=4, command=self.next_page).grid(row=2, column=3)
        
                # ページジャンプ入力
                tk.Label(self.root, text="ページジャンプ:").grid(row=3, column=0)
                self.jump_entry = tk.Entry(self.root, width=5)
                self.jump_entry.grid(row=3, column=1)
                tk.Button(self.root, text="移動", command=self.jump_to_page).grid(row=3, column=2)
        
                # 範囲と出力
                tk.Label(self.root, text="変換範囲 (例: 1,3,5-7):").grid(row=4, column=0)
                self.range_entry = tk.Entry(self.root)
                self.range_entry.grid(row=4, column=1)
        
                tk.Button(self.root, text="変換", command=self.confirm_and_convert).grid(row=4, column=2)
        
            def select_pdf(self):
                filepath = filedialog.askopenfilename(filetypes=[("PDFファイル", "*.pdf")])
                if filepath:
                    self.pdf_path = filepath
                    self.file_label.config(text=os.path.basename(filepath))
                    if self.doc:
                        self.doc.close()
                    self.doc = fitz.open(filepath)
                    self.total_pages = len(self.doc)
                    self.current_page = 0
                    self.show_thumbnail()
        
            def show_thumbnail(self):
                if not self.doc:
                    return
                try:
                    page = self.doc.load_page(self.current_page)
                    pix = page.get_pixmap(matrix=fitz.Matrix(0.5, 0.5))
                    image = Image.open(io.BytesIO(pix.tobytes("png")))
                    self.imgtk = ImageTk.PhotoImage(image)
                    self.canvas.create_image(200, 250, image=self.imgtk)
                    self.page_label.config(text=f"{self.current_page + 1}/{self.total_pages}")
                except Exception as e:
                    messagebox.showerror("エラー", f"サムネイル表示エラー:\n{str(e)}")
        
            def prev_page(self):
                if self.doc and self.current_page > 0:
                    self.current_page -= 1
                    self.show_thumbnail()
        
            def next_page(self):
                if self.doc and self.current_page < self.total_pages - 1:
                    self.current_page += 1
                    self.show_thumbnail()
        
            def jump_to_page(self):
                if not self.doc:
                    return
                try:
                    page_num = int(self.jump_entry.get()) - 1
                    if 0 <= page_num < self.total_pages:
                        self.current_page = page_num
                        self.show_thumbnail()
                    else:
                        messagebox.showwarning("範囲外", "指定されたページが存在しません。")
                except ValueError:
                    messagebox.showwarning("入力エラー", "有効なページ番号を入力してください。")
        
            def confirm_and_convert(self):
                if not self.doc:
                    messagebox.showerror("エラー", "PDFファイルが選択されていません。")
                    return
        
                if not messagebox.askokcancel("確認", "指定されたページ範囲をTXTに変換します。よろしいですか?"):
                    return
        
                self.convert_range_to_text()
        
            def convert_range_to_text(self):
                range_text = self.range_entry.get().strip()
                pages = []
        
                if not range_text:
                    pages = list(range(self.total_pages))  # 全ページ対象
                else:
                    try:
                        parts = range_text.split(",")
                        for part in parts:
                            if "-" in part:
                                start, end = map(int, part.split("-"))
                                if start < 1 or end > self.total_pages or start > end:
                                    raise ValueError
                                pages.extend(range(start - 1, end))
                            else:
                                page = int(part)
                                if page < 1 or page > self.total_pages:
                                    raise ValueError
                                pages.append(page - 1)
                    except:
                        messagebox.showerror("入力エラー", "変換範囲の指定が不正です。例: 1,3,5-7")
                        return
        
                output_folder = filedialog.askdirectory(title="出力フォルダを選択")
                if not output_folder:
                    return
        
                text = ""
                for i in sorted(set(pages)):
                    text += self.doc.load_page(i).get_text()
        
                base_name = os.path.splitext(os.path.basename(self.pdf_path))[0]
                output_file = os.path.join(output_folder, f"{base_name}_convert.txt")
                try:
                    with open(output_file, "w", encoding="utf-8") as f:
                        f.write(text)
                    messagebox.showinfo("完了", f"変換成功:\n{output_file}")
                except Exception as e:
                    messagebox.showerror("書き込みエラー", f"ファイル保存中にエラーが発生しました:\n{str(e)}")
        
            def on_close(self):
                if self.doc:
                    self.doc.close()
                    self.doc = None
                self.root.destroy()
        
        if __name__ == "__main__":
            root = tk.Tk()
            app = PDFConverterApp(root)
            root.mainloop()