图片批量转换 PDF 工具(带进度显示)

查看 59|回复 8
作者:_海东青丶   
功能简介:
这是一款基于 Python 开发的图形化工具,可以批量将文件夹中的图片自动转换成 PDF 文件。支持多层文件夹递归查找图片,自动按图片尺寸合并生成长图,并按需分页生成多页 PDF。即使面对大尺寸图片也能轻松处理,适合漫画、文档扫描等批量整理使用。
主要特点:
  • 支持 JPG、PNG、JPEG、GIF、BMP 等常见图片格式
  • 图形界面友好,无需写代码
  • 自动按文件夹结构命名 PDF 文件
  • 超大长图自动分页,避免 PDF 高度超限
  • 实时转换进度显示,方便掌握处理情况
  • 日志记录详细,可追踪每一步操作
    使用说明:
  • 双击运行脚本(需已安装 Python 和 PIL 库)。
  • 在界面中选择输入文件夹(包含图片的文件夹)。
  • 选择输出文件夹(PDF 保存位置)。
  • 点击【开始转换】,等待进度窗口提示完成。
    注意事项:
  • 输入输出文件夹不能相同。
  • 大量图片处理时请耐心等待。
  • 如遇异常,可查看生成的 log.txt 日志文件了解详细信息。



    wechat_2025-05-20_093350_468.png (17.21 KB, 下载次数: 0)
    下载附件
    2025-5-20 09:46 上传



    wechat_2025-05-20_093410_189.png (21.08 KB, 下载次数: 0)
    下载附件
    2025-5-20 09:46 上传



    wechat_2025-05-20_095628_222.png (61.53 KB, 下载次数: 0)
    下载附件
    2025-5-20 09:56 上传

    附上代码
    [Python] 纯文本查看 复制代码import os
    import tkinter as tk
    from tkinter import filedialog, ttk, messagebox
    from PIL import Image
    from pathlib import Path
    import threading
    import queue
    MAX_HEIGHT = 65000  # PIL的最大高度限制
    class ProgressWindow:
        def __init__(self):
            self.window = tk.Tk()
            self.window.title("转换进度")
            self.window.geometry("500x300")
            
            # 窗口居中
            screen_width = self.window.winfo_screenwidth()
            screen_height = self.window.winfo_screenheight()
            x = (screen_width - 500) // 2
            y = (screen_height - 300) // 2
            self.window.geometry(f"500x300+{x}+{y}")
            
            # 进度显示文本框
            self.text = tk.Text(self.window, wrap=tk.WORD, width=50, height=15)
            self.text.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
            
            # 进度条
            self.progress = ttk.Progressbar(self.window, mode='indeterminate')
            self.progress.pack(padx=10, pady=5, fill=tk.X)
            
            # 取消按钮
            self.cancel_button = ttk.Button(self.window, text="完成", command=self.window.destroy)
            self.cancel_button.pack(pady=5)
            self.cancel_button.state(['disabled'])
            
            # 消息队列
            self.queue = queue.Queue()
            self.window.after(100, self.check_queue)
       
        def check_queue(self):
            try:
                while True:
                    msg = self.queue.get_nowait()
                    if msg == "DONE":
                        self.progress.stop()
                        self.cancel_button.state(['!disabled'])
                        self.text.insert(tk.END, "\n转换完成!\n")
                    else:
                        self.text.insert(tk.END, msg + "\n")
                    self.text.see(tk.END)
            except queue.Empty:
                self.window.after(100, self.check_queue)
    def create_long_image(image_files, start_idx, end_idx):
        # 打开指定范围的图片
        images = [Image.open(f) for f in image_files[start_idx:end_idx]]
       
        # 计算总高度和最大宽度
        total_height = sum(img.height for img in images)
        max_width = max(img.width for img in images)
       
        # 创建新的长图
        long_image = Image.new('RGB', (max_width, total_height), 'white')
       
        # 垂直拼接图片
        y_offset = 0
        for img in images:
            # 如果图片宽度小于最大宽度,居中放置
            x_offset = (max_width - img.width) // 2
            long_image.paste(img, (x_offset, y_offset))
            y_offset += img.height
            img.close()
       
        return long_image
    def get_pdf_name(input_folder, current_folder):
        """根据目录结构决定PDF文件名"""
        relative_path = os.path.relpath(current_folder, input_folder)
       
        # 如果当前文件夹就是输入文件夹,使用当前文件夹名
        if relative_path == '.':
            return os.path.basename(current_folder)
       
        # 检查是否有子文件夹
        has_subfolder_with_images = False
        for root, dirs, files in os.walk(current_folder):
            if root != current_folder:  # 如果不是当前目录
                # 检查这个子目录是否包含图片
                for file in files:
                    if file.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp')):
                        has_subfolder_with_images = True
                        break
            if has_subfolder_with_images:
                break
       
        # 如果没有包含图片的子文件夹,使用当前文件夹名
        if not has_subfolder_with_images:
            return os.path.basename(current_folder)
       
        # 否则使用相对路径
        return relative_path
    def convert_folder_to_pdf(input_folder, output_folder, progress_queue):
        try:
            # 确保输出文件夹存在
            if not os.path.exists(output_folder):
                os.makedirs(output_folder)
            
            # 遍历输入文件夹中的所有子文件夹
            for root, dirs, files in os.walk(input_folder):
                # 跳过输出文件夹本身
                if os.path.abspath(root) == os.path.abspath(output_folder):
                    continue
                
                # 记录当前遍历的文件夹
                with open("log.txt", "a", encoding="utf-8") as logf:
                    logf.write(f"遍历文件夹: {root}\n")
                
                # 获取当前文件夹中的所有图片文件
                image_files = []
                for file in sorted(files):
                    if file.lower().endswith((".png", ".jpg", ".jpeg", ".gif", ".bmp")):
                        image_files.append(os.path.join(root, file))
                
                if image_files:
                    try:
                        progress_queue.put(f"正在处理文件夹: {root}")
                        with open("log.txt", "a", encoding="utf-8") as logf:
                            logf.write(f"处理图片: {image_files}\n")
                        # 获取PDF文件名
                        pdf_name = get_pdf_name(input_folder, root)
                        pdf_filename = os.path.join(output_folder, f"{pdf_name}.pdf")
                        with open("log.txt", "a", encoding="utf-8") as logf:
                            logf.write(f"输出PDF路径: {pdf_filename}\n")
                        
                        # 确保输出目录存在
                        os.makedirs(os.path.dirname(pdf_filename), exist_ok=True)
                        
                        # 计算图片高度并分组
                        current_height = 0
                        start_idx = 0
                        image_groups = []
                        
                        # 打开所有图片以获取高度
                        images_heights = [Image.open(f).height for f in image_files]
                        
                        # 根据高度限制分组
                        for i, height in enumerate(images_heights):
                            if current_height + height > MAX_HEIGHT:
                                # 当前组已满,添加到groups
                                image_groups.append((start_idx, i))
                                start_idx = i
                                current_height = height
                            else:
                                current_height += height
                        
                        # 添加最后一组
                        if start_idx
    成品:https://www.123865.com/s/tMQnTd-DBzsH
    最后,请各位大佬动动小手,送送免费的评分,赚点评分消违规,万分感谢!

    文件夹, 图片

  • CQThomas   

    不错,多谢分享!
    52bulesky   

    修改了下,去掉了UI,增加了自动加密
    [Python] 纯文本查看 复制代码import os
    import sys
    from PIL import Image
    from concurrent.futures import ThreadPoolExecutor
    import multiprocessing
    from PyPDF2 import PdfWriter, PdfReader
    # 日志文件路径
    LOG_FILE = "log.txt"
    def log_message(message):
        """记录日志到文件并打印到控制台"""
        with open(LOG_FILE, 'a', encoding='utf-8') as f:
            f.write(message + '\n')
        print(message)
    def is_image_file(file):
        """检查文件是否为图片文件"""
        try:
            with Image.open(file) as img:
                return img.format in ['JPEG', 'PNG', 'BMP', 'GIF', 'TIFF', 'WEBP']
        except IOError:
            return False
    def compress_image(input_file):
        """压缩单个图片文件"""
        try:
            with Image.open(input_file) as img:
                file_format = img.format
                if file_format == 'WEBP':
                    img.save(input_file, format=file_format, quality=80)
                else:
                    img.save(input_file, format=file_format, optimize=True)
            log_message(f"图像已成功压缩: {input_file}")
        except OSError:
            try:
                with Image.open(input_file) as img:
                    file_format = img.format
                    if file_format == 'WEBP':
                        img.save(input_file, format=file_format, quality=80)
                    else:
                        img.save(input_file, format=file_format, optimize=True, exif=b'')
                log_message(f"图像已成功压缩(处理EXIF): {input_file}")
            except Exception as e:
                log_message(f"压缩图像时出错: {input_file}, 错误信息: {str(e)}")
    def compress_images_in_folders(thread_count):
        """遍历文件夹并压缩所有图片文件"""
        image_files = []
        for root, _, files in os.walk(os.getcwd()):
            for file in files:
                input_file = os.path.join(root, file)
                _, ext = os.path.splitext(input_file)
                if ext.lower() in ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.webp']:
                    if is_image_file(input_file):
                        image_files.append(input_file)
        with ThreadPoolExecutor(max_workers=thread_count) as executor:
            executor.map(compress_image, image_files)
    def images_to_pdf(folder_path, output_pdf):
        """将文件夹中的图片合并为一个PDF文件"""
        image_files = []
        for file in os.listdir(folder_path):
            input_file = os.path.join(folder_path, file)
            _, ext = os.path.splitext(input_file)
            if ext.lower() in ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.webp']:
                if is_image_file(input_file):
                    image_files.append(input_file)
        if not image_files:
            log_message(f"文件夹 {folder_path} 中没有找到图片文件,跳过生成PDF。")
            return
        try:
            images = []
            for img_path in image_files:
                img = Image.open(img_path)
                if img.mode != 'RGB':
                    img = img.convert('RGB')
                images.append(img)
            first_image = images[0]
            images.pop(0)
            first_image.save(output_pdf, save_all=True, append_images=images)
            log_message(f"成功将文件夹 {folder_path} 中的图片合并为PDF: {output_pdf}")
        except Exception as e:
            log_message(f"将文件夹 {folder_path} 中的图片合并为PDF时出错: {str(e)}")
    def encrypt_pdf(input_path, output_path, user_password, owner_password):
        """加密单个PDF文件"""
        try:
            with open(input_path, 'rb') as file:
                reader = PdfReader(file)
                writer = PdfWriter()
                for page in reader.pages:
                    writer.add_page(page)
                writer.encrypt(user_password=user_password, owner_password=owner_password, use_128bit=True)
                with open(output_path, 'wb') as output_file:
                    writer.write(output_file)
            log_message(f"成功加密PDF: {output_path}")
        except Exception as e:
            log_message(f"加密PDF时出错: {input_path}, 错误信息: {str(e)}")
    if __name__ == "__main__":
        thread_count = multiprocessing.cpu_count()
        log_message(f"使用 {thread_count} 个线程进行图像压缩")
        compress_images_in_folders(thread_count)
        user_password = "52pojie"
        owner_password = "52pojie"
        for root, _, _ in os.walk(os.getcwd()):
            folder_name = os.path.basename(root)
            pdf_name = folder_name + ".pdf"
            
            # 将"encrypted"放在原文件名后面,格式为:原文件名_encrypted.pdf
            encrypted_pdf_name = f"{os.path.splitext(pdf_name)[0]}_encrypted.pdf"
            
            pdf_path = os.path.join(root, pdf_name)
            encrypted_pdf_path = os.path.join(root, encrypted_pdf_name)
            
            images_to_pdf(root, pdf_path)
            if os.path.exists(pdf_path):
                encrypt_pdf(pdf_path, encrypted_pdf_path, user_password, owner_password)
                try:
                    os.remove(pdf_path)
                    log_message(f"已删除未加密的PDF文件: {pdf_path}")
                except Exception as e:
                    log_message(f"删除未加密的PDF文件时出错: {pdf_path}, 错误信息: {str(e)}")
    purusi   

    谢谢,支持一下                    
    mytomsummer   

    感谢分享,试一下.
    a33521   

    谢谢分享 支持一下
    abc1234567890   

    谢谢分享 下载支持一下
    hbdzbs   

    多谢分享!
    kmadsl   

    现在就用上了。谢谢
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部