简易PDF与图片互转工具

作者 by Doubt-Fact /

如题,简易的PDF与图片互相转换的工具。

安装包:
PDF2img setup.zip
绿色版:
pdf2img green.7z

代码(使用deepseek生成注释):

# 导入必要的库
import tkinter as tk  # 用于创建GUI界面
from tkinter import filedialog, messagebox, ttk  # filedialog用于文件选择,messagebox用于弹出消息框,ttk是tkinter的扩展
from PIL import Image  # Python Imaging Library,用于处理图片
import os  # 用于处理文件和目录路径
import fitz  # PyMuPDF库,用于处理PDF文件
import glob  # 用于查找符合特定模式的文件路径
import re  # 正则表达式模块,用于字符串匹配和处理

# 定义主应用程序类
class FileConverterApp:
    def __init__(self, root):
        # 初始化应用程序
        self.root = root  # 主窗口
        self.root.title("PDF-图片互转")  # 设置窗口标题
        self.root.geometry("900x400")  # 设置窗口大小

        # 尝试设置窗口图标
        try:
            self.root.iconbitmap("favicon.ico")
        except:
            pass  # 如果图标文件不存在,忽略错误

        # 创建主框架,用于放置其他控件
        self.main_frame = ttk.Frame(root, padding="20")
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # 转换类型选择,使用StringVar来存储用户选择的转换类型
        self.convert_type = tk.StringVar(value="pdf_to_image")
        self.create_type_selector()  # 创建转换类型选择器

        # 文件选择区域,用于选择输入文件和输出路径
        self.file_frame = ttk.Frame(self.main_frame)
        self.file_frame.pack(fill=tk.X, pady=10)
        self.input_path = tk.StringVar()  # 存储输入文件路径
        self.output_path = tk.StringVar()  # 存储输出路径

        # 按钮区域,用于放置转换按钮
        self.btn_frame = ttk.Frame(self.main_frame)
        self.btn_frame.pack(fill=tk.X, pady=10)

        # 状态栏,用于显示当前状态
        self.status = ttk.Label(root, text="准备就绪", anchor=tk.W)
        self.status.pack(side=tk.BOTTOM, fill=tk.X)

        self.create_widgets()  # 创建其他控件

    def create_type_selector(self):
        # 创建转换类型选择器
        types = [
            ("PDF 转 图片", "pdf_to_image"),
            ("图片 转 PDF", "image_to_pdf")
        ]

        frame = ttk.LabelFrame(self.main_frame, text="选择转换类型")
        frame.pack(fill=tk.X, pady=10)

        # 创建单选按钮,让用户选择转换类型
        for text, value in types:
            ttk.Radiobutton(frame, text=text, variable=self.convert_type,
                            value=value, command=self.update_ui).pack(side=tk.LEFT, padx=10)

    def create_widgets(self):
        # 创建输入文件选择控件
        ttk.Label(self.file_frame, text="输入文件:").pack(side=tk.LEFT)
        self.input_entry = ttk.Entry(self.file_frame, textvariable=self.input_path, width=40)
        self.input_entry.pack(side=tk.LEFT, padx=5)
        self.input_button = ttk.Button(self.file_frame, text="浏览", command=self.select_input)
        self.input_button.pack(side=tk.LEFT)

        # 创建输出路径选择控件
        ttk.Label(self.file_frame, text="输出路径:").pack(side=tk.LEFT, padx=(10, 0))
        self.output_entry = ttk.Entry(self.file_frame, textvariable=self.output_path, width=40)
        self.output_entry.pack(side=tk.LEFT, padx=5)
        self.output_button = ttk.Button(self.file_frame, text="选择", command=self.select_output)
        self.output_button.pack(side=tk.LEFT)

        # 创建转换按钮
        self.convert_btn = ttk.Button(self.btn_frame, text="开始转换", command=self.start_conversion)
        self.convert_btn.pack(pady=10)

    def update_ui(self):
        # 根据用户选择的转换类型更新UI
        conversion_type = self.convert_type.get()
        if conversion_type == "image_to_pdf":
            self.input_path.set("")
            self.output_path.set("")
            self.input_entry.config(state='disabled')  # 禁用输入文件选择框
            self.input_button.config(text="选择目录")  # 修改按钮文本
        else:
            self.input_entry.config(state='normal')  # 启用输入文件选择框
            self.input_button.config(text="浏览")  # 恢复按钮文本

    def select_input(self):
        # 选择输入文件或目录
        conversion_type = self.convert_type.get()
        if conversion_type == "image_to_pdf":
            path = filedialog.askdirectory()  # 选择目录
            if path:
                self.input_path.set(path)
        else:
            filetypes = [("PDF文件", "*.pdf")]
            path = filedialog.askopenfilename(filetypes=filetypes)  # 选择PDF文件
            if path:
                self.input_path.set(path)
                if not self.output_path.get():
                    # 设置默认输出路径为输入文件所在目录
                    default_output = os.path.splitext(path)[0] + self.get_output_ext()
                    self.output_path.set(default_output)

    def select_output(self):
        # 选择输出路径
        conversion_type = self.convert_type.get()
        default_ext = self.get_output_ext()

        if conversion_type == "image_to_pdf":
            path = filedialog.asksaveasfilename(defaultextension=default_ext,
                                               filetypes=[("PDF文件", "*.pdf")])
        else:
            path = filedialog.asksaveasfilename(defaultextension=default_ext,
                                               filetypes=[(f"{default_ext[1:]}文件", f"*{default_ext}")])
        if path:
            self.output_path.set(path)

    def get_output_ext(self):
        # 获取输出文件的默认扩展名
        conversion_type = self.convert_type.get()
        if conversion_type == "pdf_to_image":
            return "_images"
        elif conversion_type == "image_to_pdf":
            return ".pdf"
        return ""

    def start_conversion(self):
        # 开始转换
        conversion_type = self.convert_type.get()
        input_path = self.input_path.get()
        output_path = self.output_path.get()

        if not input_path or not output_path:
            messagebox.showwarning("错误", "请选择输入文件和输出路径")
            return

        try:
            self.status.config(text="正在转换,请稍候...")
            if conversion_type == "pdf_to_image":
                self.pdf_to_image(input_path, output_path)  # 调用PDF转图片函数
            elif conversion_type == "image_to_pdf":
                self.image_to_pdf(input_path, output_path)  # 调用图片转PDF函数
            self.status.config(text="转换完成!")
            messagebox.showinfo("成功", "转换完成!")
        except Exception as e:
            self.status.config(text="转换失败")
            messagebox.showerror("错误", f"转换失败:{str(e)}")

    def pdf_to_image(self, pdf_path, output_folder):
        # 将PDF文件转换为图片
        try:
            doc = fitz.open(pdf_path)  # 打开PDF文件
            if not os.path.exists(output_folder):
                os.makedirs(output_folder)  # 如果输出目录不存在,创建它
            for page_num in range(len(doc)):
                page = doc.load_page(page_num)  # 加载PDF的每一页
                pix = page.get_pixmap(dpi=300)  # 将页面转换为图片,设置DPI为300
                output_path = os.path.join(output_folder, f"page_{page_num + 1}.jpg")  # 设置输出图片路径
                pix.save(output_path)  # 保存图片
        finally:
            doc.close()  # 关闭PDF文件

    def image_to_pdf(self, image_folder, pdf_path):
        # 将图片转换为PDF文件
        # 获取所有jpg和png文件并自然排序
        image_files = glob.glob(os.path.join(image_folder, "*.jpg")) + glob.glob(os.path.join(image_folder, "*.png"))
        # 自然排序逻辑,确保文件按数字顺序排列
        image_files.sort(key=lambda x: [
            int(text) if text.isdigit() else text.lower() 
            for text in re.split(r'(\d+)', os.path.basename(x))
        ])
        
        images = []
        for img_file in image_files:
            try:
                im = Image.open(img_file)  # 打开图片
                if im.mode == 'RGBA':
                    im = im.convert('RGB')  # 如果图片是RGBA模式,转换为RGB模式
                images.append(im)  # 将图片添加到列表中
            except Exception as e:
                print(f"跳过文件 {img_file}: {str(e)}")  # 如果图片无法打开,跳过并打印错误信息
        
        if images:
            # 将所有图片保存为一个PDF文件
            images[0].save(pdf_path, save_all=True, append_images=images[1:])

# 程序入口
if __name__ == "__main__":
    root = tk.Tk()  # 创建主窗口
    app = FileConverterApp(root)  # 创建应用程序实例
    root.mainloop()  # 进入主事件循环

评论已关闭