Files
test/work/001_文件处理/auto_pdf_image_gui.py
T
2026-05-19 17:13:01 +08:00

497 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
图形化自动处理:
1. Word -> PDF -> 图片版PDF
2. PDF -> 图片版PDF
依赖:
pip install pymupdf pywin32
说明:
- 支持 .doc / .docx / .pdf
- 支持一次选择多个文件
- 输出图片版 PDF
- 自动清理临时文件
"""
import os
import time
import shutil
import threading
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
import fitz
import win32com.client
# =========================
# 核心转换函数
# =========================
def word_to_pdf_by_wps(word_path, pdf_path, log_func=print):
"""
WPS 转 PDF
"""
wps = None
doc = None
try:
try:
wps = win32com.client.Dispatch("KWPS.Application")
except Exception:
wps = win32com.client.Dispatch("wps.Application")
wps.Visible = False
doc = wps.Documents.Open(os.path.abspath(word_path))
time.sleep(1)
# 17 = PDF
doc.ExportAsFixedFormat(os.path.abspath(pdf_path), 17)
log_func(f"[OK] Word 转 PDF 完成: {pdf_path}")
finally:
try:
if doc:
doc.Close(False)
except Exception:
pass
try:
if wps:
wps.Quit()
except Exception:
pass
def pdf_to_images(pdf_path, image_dir, zoom=2, log_func=print):
"""
PDF 每页转图片
"""
os.makedirs(image_dir, exist_ok=True)
doc = fitz.open(pdf_path)
image_paths = []
try:
for i in range(len(doc)):
page = doc.load_page(i)
pix = page.get_pixmap(
matrix=fitz.Matrix(zoom, zoom),
alpha=False
)
img_path = os.path.join(
image_dir,
f"page_{i + 1}.png"
)
pix.save(img_path)
image_paths.append(img_path)
log_func(f"[OK] 截图完成: {img_path}")
finally:
doc.close()
return image_paths
def images_to_pdf(image_paths, output_pdf_path, log_func=print):
"""
图片重新合成 PDF
"""
pdf = fitz.open()
try:
for img_path in image_paths:
img = fitz.open(img_path)
try:
rect = img[0].rect
page = pdf.new_page(
width=rect.width,
height=rect.height
)
page.insert_image(rect, filename=img_path)
finally:
img.close()
pdf.save(output_pdf_path, deflate=True)
finally:
pdf.close()
log_func(f"[OK] 图片版 PDF 已生成: {output_pdf_path}")
def safe_delete(path, log_func=print):
"""
删除文件或目录
"""
if not os.path.exists(path):
return
try:
if os.path.isfile(path):
os.remove(path)
log_func(f"[OK] 文件已删除: {path}")
elif os.path.isdir(path):
shutil.rmtree(path)
log_func(f"[OK] 文件夹已删除: {path}")
except Exception as e:
log_func(f"[警告] 删除失败: {path}")
log_func(f"原因: {e}")
def convert_one_file(input_path, output_dir, zoom=2, log_func=print):
"""
处理单个文件
"""
input_path = os.path.abspath(input_path)
if not os.path.exists(input_path):
raise FileNotFoundError(f"文件不存在: {input_path}")
ext = os.path.splitext(input_path)[1].lower()
base_name = os.path.splitext(os.path.basename(input_path))[0]
work_dir = output_dir if output_dir else os.path.dirname(input_path)
os.makedirs(work_dir, exist_ok=True)
image_dir = os.path.join(
work_dir,
base_name + "_images"
)
output_pdf_path = os.path.join(
work_dir,
base_name + "_图片版.pdf"
)
temp_pdf_path = os.path.join(
work_dir,
base_name + "_temp.pdf"
)
generated_temp_pdf = False
try:
if ext in [".doc", ".docx"]:
log_func("=" * 80)
log_func(f"[1] 检测到 Word 文档: {input_path}")
word_to_pdf_by_wps(
input_path,
temp_pdf_path,
log_func
)
pdf_path = temp_pdf_path
generated_temp_pdf = True
elif ext == ".pdf":
log_func("=" * 80)
log_func(f"[1] 检测到 PDF 文件: {input_path}")
pdf_path = input_path
else:
raise ValueError(f"不支持的文件类型: {ext}")
log_func("[2] 正在按页截图...")
image_paths = pdf_to_images(
pdf_path,
image_dir,
zoom=zoom,
log_func=log_func
)
if not image_paths:
raise RuntimeError("PDF 没有成功生成任何图片")
log_func("[3] 正在生成图片版 PDF...")
images_to_pdf(
image_paths,
output_pdf_path,
log_func=log_func
)
log_func("[4] 清理临时文件...")
safe_delete(image_dir, log_func)
if generated_temp_pdf:
safe_delete(temp_pdf_path, log_func)
log_func(f"[完成] 最终文件: {output_pdf_path}")
return output_pdf_path
except Exception as e:
log_func(f"[失败] {input_path}")
log_func(f"原因: {e}")
raise
# =========================
# 图形化界面
# =========================
class PdfImageApp:
def __init__(self, root):
self.root = root
self.root.title("Word / PDF 转图片版 PDF 工具")
self.root.geometry("900x600")
self.selected_files = []
self.output_dir = ""
self.build_ui()
def build_ui(self):
title_label = tk.Label(
self.root,
text="需要本地有wps \nWord / PDF 转图片版 PDF 工具",
font=("微软雅黑", 18, "bold")
)
title_label.pack(pady=10)
desc_label = tk.Label(
self.root,
text="支持 .doc / .docx / .pdf,可一次选择多个文件,自动生成图片版 PDF",
font=("微软雅黑", 10)
)
desc_label.pack()
button_frame = tk.Frame(self.root)
button_frame.pack(pady=10)
select_file_btn = tk.Button(
button_frame,
text="选择文件",
width=18,
command=self.select_files
)
select_file_btn.grid(row=0, column=0, padx=10)
select_output_btn = tk.Button(
button_frame,
text="选择输出目录",
width=18,
command=self.select_output_dir
)
select_output_btn.grid(row=0, column=1, padx=10)
start_btn = tk.Button(
button_frame,
text="开始转换",
width=18,
bg="#4CAF50",
fg="white",
command=self.start_convert
)
start_btn.grid(row=0, column=2, padx=10)
clear_btn = tk.Button(
button_frame,
text="清空日志",
width=18,
command=self.clear_log
)
clear_btn.grid(row=0, column=3, padx=10)
self.file_label = tk.Label(
self.root,
text="当前未选择文件",
fg="blue",
wraplength=850,
justify="left"
)
self.file_label.pack(pady=5)
self.output_label = tk.Label(
self.root,
text="输出目录:默认输出到原文件所在目录",
fg="purple",
wraplength=850,
justify="left"
)
self.output_label.pack(pady=5)
zoom_frame = tk.Frame(self.root)
zoom_frame.pack(pady=5)
# tk.Label(
# zoom_frame,
# text="清晰度 zoom"
# ).pack(side=tk.LEFT)
#
# self.zoom_var = tk.StringVar(value="2")
#
# zoom_entry = tk.Entry(
# zoom_frame,
# textvariable=self.zoom_var,
# width=8
# )
# zoom_entry.pack(side=tk.LEFT)
#
# tk.Label(
# zoom_frame,
# text="建议:2 正常,3 更清晰但文件更大"
# ).pack(side=tk.LEFT, padx=10)
self.log_box = scrolledtext.ScrolledText(
self.root,
width=110,
height=25,
font=("Consolas", 10)
)
self.log_box.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
def log(self, msg):
self.log_box.insert(tk.END, msg + "\n")
self.log_box.see(tk.END)
self.root.update_idletasks()
def clear_log(self):
self.log_box.delete("1.0", tk.END)
def select_files(self):
files = filedialog.askopenfilenames(
title="请选择 Word 或 PDF 文件",
filetypes=[
("支持的文件", "*.doc *.docx *.pdf"),
("Word 文件", "*.doc *.docx"),
("PDF 文件", "*.pdf"),
("所有文件", "*.*")
]
)
if files:
self.selected_files = list(files)
self.file_label.config(
text=f"已选择 {len(self.selected_files)} 个文件:\n" +
"\n".join(self.selected_files)
)
self.log(f"[选择文件] 共选择 {len(self.selected_files)} 个文件")
def select_output_dir(self):
output_dir = filedialog.askdirectory(
title="请选择输出目录"
)
if output_dir:
self.output_dir = output_dir
self.output_label.config(
text=f"输出目录:{self.output_dir}"
)
self.log(f"[输出目录] {self.output_dir}")
def start_convert(self):
if not self.selected_files:
messagebox.showwarning(
"提示",
"请先选择一个或多个 Word / PDF 文件"
)
return
try:
zoom = float(self.zoom_var.get())
if zoom <= 0:
raise ValueError
except Exception:
messagebox.showwarning(
"提示",
"zoom 必须是大于 0 的数字,例如 2 或 3"
)
return
t = threading.Thread(
target=self.convert_files_thread,
args=(zoom,),
daemon=True
)
t.start()
def convert_files_thread(self, zoom):
success_files = []
failed_files = []
self.log("")
self.log("========== 开始批量转换 ==========")
for index, file_path in enumerate(self.selected_files, start=1):
self.log("")
self.log(f"[任务 {index}/{len(self.selected_files)}] {file_path}")
try:
output_pdf = convert_one_file(
input_path=file_path,
output_dir=self.output_dir,
zoom=zoom,
log_func=self.log
)
success_files.append(output_pdf)
except Exception as e:
failed_files.append((file_path, str(e)))
self.log("")
self.log("========== 批量转换结束 ==========")
self.log(f"[成功] {len(success_files)}")
self.log(f"[失败] {len(failed_files)}")
if success_files:
self.log("")
self.log("生成的图片版 PDF")
for f in success_files:
self.log(f)
if failed_files:
self.log("")
self.log("失败文件:")
for f, reason in failed_files:
self.log(f"{f} -> {reason}")
if failed_files:
messagebox.showwarning(
"转换完成",
f"处理完成。\n\n成功:{len(success_files)}\n失败:{len(failed_files)}\n\n请查看日志。\n\n请使用生成的“图片版 PDF”文件。"
)
else:
messagebox.showinfo(
"转换完成",
f"全部处理完成。\n\n成功生成 {len(success_files)} 个图片版 PDF。\n\n请使用生成的“图片版 PDF”文件。"
)
def main():
root = tk.Tk()
app = PdfImageApp(root)
root.mainloop()
if __name__ == "__main__":
main()