『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()