PDF批量加水印工具

查看 65|回复 11
作者:pythonfun   
一、软件名称:PDF批量水印添加工具  
二、软件功能: 本软件是一款便捷的PDF文档处理工具,主要功能包括:  
1. 支持批量对指定文件夹中的PDF文件添加自定义文字水印  
2. 可灵活调整水印文字内容、字体大小、旋转角度、透明度等参数  
3. 支持设置水印的行间距和列间距,控制水印在页面中的分布密度  
4. 采用多线程处理技术,避免界面冻结,同时实时显示处理进度和日志信息  
三、使用方法:  
运行程序后会出现一个图形化操作窗口:  
1. 通过"选择"按钮分别指定PDF文件夹和输出文件夹路径  
2. 在水印设置区域输入水印文字,并调整字体大小、角度、间距和透明度等参数  
3. 点击"开始加水印"按钮启动处理过程,可通过进度条和日志框查看实时处理状态  
4. 处理完成后可在输出文件夹中找到添加了水印的PDF文件(文件名后缀为_watermarked.pdf)  
5. 如需中断处理可点击"重置服务"按钮
四、软件截图



五、源码展示:
[Python] 纯文本查看 复制代码import os
import threading
import queue
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import tkinter.font as tkfont
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:
            
            # 注册中文字体(只需注册一次,可以放外面,但放这里也没问题)
            pdfmetrics.registerFont(TTFont('SimSun', 'simsun.ttc'))
            # 创建临时水印 PDF
            watermark_pdf = os.path.join(self.output_folder, "watermark_temp.pdf")
            c = canvas.Canvas(watermark_pdf, pagesize=letter)
            width, height = letter
            c.setFont("SimSun", self.font_size)  # 用宋体支持中文
            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[0])
                writer.add_page(page)
            out_pdf = os.path.join(
                self.output_folder,
                os.path.splitext(os.path.basename(pdf_path))[0] + "_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加水印工具-pythonfun制作')
        self.root.geometry('600x520')
        # 修改全局默认字体为 宋体 11号
        default_font = tkfont.nametofont("TkDefaultFont")
        default_font.configure(family="宋体", size=11)
        # 让 ttk 里的控件也跟随默认字体
        root.option_add("*Font", default_font)
        main_frame = ttk.Frame(self.root, padding="10")
        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)
        # 文件夹选择
        folder_group = ttk.LabelFrame(main_frame, text="文件夹选择")
        folder_group.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        folder_group.columnconfigure(1, weight=1)
        ttk.Label(folder_group, text="PDF文件夹:").grid(row=0, column=0, sticky=tk.W)
        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)
        ttk.Button(folder_group, text="选择", command=self.browse_pdf_folder).grid(row=0, column=2, padx=5)
        ttk.Label(folder_group, text="输出文件夹:").grid(row=1, column=0, sticky=tk.W)
        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)
        ttk.Button(folder_group, text="选择", command=self.browse_output_folder).grid(row=1, column=2, padx=5)
        # 水印设置
        watermark_group = ttk.LabelFrame(main_frame, text="水印设置", padding="5")
        watermark_group.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        # 第1行:水印文字
        ttk.Label(watermark_group, text="水印文字:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.watermark_var = tk.StringVar(value="科技有限公司")
        ttk.Entry(watermark_group, textvariable=self.watermark_var, width=40).grid(row=0, column=1, columnspan=3, sticky=(tk.W, tk.E), padx=5, pady=5)
        # 第2行:字体大小 + 角度
        ttk.Label(watermark_group, text="字体大小:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
        self.font_size_var = tk.IntVar(value=42)
        ttk.Spinbox(watermark_group, from_=10, to=100, textvariable=self.font_size_var, width=10).grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)
        ttk.Label(watermark_group, text="角度:").grid(row=1, column=2, sticky=tk.W, padx=5, pady=5)
        self.angle_var = tk.IntVar(value=45)
        ttk.Spinbox(watermark_group, from_=0, to=90, textvariable=self.angle_var, width=10).grid(row=1, column=3, sticky=tk.W, padx=5, pady=5)
        # 第3行:行间距 + 列间距
        ttk.Label(watermark_group, text="行间距:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=5)
        self.spacing_var = tk.IntVar(value=80)
        ttk.Spinbox(watermark_group, from_=20, to=200, textvariable=self.spacing_var, width=10).grid(row=2, column=1, sticky=tk.W, padx=5, pady=5)
        ttk.Label(watermark_group, text="列间距:").grid(row=2, column=2, sticky=tk.W, padx=5, pady=5)
        self.column_var = tk.IntVar(value=500)
        ttk.Spinbox(watermark_group, from_=300, to=800, increment=50, textvariable=self.column_var, width=10).grid(row=2, column=3, sticky=tk.W, padx=5, pady=5)
        # 第4行:透明度
        ttk.Label(watermark_group, text="透明度:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
        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=10).grid(row=3, column=1, sticky=tk.W, padx=5, pady=5)
               
        # 进度条
        self.progress_var = tk.IntVar()
        self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, maximum=100)
        self.progress_bar.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        #self.progress_bar.grid_remove()
        # 日志
        log_frame = ttk.LabelFrame(main_frame, text="日志", padding="5")
        log_frame.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
        self.log_text = scrolledtext.ScrolledText(log_frame, width=70, height=8)
        self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        self.log_text.config(state=tk.DISABLED)
        # 按钮
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=4, column=0, columnspan=2, pady=10)
        self.start_btn = ttk.Button(button_frame, text="开始加水印", command=self.start_watermark)
        self.start_btn.pack(side=tk.LEFT, padx=5)
        self.stop_btn = ttk.Button(button_frame, text="重置服务", command=self.stop_watermark, state=tk.DISABLED)
        self.stop_btn.pack(side=tk.LEFT, padx=5)
        self.status_var = tk.StringVar(value="就绪")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.grid(row=1, column=0, sticky=(tk.W, tk.E))
    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.log_message(message)
        except queue.Empty:
            pass
        finally:
            self.root.after(100, self.check_queue)
if __name__ == '__main__':
    root = tk.Tk()
    app = PDFWatermarkApp(root)
    root.mainloop()
六、成品下载载地址:
https://wwkg.lanzouq.com/iHFtw35hsq0f

水印, 文件夹

Yangyang0403   

请问成品怎么打包成exe呢?
tutu2   

没有预览功能特别不方便  但是这个python吧 加上预览功能风扇就呼呼转   在处理PDF方面   pyhton还是差一点
tenxunshipin   

哇更加方便了
mmdhh696   

好的,谢谢大佬
ajay1995   

方便,感谢大佬
qck   

感谢分享
wu1357924680   

非常实用的一个功能
zlm6666   

感觉不错,试试看
stlcb   

真方便,谢谢分享
您需要登录后才可以回帖 登录 | 立即注册

返回顶部