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

下载: 请点击这里

相关推荐
喵本喵叁肆14 小时前
Rényi 差分隐私:把隐私损失看成一族矩(英文原题:Rényi Differential Privacy (CSF 2017))
开发语言·r语言
梯度下降者15 小时前
我校学子斩获 2025 全国高新技术竞赛 Python 赛道全国一等奖
python·教育·竞赛·信息技术·人才培养
小小张说故事15 小时前
Python 的 memoryview 为什么能零拷贝:拆开 buffer protocol、cast 和那道 BufferError
python
Hvitur15 小时前
Java - Lambda
java·开发语言·python
xiaoye-duck16 小时前
《C++ 项目:手写大模型接入 SDK》DataManager数据管理模块:SQLite持久化存储
开发语言·数据库·c++·sqlite
Evand J16 小时前
【MATLAB例程,PMSM19】永磁同步电机(PMSM)基于多速率无迹卡尔曼滤波(UKF)的转速与转子位置估计。测量缺失、状态预测与恢复性能分析
开发语言·matlab·电机·ekf·卡尔曼·电机滤波
喵了几个咪16 小时前
GoWind Flow 拆解:一个 Go 工作流引擎与它的双模式设计器
开发语言·人工智能·后端·golang
2601_9679801116 小时前
win10如何把python加入环境变量
python·win10·安装教程·命令行·环境变量
꧁细听勿语情꧂16 小时前
二叉树余题,排序、直接插入排序、希尔排序
c语言·开发语言·数据结构·算法
是main不是漫17 小时前
【C++——类和对象(上)】筑基篇:以封装奠定面向对象之基
c语言·开发语言·c++