import math import os import platform import random import subprocess import sys from tkinter import Button, Label, Tk, Toplevel, filedialog from tkinter.filedialog import askdirectory, askopenfiles from fpdf import FPDF from PIL import Image from pypdf import PdfReader, PdfWriter, Transformation def get_os(): return platform.system() def check_output(path): dir_name = os.path.dirname(path) if False == os.path.exists(dir_name): pass # 创建文件夹 os.mkdir(dir_name) def img_to_pdf(img_path, out_path="", x=0, y=0, w=100, h=100): """图片转PDF""" if out_path == "": dir_name = os.path.dirname(img_path) basename = os.path.basename(img_path) name, _ = os.path.splitext(basename) out_path = os.path.join(dir_name, name + ".pdf") pdf = FPDF() pdf.add_page() pdf.image(img_path, x=x, y=y, w=w, h=h) pdf.output(out_path, "F") return out_path def add_image_watermark(input_path, watermark_path, output_path): """图片添加印章""" # 打开原始图片和要添加的水印图片 original_img = Image.open(input_path).convert("RGBA") watermark_img = Image.open(watermark_path).convert("RGBA") # 获取原始图片 original_width, original_height = original_img.size water_width = water_height = math.ceil(38 / 210 * original_width) # 将水印图片缩放到与原始图片相同的大小 watermark_img = watermark_img.resize((water_width, water_height)) watermark_img = watermark_img.rotate(random.uniform(-180, 180)) # 合并原始图片和水印图片,并将结果转换为RGB模式 original_img.paste( watermark_img, ( math.ceil(original_width - original_width / 2.6), math.ceil(original_height - original_height / 2.6), ), watermark_img, ) # 保存水印后的图片 original_img.save(output_path) def add_pdf_watermark(intput_file, watermark_path, output_path): """PDF添加印章""" stamp = PdfReader(watermark_path).pages[0] writer = PdfWriter(clone_from=intput_file) for page in writer.pages: page.merge_transformed_page( stamp, Transformation().translate(980, 60).scale(0.37) ) writer.write(output_path) class CustomMessageBox(Toplevel): def __init__(self, master, text, width=400, height=100): super().__init__(master) self.transient(master) self.title("Error") self.label = Label(self, text=text) self.label.pack(padx=20, pady=20) # 设置了宽和高 self.width = width self.height = height self.geometry(f"{width}x{height}+{master.winfo_x()}+{master.winfo_y()}") self.grab_set() # 使得对话框不会在点击窗口之外的地方时关闭 self.wait_window(self) # 等待对话框关闭 if __name__ == "__main__": multi_files = [] # 多选的文件 dir_files = [] # 文件夹中的文件 water_path = "" # 印章路径 tmp_water = "" # 当为图片时,删除临时生成的PDF印章 last_stamped = "" # 最后一个添加印章后的文件 print(print(get_os())) def reset(): global multi_files, dir_files, water_path, tmp_water multi_files = [] dir_files = [] water_path = "" tmp_water = "" lable_dir.config(text="") lable_files.config(text="") lable_stamp.config(text="") label_create.config(text="") button_create.config(state="disabled") button_opendir.place_forget() button_openfile.place_forget() def on_closing(): root.destroy() root.quit() exit() def check_status(): # 如果文件和水印都选择便启用水印生成按钮,否则禁用 if ( lable_files.cget("text") != "" or lable_dir.cget("text") != "" ) and lable_stamp.cget("text") != "": button_create.config(state="normal") else: button_create.config(state="disabled") def select_files(extensions=["pdf", "png", "jpeg", "jpg"]): Tk().withdraw() files = askopenfiles() # 检测文件类型 for item in files: file_extension = item.name.split(".")[-1].lower() if file_extension not in extensions: CustomMessageBox(root, "无效的文件类型。") select_files() return paths = [(item.name) for item in files] global multi_files multi_files = paths str = ",".join(paths) if len(str) >= 50: str = str[:50] + "..." lable_files.config(text=str) check_status() def select_dir(): root = Tk() root.withdraw() folder_path = askdirectory() items = recursive_walk(folder_path) global dir_files dir_files = items if len(folder_path) >= 50: folder_path = str[:50] + "..." lable_dir.config(text=folder_path) check_status() def select_file(label, extensions=["png", "jpeg", "jpg"]): Tk().withdraw() file_path = filedialog.askopenfilename() if file_path: file_extension = file_path.split(".")[-1].lower() if file_extension in extensions: # 隐藏操作按钮 button_opendir.place_forget() button_openfile.place_forget() if len(file_path) >= 50: file_path = str[:50] + "..." label.config(text=file_path) # 检查水印生成按键可用状态 check_status() else: CustomMessageBox(root, "无效的文件类型。") select_file() def recursive_walk(dir_path): file_list = [] # 遍历指定文件夹 for root, dirs, files in os.walk(dir_path): for file in files: # 检查文件是否是PDF或图片 if ( file.endswith(".pdf") or file.endswith(".png") or file.endswith(".jpg") or file.endswith(".jpeg") ): # 如果是,则将其完整路径添加到列表中 file_list.append(os.path.join(root, file)) return file_list def add_watermask(file_path, water_path): baseaname = os.path.basename(file_path) name, suffix = os.path.splitext(baseaname) dir_path = os.path.dirname(file_path) # # 判断目标文件是图片还是PDF if suffix.lower() == ".pdf": global tmp_water # 如果水印是图片,先转成PDF if tmp_water == "": if water_path.split(".")[-1].lower() != "pdf": tmp_water = img_to_pdf(water_path) out_path = os.path.join(dir_path, "output", name + "_watered" + suffix) check_output(out_path) add_pdf_watermark(file_path, tmp_water, out_path) else: out_path = os.path.join(dir_path, "output", name + "_watered" + ".png") check_output(out_path) add_image_watermark(file_path, water_path, out_path) # 显示操作按钮 global last_stamped last_stamped = out_path label_create.config(text="生成成功:") label_create.place(x=120, y=4 * padding_y, width=80, height=lable_height) button_openfile.place( x=200, y=4 * padding_y, width=button_width, height=button_height ) button_opendir.place( x=280, y=4 * padding_y, width=button_width, height=button_height ) def multi_watermask(): global multi_files, dir_files, tmp_water water_path = lable_stamp.cget("text") selected_files = list(set(multi_files + dir_files)) for item in selected_files: add_watermask(item, water_path) # 删除临时PDF印章 if tmp_water != "": os.remove(tmp_water) tmp_water = "" def open_dirname(): global last_stamped dir_name = os.path.dirname(last_stamped) if get_os == "Windows": os.startfile(dir_name) else: subprocess.run(["open", dir_name]) def open_file(): global last_stamped if get_os == "Windows": os.startfile(last_stamped) else: subprocess.run(["open", last_stamped]) def get_path(relative_path): try: base_path = sys._MEIPASS except AttributeError: base_path = os.path.abspath(".") return os.path.normpath(os.path.join(base_path, relative_path)) # 主窗口 button_width = 70 button_height = 26 label_width = 300 lable_height = 26 button_x = 50 label_x = 150 button_y = 100 padding_y = 40 root = Tk() root.title("印章助手") root.resizable(False, False) root.protocol("WM_DELETE_WINDOW", on_closing) # 获取屏幕宽度和高度 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() # 计算窗口的理想位置 x_pos = int((screen_width - 500) / 2) y_pos = int((screen_height - 300) / 2) # 设置窗口位置 root.geometry("{}x{}+{}+{}".format(500, 300, x_pos, y_pos)) # 选择文件-多选 button_files = Button(root, text="选择文件", command=select_files) button_files.place( x=button_x, y=padding_y, width=button_width, height=button_height ) lable_files = Label(root, text="") lable_files.place(x=label_x, y=padding_y, width=label_width, height=lable_height) # 选择目录 button_dir = Button(root, text="选择目录", command=select_dir) button_dir.place( x=button_x, y=2 * padding_y, width=button_width, height=button_height ) lable_dir = Label(root, text="") lable_dir.place(x=label_x, y=2 * padding_y, width=label_width, height=lable_height) # 选择公章 button_stamp = Button( root, text="选择公章", command=lambda: select_file(lable_stamp, extensions=["png", "jpeg", "jpg"]), ) button_stamp.place( x=button_x, y=3 * padding_y, width=button_width, height=button_height ) lable_stamp = Label(root, text=get_path("waters/stamp.png")) lable_stamp.place( x=label_x, y=3 * padding_y, width=label_width, height=lable_height ) # 预览 label_create = Label(root, text="") button_opendir = Button(root, text="目录", command=open_dirname) button_openfile = Button(root, text="预览", command=open_file) # 添加水印 button_create = Button(root, text="生成", command=multi_watermask, state="disabled") button_create.place( x=120, y=6 * padding_y, width=button_width, height=button_height ) # 重置 button_reset = Button(root, text="重置", command=reset) button_reset.place(x=320, y=6 * padding_y, width=button_width, height=button_height) root.mainloop()