PDF批量水印工具 - 轻松为多份PDF文档添加自定义水印支持字体大小、间距、透明度调节
本帖最后由 china365love 于 2025-10-16 18:16 编辑PDF批量水印工具是一款专业、高效的文档保护软件,专为需要批量处理PDF文件的用户设计。
主要功能与特点:
- 批量处理:一次操作可处理整个文件夹中的所有PDF文件,大幅提高工作效率
- 自定义水印:支持自由设置水印文字内容,默认预设为"科技有限公司"
- 丰富的水印参数调节:可自定义字体大小(10-100)、旋转角度(0-90度)、行间距(20-200)、列间距(300-800)和透明度(0.1-0.9)
- 多线程处理:采用后台线程处理机制,避免界面卡顿,保证操作流畅
- 友好的用户界面:简洁明了的界面设计,操作直观,无需专业知识
- 完整的处理日志:实时显示处理进度和结果,便于追踪
- 无需安装:绿色免安装版本,解压即可使用,支持在多台电脑间移植使用
适用场景:
- 企业文档版权保护:为公司文档添加公司名称、版权声明等水印
- 机密文档标记:为敏感文档添加"机密"、"内部使用"等标记
- 电子文档批量处理:需要处理大量PDF文件的办公场景
- 个人文档个性化:为个人文档添加个性化水印标识
import os
import threading
import queue
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import tkinter.font as tkfont
import webbrowser
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from PyPDF2 import PdfReader, PdfWriter
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
class WatermarkThread(threading.Thread):
"""加水印线程,避免界面冻结"""
def __init__(self, pdf_folder, output_folder, watermark_text, font_size, angle, spacing, alpha, column,progress_queue):
super().__init__()
self.pdf_folder = pdf_folder
self.output_folder = output_folder
self.watermark_text = watermark_text
self.font_size = font_size
self.angle = angle
self.spacing = spacing
self.alpha = alpha
self.column = column
self.progress_queue = progress_queue
self.is_running = True
self.daemon = True
def run(self):
total_files = 0
processed_files = 0
# 计算 PDF 文件总数
if self.pdf_folder:
for filename in os.listdir(self.pdf_folder):
if filename.lower().endswith(".pdf"):
total_files += 1
# 遍历文件并处理
if self.pdf_folder:
for filename in os.listdir(self.pdf_folder):
if not self.is_running:
break
if filename.lower().endswith(".pdf"):
pdf_path = os.path.join(self.pdf_folder, filename)
processed_files += 1
progress = int((processed_files / total_files) * 100) if total_files > 0 else 0
self.progress_queue.put((progress, f"处理中: {filename}"))
watermarked_pdf = self.add_watermark(pdf_path)
if watermarked_pdf:
self.progress_queue.put((progress, f"已添加水印: {watermarked_pdf}"))
else:
self.progress_queue.put((progress, f"处理失败: {filename}"))
self.progress_queue.put((100, "全部完成!"))
def stop_process(self):
"""点击停止按钮时调用"""
self.is_running = False
def add_watermark(self, pdf_path):
"""在 PDF 中添加水印"""
try:
# 创建临时水印 PDF
watermark_pdf = os.path.join(self.output_folder, "watermark_temp.pdf")
c = canvas.Canvas(watermark_pdf, pagesize=letter)
width, height = letter
# 尝试注册中文字体,如果失败则使用默认字体
try:
pdfmetrics.registerFont(TTFont('SimSun', 'simsun.ttc'))
c.setFont("SimSun", self.font_size)# 用宋体支持中文
except:
# 使用reportlab默认字体
c.setFont("Helvetica", self.font_size)
self.progress_queue.put((0, "警告:无法加载中文字体,将使用默认字体"))
c.setFillColor(colors.grey, alpha=self.alpha)# 淡灰色 + 透明度
c.saveState()
c.rotate(self.angle)
# 行列循环打印水印
row_spacing = self.spacing # 行间距(上下)
col_spacing = self.column # 列间距(左右),可以做成参数
for y in range(-500, int(height * 2), row_spacing):
for x in range(-500, int(width * 2), col_spacing):
c.drawString(x, y, self.watermark_text)
c.restoreState()
c.save()
# 合并水印和原 PDF
reader = PdfReader(pdf_path)
writer = PdfWriter()
wm_reader = PdfReader(watermark_pdf)
for page in reader.pages:
page.merge_page(wm_reader.pages)
writer.add_page(page)
out_pdf = os.path.join(
self.output_folder,
os.path.splitext(os.path.basename(pdf_path)) + "_watermarked.pdf"
)
out_pdf = os.path.normpath(out_pdf)
with open(out_pdf, "wb") as f:
writer.write(f)
os.remove(watermark_pdf)# 删除临时文件
return out_pdf
except Exception as e:
self.progress_queue.put((0, f"加水印失败: {e}"))
return None
class PDFWatermarkApp:
def __init__(self, root):
self.root = root
self.watermark_thread = None
self.progress_queue = queue.Queue()
self.initUI()
self.check_queue()
def initUI(self):
# 设置窗口标题和大小
self.root.title('PDF批量加水印工具 - 大飞哥软件自习室')
self.root.geometry('700x600')
self.root.minsize(650, 550)
# 设置窗口图标(如果有logo.ico文件)
try:
self.root.iconbitmap("logo.ico")
except:
pass
# 创建样式
self.style = ttk.Style()
self.style.theme_use("clam")# 使用现代风格
# 自定义样式
self.style.configure("TFrame", background="#f0f0f0")
self.style.configure("TLabel", background="#f0f0f0", font=("微软雅黑", 10))
self.style.configure("TLabelframe", background="#f0f0f0", font=("微软雅黑", 11, "bold"))
self.style.configure("TLabelframe.Label", background="#f0f0f0", font=("微软雅黑", 11, "bold"))
self.style.configure("TButton", font=("微软雅黑", 10))
self.style.configure("TEntry", font=("微软雅黑", 10))
self.style.configure("TSpinbox", font=("微软雅黑", 10))
self.style.configure("TProgressbar", troughcolor="#e0e0e0", background="#4a90e2")
# 修改全局默认字体
default_font = tkfont.nametofont("TkDefaultFont")
default_font.configure(family="微软雅黑", size=10)
# 创建主框架
main_frame = ttk.Frame(self.root, padding="15")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置窗口网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(3, weight=1)
# 文件夹选择
folder_group = ttk.LabelFrame(main_frame, text="📁 文件夹设置")
folder_group.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=10, padx=5)
folder_group.columnconfigure(1, weight=1)
ttk.Label(folder_group, text="PDF文件夹: ").grid(row=0, column=0, sticky=tk.W, pady=5)
self.pdf_folder_var = tk.StringVar()
pdf_entry = ttk.Entry(folder_group, textvariable=self.pdf_folder_var, width=50)
pdf_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5, pady=5)
browse_pdf_btn = ttk.Button(folder_group, text="浏览...", command=self.browse_pdf_folder)
browse_pdf_btn.grid(row=0, column=2, padx=5, pady=5)
self.style.configure("Browse.TButton", foreground="#0066cc")
browse_pdf_btn.config(style="Browse.TButton")
ttk.Label(folder_group, text="输出文件夹: ").grid(row=1, column=0, sticky=tk.W, pady=5)
self.output_folder_var = tk.StringVar()
output_entry = ttk.Entry(folder_group, textvariable=self.output_folder_var, width=50)
output_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=5, pady=5)
browse_output_btn = ttk.Button(folder_group, text="浏览...", command=self.browse_output_folder)
browse_output_btn.grid(row=1, column=2, padx=5, pady=5)
browse_output_btn.config(style="Browse.TButton")
# 水印设置
watermark_group = ttk.LabelFrame(main_frame, text="🔤 水印参数设置", padding="10")
watermark_group.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=10, padx=5)
# 配置网格权重
watermark_group.columnconfigure(1, weight=1)
watermark_group.columnconfigure(3, weight=1)
# 第1行:水印文字
ttk.Label(watermark_group, text="水印文字: ", font=("微软雅黑", 10, "bold")).grid(row=0, column=0, sticky=tk.W, padx=10, pady=8)
self.watermark_var = tk.StringVar(value="科技有限公司")
watermark_entry = ttk.Entry(watermark_group, textvariable=self.watermark_var, width=40, font=("微软雅黑", 10))
watermark_entry.grid(row=0, column=1, columnspan=3, sticky=(tk.W, tk.E), padx=10, pady=8)
watermark_entry.config(foreground="#333333")
# 第2行:字体大小 + 角度
ttk.Label(watermark_group, text="字体大小: ").grid(row=1, column=0, sticky=tk.W, padx=10, pady=8)
self.font_size_var = tk.IntVar(value=42)
ttk.Spinbox(watermark_group, from_=10, to=100, textvariable=self.font_size_var, width=12).grid(row=1, column=1, sticky=tk.W, padx=10, pady=8)
ttk.Label(watermark_group, text="旋转角度: ").grid(row=1, column=2, sticky=tk.W, padx=10, pady=8)
self.angle_var = tk.IntVar(value=45)
ttk.Spinbox(watermark_group, from_=0, to=90, textvariable=self.angle_var, width=12).grid(row=1, column=3, sticky=tk.W, padx=10, pady=8)
# 第3行:行间距 + 列间距
ttk.Label(watermark_group, text="行间距: ").grid(row=2, column=0, sticky=tk.W, padx=10, pady=8)
self.spacing_var = tk.IntVar(value=80)
ttk.Spinbox(watermark_group, from_=20, to=200, textvariable=self.spacing_var, width=12).grid(row=2, column=1, sticky=tk.W, padx=10, pady=8)
ttk.Label(watermark_group, text="列间距: ").grid(row=2, column=2, sticky=tk.W, padx=10, pady=8)
self.column_var = tk.IntVar(value=500)
ttk.Spinbox(watermark_group, from_=300, to=800, increment=50, textvariable=self.column_var, width=12).grid(row=2, column=3, sticky=tk.W, padx=10, pady=8)
# 第4行:透明度
ttk.Label(watermark_group, text="透明度: ").grid(row=3, column=0, sticky=tk.W, padx=10, pady=8)
self.alpha_var = tk.DoubleVar(value=0.2)
ttk.Spinbox(watermark_group, from_=0.1, to=0.9, increment=0.1, textvariable=self.alpha_var, width=12).grid(row=3, column=1, sticky=tk.W, padx=10, pady=8)
# 进度条和状态显示
progress_frame = ttk.Frame(main_frame, padding="5")
progress_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
progress_frame.columnconfigure(1, weight=1)
ttk.Label(progress_frame, text="处理进度: ").grid(row=0, column=0, sticky=tk.W, padx=5)
self.progress_var = tk.IntVar()
self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100, length=400)
self.progress_bar.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5)
self.progress_label = ttk.Label(progress_frame, text="0%")
self.progress_label.grid(row=0, column=2, sticky=tk.W, padx=5)
# 日志
log_frame = ttk.LabelFrame(main_frame, text="📋 处理日志", padding="10")
log_frame.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10)
log_frame.columnconfigure(0, weight=1)
log_frame.rowconfigure(0, weight=1)
self.log_text = scrolledtext.ScrolledText(log_frame, width=70, height=10, font=("微软雅黑", 9), wrap=tk.WORD)
self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.log_text.config(state=tk.DISABLED, bg="#ffffff", fg="#333333", insertbackground="#333333")
# 按钮
button_frame = ttk.Frame(main_frame, padding="10")
button_frame.grid(row=4, column=0, columnspan=2, pady=5)
button_frame.columnconfigure(0, weight=1)
button_frame.columnconfigure(1, weight=1)
# 自定义按钮样式
self.style.configure("Start.TButton", font=("微软雅黑", 11, "bold"), foreground="#ffffff", background="#4CAF50")
self.style.configure("Stop.TButton", font=("微软雅黑", 11), foreground="#ffffff", background="#f44336")
self.start_btn = ttk.Button(button_frame, text="🚀 开始加水印", command=self.start_watermark, style="Start.TButton")
self.start_btn.grid(row=0, column=0, padx=10, pady=5, sticky=(tk.E))
self.stop_btn = ttk.Button(button_frame, text="🔄 重置", command=self.stop_watermark, state=tk.DISABLED, style="Stop.TButton")
self.stop_btn.grid(row=0, column=1, padx=10, pady=5, sticky=(tk.W))
# 状态栏
status_frame = ttk.Frame(self.root, padding="5")
status_frame.grid(row=1, column=0, sticky=(tk.W, tk.E))
status_frame.columnconfigure(0, weight=1)
self.status_var = tk.StringVar(value="准备就绪")
status_bar = ttk.Label(status_frame, textvariable=self.status_var, font=("微软雅黑", 9), anchor=tk.W)
status_bar.grid(row=0, column=0, sticky=(tk.W, tk.E))
# B站链接
self.bilibili_label = ttk.Label(status_frame, text="大飞哥软件自习室荣誉出品", font=("微软雅黑", 9, "bold"), foreground="#ff6b6b", cursor="hand2")
self.bilibili_label.grid(row=0, column=1, padx=10, sticky=tk.E)
self.bilibili_label.bind("<Button-1>", self.open_bilibili_link)
self.bilibili_label.bind("<Enter>", lambda e: self.bilibili_label.configure(foreground="#ff3333", underline=True))
self.bilibili_label.bind("<Leave>", lambda e: self.bilibili_label.configure(foreground="#ff6b6b", underline=False))
def browse_pdf_folder(self):
path = filedialog.askdirectory(title="选择PDF文件夹", initialdir=".")
if path:
self.pdf_folder_var.set(path)
def browse_output_folder(self):
path = filedialog.askdirectory(title="选择输出文件夹", initialdir=".")
if path:
self.output_folder_var.set(path)
def log_message(self, message):
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
self.status_var.set(message)
def start_watermark(self):
if not self.pdf_folder_var.get() or not self.output_folder_var.get():
messagebox.showwarning("警告", "请选择文件夹")
return
if not os.path.exists(self.output_folder_var.get()):
os.makedirs(self.output_folder_var.get())
self.start_btn.config(state=tk.DISABLED)
self.stop_btn.config(state=tk.NORMAL)
self.progress_bar.grid()
self.progress_var.set(0)
self.watermark_thread = WatermarkThread(
self.pdf_folder_var.get(),
self.output_folder_var.get(),
self.watermark_var.get(),
self.font_size_var.get(),
self.angle_var.get(),
self.spacing_var.get(),
self.alpha_var.get(),
self.column_var.get(),
self.progress_queue
)
self.watermark_thread.start()
def stop_watermark(self):
if self.watermark_thread and self.watermark_thread.is_alive():
self.watermark_thread.stop_process() # 调用线程的停止方法
self.watermark_thread.join()
self.log_message("已停止")
# 重置 UI
self.start_btn.config(state=tk.NORMAL)
self.stop_btn.config(state=tk.DISABLED)
self.progress_var.set(0)
self.status_var.set("准备就绪")
def check_queue(self):
try:
while True:
progress, message = self.progress_queue.get_nowait()
self.progress_var.set(progress)
self.progress_label.config(text=f"{progress}%")
self.log_message(message)
except queue.Empty:
pass
finally:
self.root.after(100, self.check_queue)
def open_bilibili_link(self, event):
"""打开B站链接"""
webbrowser.open("https://space.bilibili.com/286436365")
if __name__ == '__main__':
root = tk.Tk()
app = PDFWatermarkApp(root)
root.mainloop()
使用方法简单,只需选择源文件夹、输出文件夹,设置水印参数,点击开始按钮即可完成批量处理,是您处理PDF文档的得力助手。
成品链接自行研究:https://owmei.lanzoum.com/iJ5aJ38l2lja
PYG有你更精彩! 沙发~!
好东东~!
谢谢分享~! 谢谢楼主提供分享下载玩玩
PYG有你更精彩!膜拜大佬
页:
[1]