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、小工具下载链接

下载: 请点击这里

相关推荐
nju_spy1 分钟前
动手学强化学习上交张伟楠(一)导论 + 多臂老虎机 MAB(ε-greedy+上置信界+汤普森采样)
人工智能·python·强化学习·actor-critic·多臂老虎机·汤普森采样·探索与利用
A星空1231 分钟前
3519Hisidv500的QT配置
开发语言·qt
tjjucheng2 分钟前
专业做小程序定制开发的企业
python
阿里嘎多学长3 分钟前
2026-01-12 GitHub 热点项目精选
开发语言·程序员·github·代码托管
ACERT3336 分钟前
6.吴恩达机器学习——TensorFlow与激活函数
人工智能·python·机器学习
KeLin&10 分钟前
讯为iTOP4412-Qt5.7环境搭建
开发语言·arm开发·qt·arm
星火开发设计10 分钟前
C++ multimap 全面解析与实战指南
java·开发语言·数据结构·c++·学习·知识
superman超哥13 分钟前
Rust 异步并发基石:异步锁(Mutex、RwLock)的设计与深度实践
开发语言·后端·rust·编程语言·rust异步并发·rust异步锁·rust mutex
码农水水14 分钟前
阿里Java面试被问:RocketMQ的消息轨迹追踪实现
java·开发语言·windows·算法·面试·rocketmq·java-rocketmq