PDF 转成图片

PDF 转成图片

依赖库:pdf2image,(内部调用 poppler,所以需要安装 poppler)
https://github.com/oschwartz10612/poppler-windows/releases/

  • 但是 poppler 的 bin 目录添加环境变量,程序也无法察觉到 ===> pdf2image 不会自动搜索系统 PATH(这是它的设计缺陷)
  • 所以在程序中,手动指定 path
 pages = convert_from_path(pdf_path, dpi=dpi, poppler_path=r'D:\Release-25.11.0-0\poppler-25.11.0\Library\bin')

完整程序

import os
import threading
from tkinter import Tk, Label, Button, Entry, filedialog, StringVar, ttk, messagebox
from pdf2image import convert_from_path

def select_pdf():
    path = filedialog.askopenfilename(
        title="选择 PDF 文件",
        filetypes=[("PDF 文件", "*.pdf")]
    )
    pdf_path_var.set(path)

def select_output_dir():
    path = filedialog.askdirectory(title="选择输出目录")
    output_dir_var.set(path)

def convert_pdf():
    pdf_path = pdf_path_var.get()
    output_dir = output_dir_var.get()
    dpi = dpi_var.get()
    fmt = format_var.get().lower()

    if not pdf_path:
        messagebox.showerror("错误", "请选择 PDF 文件")
        return

    if not output_dir:
        messagebox.showerror("错误", "请选择输出目录")
        return

    try:
        dpi = int(dpi)
    except:
        messagebox.showerror("错误", "DPI 必须是整数")
        return

    # 启动异步线程执行转换(防止 GUI 卡死)
    threading.Thread(target=convert_worker, args=(pdf_path, output_dir, dpi, fmt), daemon=True).start()

def convert_worker(pdf_path, output_dir, dpi, fmt):
    progress_bar["value"] = 0
    progress_label.config(text="正在加载 PDF...")

    try:
        pages = convert_from_path(pdf_path, dpi=dpi, poppler_path=r'D:\Release-25.11.0-0\poppler-25.11.0\Library\bin')
    except Exception as e:
        print(pdf_path)
        print("=======")
        messagebox.showerror("错误", f"PDF 读取失败:\n{e}")
        return

    total = len(pages)
    progress_bar["maximum"] = total

    try:
        for i, page in enumerate(pages):
            filename = os.path.join(output_dir, f"page_{i+1}.{fmt}")
            page.save(filename, fmt.upper())

            progress_bar["value"] = i + 1
            progress_label.config(text=f"正在转换第 {i+1}/{total} 页...")
    except Exception as e:
        messagebox.showerror("错误", f"转换失败:\n{e}")
        return

    progress_label.config(text="完成!")
    messagebox.showinfo("完成", f"全部页面已成功导出至:\n{output_dir}")

# GUI -----------------------------

root = Tk()
root.title("PDF 分页保存为图片转换器")
root.geometry("500x350")

Label(root, text="选择 PDF 文件:").pack()
pdf_path_var = StringVar()
Entry(root, textvariable=pdf_path_var, width=60).pack()
Button(root, text="浏览...", command=select_pdf).pack(pady=5)

Label(root, text="选择输出目录:").pack()
output_dir_var = StringVar()
Entry(root, textvariable=output_dir_var, width=60).pack()
Button(root, text="浏览...", command=select_output_dir).pack(pady=5)

Label(root, text="DPI(清晰度,默认 200):").pack()
dpi_var = StringVar(value="200")
Entry(root, textvariable=dpi_var, width=10).pack()

Label(root, text="输出图片格式:").pack()
format_var = StringVar(value="PNG")
ttk.Combobox(root, textvariable=format_var, values=["PNG", "JPG"], width=10).pack()

# 进度条
progress_label = Label(root, text="等待开始...")
progress_label.pack(pady=10)

progress_bar = ttk.Progressbar(root, length=350)
progress_bar.pack()

Button(root, text="开始转换", command=convert_pdf, width=20).pack(pady=15)

root.mainloop()

打包成 exe

难点:码依赖于 pdf2image,而 pdf2image 又依赖于外部程序 Poppler,所以打包过程需要特别注意如何包含 Poppler。

1. 定位 Poppler 路径

为了让打包后的 .exe 文件在任何电脑上都能运行,您需要将 Poppler 的相关动态链接库(DLLs) 和可执行文件(如 pdftoppm.exe, pdftocairo.exe 等)包含到您的可执行文件或其生成的目录中。

推荐做法:

  1. 将 Poppler 的 bin 文件夹(包含 DLLs 和 EXE 文件)拷贝到您的 Python 脚本(your_script_name.py)所在的目录下,并将其命名为,例如,poppler_bin。
  2. 修改代码,使其在打包后能找到 Poppler。

修改后的代码片段(convert_worker 函数):
由于打包后文件的结构会变化,最好的方法是让程序去寻找一个相对路径的 Poppler 目录。在 PyInstaller 打包的程序中,运行时文件的临时路径可以通过 sys._MEIPASS 获取,但为了简化,我们可以让它查找程序运行目录下的 poppler_bin 文件夹。

将硬编码路径替换为相对路径:

import os
import threading
from tkinter import Tk, Label, Button, Entry, filedialog, StringVar, ttk, messagebox
from pdf2image import convert_from_path
import sys  # 导入 sys 模块

# ... (其他函数保持不变)

def convert_worker(pdf_path, output_dir, dpi, fmt):
    progress_bar["value"] = 0
    progress_label.config(text="正在加载 PDF...")

    # ----------------------------------------------------
    # 修改 Poppler 路径处理
    if getattr(sys, 'frozen', False):
        # 如果是打包后的 exe,Poppler 文件夹应该放在程序运行目录下
        # 这里假设您将 Poppler bin 文件夹重命名为 'poppler_bin' 放在程序根目录
        base_path = os.path.dirname(sys.executable)
    else:
        # 如果是直接运行 Python 脚本,则使用原始路径(或相对路径)
        # 在 Python 脚本中:
        # __file__ 这个内置变量(magic variable)总是指向当前被执行的 Python 模块(文件)的完整路径(包括文件名)。
        # os.path.dirname(...) 的作用: 获取给定路径的目录部分。
        # 所以,base_path 最终获取的就是 当前正在运行的 Python 脚本文件所在的目录。
        base_path = os.path.dirname(os.path.abspath(__file__))
    
    # 构建 Poppler 的 bin 目录路径
    poppler_bin_path = os.path.join(base_path, 'poppler_bin')

    # 检查路径是否存在
    if not os.path.exists(poppler_bin_path):
        messagebox.showerror("错误", f"Poppler 路径未找到:\n{poppler_bin_path}\n请确保 'poppler_bin' 文件夹存在于程序运行目录。")
        return
    
    # ----------------------------------------------------
    
    try:
        # 使用修改后的 poppler_bin_path
        pages = convert_from_path(pdf_path, dpi=dpi, poppler_path=poppler_bin_path)
    except Exception as e:
        # ... (错误处理)
        # ...

2. 执行 PyInstaller 命令

pyinstaller --onefile --noconsole --name "PDFToImageConverter" pdf_converter.py
  • --onefile: 将所有内容打包成一个单独的 .exe 文件(文件较大,但方便分发)。
  • --noconsole 或 --windowed: 对于 GUI 程序,强烈建议使用,可以防止在运行程序时弹出一个黑色命令行窗口。
  • --name "PDFToImageConverter": 设置最终 .exe 文件的名称。

3. 最终部署

执行 PyInstaller 后,会在您的目录下生成两个文件夹:build 和 dist。

最终的可执行文件位于 dist 文件夹内。

部署文件结构:

将以下两个项目一起提供给用户:

  1. dist/PDFToImageConverter.exe
  2. poppler_bin 文件夹 (这是您在步骤二中准备的 Poppler 的 bin 文件夹的拷贝,需要与 .exe 文件放在同一目录下)。

用户运行 PDFToImageConverter.exe 时,程序就会在同级目录下找到 poppler_bin 文件夹,从而成功调用 Poppler 进行 PDF 转换。

4. 完整代码

import os
import sys
import threading
from tkinter import Tk, Label, Button, Entry, filedialog, StringVar, ttk, messagebox
from pdf2image import convert_from_path

def select_pdf():
    path = filedialog.askopenfilename(
        title="选择 PDF 文件",
        filetypes=[("PDF 文件", "*.pdf")]
    )
    pdf_path_var.set(path)

def select_output_dir():
    path = filedialog.askdirectory(title="选择输出目录")
    output_dir_var.set(path)

def convert_pdf():
    pdf_path = pdf_path_var.get()
    output_dir = output_dir_var.get()
    dpi = dpi_var.get()
    fmt = format_var.get().lower()

    if not pdf_path:
        messagebox.showerror("错误", "请选择 PDF 文件")
        return

    if not output_dir:
        messagebox.showerror("错误", "请选择输出目录")
        return

    try:
        dpi = int(dpi)
    except:
        messagebox.showerror("错误", "DPI 必须是整数")
        return

    # 启动异步线程执行转换(防止 GUI 卡死)
    threading.Thread(target=convert_worker, args=(pdf_path, output_dir, dpi, fmt), daemon=True).start()

def convert_worker(pdf_path, output_dir, dpi, fmt):
    progress_bar["value"] = 0
    progress_label.config(text="正在加载 PDF...")

    # ----------------------------------------------------
    # 修改 Poppler 路径处理
    if getattr(sys, 'frozen', False):
        # 如果是打包后的 exe,Poppler 文件夹应该放在程序运行目录下
        # 这里假设您将 Poppler bin 文件夹重命名为 'poppler_bin' 放在程序根目录
        base_path = os.path.dirname(sys.executable)
    else:
        # 如果是直接运行 Python 脚本,则使用原始路径(或相对路径)
        base_path = os.path.dirname(os.path.abspath(__file__))

    # 构建 Poppler 的 bin 目录路径
    poppler_bin_path = os.path.join(base_path, 'poppler_bin')

    # 检查路径是否存在
    if not os.path.exists(poppler_bin_path):
        messagebox.showerror("错误", f"Poppler 路径未找到:\n{poppler_bin_path}\n请确保 'poppler_bin' 文件夹存在于程序运行目录。")
        return

    # ----------------------------------------------------

    try:
        # 使用修改后的 poppler_bin_path
        pages = convert_from_path(pdf_path, dpi=dpi, poppler_path=poppler_bin_path)
    except Exception as e:
        print(pdf_path)
        print("=======")
        messagebox.showerror("错误", f"PDF 读取失败:\n{e}")
        return

    total = len(pages)
    progress_bar["maximum"] = total

    try:
        for i, page in enumerate(pages):
            filename = os.path.join(output_dir, f"page_{i+1}.{fmt}")
            page.save(filename, fmt.upper())

            progress_bar["value"] = i + 1
            progress_label.config(text=f"正在转换第 {i+1}/{total} 页...")
    except Exception as e:
        messagebox.showerror("错误", f"转换失败:\n{e}")
        return

    progress_label.config(text="完成!")
    messagebox.showinfo("完成", f"全部页面已成功导出至:\n{output_dir}")

# GUI -----------------------------

root = Tk()
root.title("PDF 分页保存为图片转换器")
root.geometry("500x350")

Label(root, text="选择 PDF 文件:").pack()
pdf_path_var = StringVar()
Entry(root, textvariable=pdf_path_var, width=60).pack()
Button(root, text="浏览...", command=select_pdf).pack(pady=5)

Label(root, text="选择输出目录:").pack()
output_dir_var = StringVar()
Entry(root, textvariable=output_dir_var, width=60).pack()
Button(root, text="浏览...", command=select_output_dir).pack(pady=5)

Label(root, text="DPI(清晰度,默认 200):").pack()
dpi_var = StringVar(value="200")
Entry(root, textvariable=dpi_var, width=10).pack()

Label(root, text="输出图片格式:").pack()
format_var = StringVar(value="PNG")
ttk.Combobox(root, textvariable=format_var, values=["PNG", "JPG"], width=10).pack()

# 进度条
progress_label = Label(root, text="等待开始...")
progress_label.pack(pady=10)

progress_bar = ttk.Progressbar(root, length=350)
progress_bar.pack()

Button(root, text="开始转换", command=convert_pdf, width=20).pack(pady=15)

root.mainloop()
posted @ 2025-11-25 15:45  爱新觉罗LQ  阅读(46)  评论(0)    收藏  举报