Python小工具之PDF合并

Python小工具之PDF合并

1、Python小工具(PDF合并代码)

python 复制代码
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
from PyPDF2 import PdfMerger,PdfReader
from threading import Thread
from PIL import Image, ImageTk 


class PDFMergerGUI:
    def __init__(self, master):
        self.master = master
        master.title("PDF合并工具 v2.0")
        master.geometry("800x600")

        # 设置窗口图标
        self.set_window_icon()

        # 创建界面元素
        self.create_widgets()

        # 初始化变量
        self.files = []
        self.merging = False

    def create_widgets(self):
        # 文件列表框架
        list_frame = ttk.LabelFrame(self.master, text="待合并文件列表")
        list_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)

        # 文件列表
        self.tree = ttk.Treeview(
            list_frame,
            columns=("path", "pages"),
            show="headings",
            selectmode="extended"
        )
        self.tree.heading("path", text="文件路径")
        self.tree.heading("pages", text="页数")
        self.tree.column("path", width=500)
        self.tree.column("pages", width=80)
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 滚动条
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.tree.configure(yscrollcommand=scrollbar.set)

        # 操作按钮框架
        btn_frame = ttk.Frame(self.master)
        btn_frame.pack(pady=5, fill=tk.X)

        ttk.Button(btn_frame, text="添加文件", command=self.add_files).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="移除选中", command=self.remove_files).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="上移", command=lambda: self.move_items(-1)).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="下移", command=lambda: self.move_items(1)).pack(side=tk.LEFT, padx=5)

        # 输出路径框架
        output_frame = ttk.Frame(self.master)
        output_frame.pack(pady=5, fill=tk.X, padx=10)

        ttk.Label(output_frame, text="输出文件:").pack(side=tk.LEFT)
        self.output_entry = ttk.Entry(output_frame, width=50)
        self.output_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        ttk.Button(output_frame, text="浏览...", command=self.select_output).pack(side=tk.LEFT)

        # 合并按钮
        self.merge_btn = ttk.Button(
            self.master,
            text="开始合并",
            command=self.start_merge,
            style="Accent.TButton"
        )
        self.merge_btn.pack(pady=10)

        # 状态栏
        self.status = ttk.Label(self.master, text="就绪", relief=tk.SUNKEN)
        self.status.pack(side=tk.BOTTOM, fill=tk.X)

    def setup_drag_drop(self):
        # 支持拖放文件
        self.master.drop_target_register('*')
        self.master.dnd_bind('<<Drop>>', self.handle_drop)

    def handle_drop(self, event):
        files = self.master.tk.splitlist(event.data)
        self.add_files(files=[f for f in files if f.lower().endswith('.pdf')])

    def add_files(self, files=None):
        if not files:
            files = filedialog.askopenfilenames(
                filetypes=[("PDF文件", "*.pdf")],
                title="选择要合并的PDF文件"
            )
        for f in files:
            if f not in self.files:
                self.files.append(f)
                self.tree.insert("", tk.END, values=(f, self.get_page_count(f)))
        self.update_status()

    def remove_files(self):
        selected = self.tree.selection()
        for item in selected:
            index = self.tree.index(item)
            del self.files[index]
            self.tree.delete(item)
        self.update_status()

    def move_items(self, direction):
        selected = self.tree.selection()
        if not selected:
            return

        items = [(self.tree.index(item), item) for item in selected]
        items.sort(reverse=direction > 0)

        for index, item in items:
            new_pos = index + direction
            if 0 <= new_pos < len(self.files):
                self.files.insert(new_pos, self.files.pop(index))
                self.tree.move(item, "", new_pos)

    def select_output(self):
        filename = filedialog.asksaveasfilename(
            defaultextension=".pdf",
            filetypes=[("PDF文件", "*.pdf")],
            initialfile="merged.pdf"  # 添加默认文件名
        )
        if filename:
            filename = os.path.abspath(filename)  # 转为绝对路径
            self.output_entry.delete(0, tk.END)
            self.output_entry.insert(0, filename)

    def get_page_count(self, filepath):
        """获取PDF页数(带详细错误分类)"""
        try:
            with open(filepath, 'rb') as f:
                reader = PdfReader(f)

                if reader.is_encrypted:
                    return ("🔒 加密", "orange")  # 返回元组(显示文本,标签样式)
                return (f"{len(reader.pages)}页")
        except PermissionError:
            return ("⛔ 无权限", "red")
        except Exception as e:
            print(f"页数获取错误 ({filepath}): {str(e)}")
            return ("❌ 错误", "red")

    def start_merge(self):
        if self.merging:
            return

        output_path = self.output_entry.get()
        if not output_path:
            messagebox.showerror("错误", "请先选择输出路径")
            return

        if len(self.files) < 1:
            messagebox.showerror("错误", "请添加要合并的PDF文件")
            return

        def merge_thread():
            try:
                merger = PdfMerger()
                total = len(self.files)

                for i, f in enumerate(self.files, 1):
                    if not os.path.exists(f):
                        raise FileNotFoundError(f"文件不存在: {f}")
                    merger.append(f)
                    self.update_progress(i, total)

                os.makedirs(os.path.dirname(output_path), exist_ok=True)
                with open(output_path, 'wb') as f:
                    merger.write(f)

                messagebox.showinfo("成功", f"合并完成!\n输出文件: {output_path}")
            except Exception as e:
                messagebox.showerror("错误", str(e))
            finally:
                merger.close()
                self.merging = False
                self.merge_btn.config(text="开始合并")
                self.status.config(text="就绪")

        self.merging = True
        self.merge_btn.config(text="合并中...")
        Thread(target=merge_thread, daemon=True).start()

    def update_progress(self, current, total):
        self.master.after(0, lambda: self.status.config(
            text=f"正在合并 ({current}/{total}): {os.path.basename(self.files[current - 1])}"
        ))

    def update_status(self):
        self.status.config(text=f"已选择 {len(self.files)} 个PDF文件")


    def set_window_icon(self):
        """设置窗口图标(支持PNG和ICO)"""
        try:
                # 优先尝试加载PNG
            img = Image.open("pdf.ico")
            photo = ImageTk.PhotoImage(img)
            self.master.iconphoto(True, photo)
        except Exception as e:
            print(f"PNG图标加载失败: {str(e)}")
        try:
            # 备用ICO加载
            self.master.iconbitmap("pdf.ico")
        except Exception as e:
            print(f"ICO图标加载失败: {str(e)}")
            messagebox.showwarning("图标错误", "无法加载程序图标")


if __name__ == "__main__":
    root = tk.Tk()
    style = ttk.Style(root)
    style.theme_use("clam")
    app = PDFMergerGUI(root)
    root.mainloop()

2、打包代码生成exe文件

pyhon 复制代码
pyinstaller --onefile --icon=pdf.ico --add-data="pdf.ico;." merge_pdf.py

3、打开小工具查看效果

4、小工具下载链接

下载: 请点击这里

相关推荐
3GPP仿真实验室几秒前
【Matlab源码】6G候选波形:OFDM-IM 增强仿真平台 DM、CI
开发语言·matlab·ci/cd
devmoon4 分钟前
在 Polkadot 上部署独立区块链Paseo 测试网实战部署指南
开发语言·安全·区块链·polkadot·erc-20·测试网·独立链
lili-felicity4 分钟前
CANN流水线并行推理与资源调度优化
开发语言·人工智能
沐知全栈开发5 分钟前
CSS3 边框:全面解析与实战技巧
开发语言
lili-felicity8 分钟前
CANN模型量化详解:从FP32到INT8的精度与性能平衡
人工智能·python
数据知道11 分钟前
PostgreSQL实战:详解如何用Python优雅地从PG中存取处理JSON
python·postgresql·json
island131415 分钟前
CANN GE(图引擎)深度解析:计算图优化管线、内存静态规划与异构 Stream 调度机制
c语言·开发语言·神经网络
呉師傅17 分钟前
【使用技巧】Adobe Photoshop 2024调整缩放与布局125%后出现点菜单项漂移问题的简单处理
运维·服务器·windows·adobe·电脑·photoshop
曹牧19 分钟前
Spring Boot:如何在Java Controller中处理POST请求?
java·开发语言
浅念-23 分钟前
C++入门(2)
开发语言·c++·经验分享·笔记·学习