word转pdf
This commit is contained in:
@@ -0,0 +1,498 @@
|
||||
"""
|
||||
图形化自动处理:
|
||||
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()
|
||||
Reference in New Issue
Block a user