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

下载: 请点击这里

相关推荐
薛定谔的猫喵喵3 分钟前
天然气压力能利用系统综合性评价平台:基于Python和PyQt5的AHP与模糊综合评价集成应用
开发语言·python·qt
yuluo_YX8 分钟前
Reactive 编程 - Java Reactor
java·python·apache
独好紫罗兰16 分钟前
对python的再认识-基于数据结构进行-a004-列表-实用事务
开发语言·数据结构·python
gjxDaniel17 分钟前
Objective-C编程语言入门与常见问题
开发语言·objective-c
ZH154558913118 分钟前
Flutter for OpenHarmony Python学习助手实战:模块与包管理的实现
python·学习·flutter
choke23327 分钟前
[特殊字符] Python异常处理
开发语言·python
云中飞鸿28 分钟前
linux中qt安装
开发语言·qt
少控科技42 分钟前
QT第6个程序 - 网页内容摘取
开发语言·qt
darkb1rd42 分钟前
八、PHP SAPI与运行环境差异
开发语言·网络安全·php·webshell
历程里程碑44 分钟前
Linux20 : IO
linux·c语言·开发语言·数据结构·c++·算法