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

下载: 请点击这里

相关推荐
不做Java程序猿好多年21 分钟前
Java中 String、StringBuffer、StringBuilder 的区别详解
开发语言·python
金銀銅鐵29 分钟前
[Python] 用 turtle 来绘制国际象棋棋盘(不含棋子)
python·游戏
這花開嗎32 分钟前
2026年TTS文字转语音API哪家强?批量自动化配音成本与技术实测
python·flask·自动化
会周易的程序员33 分钟前
js-shm: 高性能 Node.js 共享内存模块
开发语言·javascript·c++·node.js·共享内存·shm
笨蛋不要掉眼泪1 小时前
Java虚拟机:常用参数
java·开发语言·python
happy_0x3f1 小时前
前端应用的离线暂停更新策略
开发语言·前端·php
2501_909509101 小时前
DAY 31
python
李昊哲小课1 小时前
FastAPI 猫咖预约系统 API
人工智能·python·fastapi
我登哥MVP1 小时前
Headroom 深度指南:AI Agent 上下文压缩实战
人工智能·python·深度学习·神经网络·机器学习·自然语言处理·pip
shylyly_2 小时前
C++中的类型转换
开发语言·c++·匿名对象·隐式类型转换·拷贝优化