再也不用充会员了,PDF转图片、拆分PDF、合并PDF等永久免费使用。

今天在办公室听到一同事在求助,说他的WPS会员到期了,想用PDF转图片和多个图片转PDF功能,问谁有会员?大家沉默了,办公室里竟全是屌丝,没有人为此充会员,只有我站了起来,我说你要是经常需要用这个功能我给你做一个免费的,这不,刚下班回家半个小时就给做好了,展示下成品先。

图片

有同样需求的可以免费获取,即开即用,无需安装,链接:

通过网盘分享的文件:PDFTools-v3.1.rar
链接: https://pan.baidu.com/s/1UOk7wdvs9fZmLtAEvxLhqw?pwd=TF66 提取码: TF66

如果对这个小工具的制作过程感兴趣的同学,可以继续往下。

今天其实最想给大家推荐的一款超好用的免费的开发工具,它是字节跳动于3月3日发布国内首款 AI 原生集成开发环境工具(AI IDE)——Trae 国内版,搭载 doubao-1.5-pro 模型,并支持切换 DeepSeek R1&V3。帮助开发者高效协作 AI,提升编程效率和质量。立即体验

https://www.trae.com.cn/。

上面的小工具就是我用它在半小时内完成的,没有写一段代码,只是在向它提出我的需求和完善我的需求,运行代码并提出修改意见,然后他就交出了这样一份满意的成品。

图片

就这样简单的一句话,它就自动创建了一个python文件,并且把代码也写好了,还要问我是否接受这段代码。

图片

先运行一下他给出的这个代码,效果如下:

图片

很简单且简陋,功能也都无法使用,我一看这代码上面好多功能写着开发中,我就立马不乐意了,就赶紧让他继续完善其他功能。

图片

软件很轻量化,界面左边是代码编辑区,右边就是AI助手。

图片

继续向他提要求。

图片

每次新的要求给到他,他都会进行修改,代码有增有减,并且会提示你是否接受,不用犹豫,先直接点击“全部接受”,后运行看看是否达到了你想要的效果,如果不满足,继续提要求,知道满意为止。

图片

图片

如果在运行代码时报错了,可以将错误发给他,按照他的提示去做就好了,甚至不需要你做什么,你只要告诉他出了什么问题就行。

图片

图片

到这里,成品已经差不多是这样了,功能也都经过测试,可以实现。

图片

但是我看着这个界面土土的,我还是不满意,继续提要求。

图片

图片

图片

图片

到这里,成品已经让我很满意了。咱们来看看吧。

图片

录了个小视频,看看功能的实现效果吧。

20250306_210603

以下是AI工具写的完整代码:

from datetime import datetime
import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
import os
import io
import threading
from PyPDF2 import PdfReader, PdfMerger, PdfWriter
from pdf2image import convert_from_path
from PIL import Image
import win32com.client
import ttkbootstrap as ttk
from ttkbootstrap.widgets import Progressbar

class FileConverterApp:
    def __init__(self):
        # 使用ttkbootstrap的Style创建主窗口
        self.root = ttk.Window(themename="flatly")  # 使用flatly主题,可模仿win11风格
        self.root.title("办公文件处理工具")
        self.root.geometry("400x300")
        
        self.create_widgets()
        self.progress_bar = None

    def create_widgets(self):
        # PDF操作区域
        pdf_frame = ttk.LabelFrame(self.root, text="PDF工具")
        pdf_frame.pack(pady=10, fill="x", padx=5)

        ttk.Button(pdf_frame, text="PDF转图片", command=self.start_pdf_to_images, width=10, style="primary.TButton").pack(
            side=tk.LEFT, padx=5)
        ttk.Button(pdf_frame, text="合并PDF", command=self.start_merge_pdf, width=10, style="success.TButton").pack(
            side=tk.LEFT, padx=5)
        ttk.Button(pdf_frame, text="拆分PDF", command=self.start_split_pdf, width=10, style="info.TButton").pack(
            side=tk.LEFT, padx=5)
        ttk.Button(pdf_frame, text="加密PDF", command=self.encrypt_pdf, width=10, style="warning.TButton").pack(
            side=tk.LEFT, padx=5)
        ttk.Button(pdf_frame, text="解密PDF", command=self.decrypt_pdf, width=10, style="danger.TButton").pack(
            side=tk.LEFT, padx=5)

        # 文件转换区域
        convert_frame = ttk.LabelFrame(self.root, text="文档转换工具")
        convert_frame.pack(pady=10, fill="x", padx=5)

        ttk.Button(convert_frame, text="Word转PDF", command=self.start_word_to_pdf, width=10, style="primary.TButton").pack(
            side=tk.LEFT, padx=5)
        ttk.Button(convert_frame, text="Excel转PDF", command=self.start_excel_to_pdf, width=10, style="success.TButton").pack(
            side=tk.LEFT, padx=5)
        ttk.Button(convert_frame, text="PPT转PDF", command=self.start_ppt_to_pdf, width=10, style="info.TButton").pack(
            side=tk.LEFT, padx=5)
        ttk.Button(convert_frame, text="图片转PDF", command=self.start_images_to_pdf, width=10, style="warning.TButton").pack(
            side=tk.LEFT, padx=5)

        # 图片操作区域
        img_frame = ttk.LabelFrame(self.root, text="图片工具")
        img_frame.pack(pady=10, fill="x", padx=5)

        ttk.Button(img_frame, text="PDF提取图片", command=self.get_images_from_pdf, width=10, style="primary.TButton").pack(
            side=tk.LEFT, padx=5)

        # 调整窗口尺寸
        self.root.geometry("600x400")
        
        # 状态栏
        self.status = tk.Label(self.root, text="就绪", bd=1, relief=tk.SUNKEN, anchor=tk.W)
        self.status.pack(side=tk.BOTTOM, fill=tk.X)

    def start_pdf_to_images(self):
        # 启动一个新线程来执行 PDF 转换任务
        thread = threading.Thread(target=self.pdf_to_images)
        thread.start()

    def pdf_to_images(self):
        try:
            pdf_path = filedialog.askopenfilename(
                title='选择PDF文件',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not pdf_path:
                return

            output_dir = filedialog.askdirectory(title='选择图片保存目录')
            if not output_dir:
                return

            self.status.config(text='正在转换PDF...')

            # 创建进度条
            self.progress_bar = Progressbar(self.root, orient="horizontal", length=300, mode="determinate")
            self.progress_bar.pack(pady=10)

            # 获取 PDF 总页数
            reader = PdfReader(pdf_path)
            total_pages = len(reader.pages)

            # 获取 PDF 文件名(不含扩展名)
            pdf_basename = os.path.splitext(os.path.basename(pdf_path))[0]

            # 使用 pdf2image 将 PDF 每一页转换为图片
            poppler_path = r'.\poppler-24.08.0\Library\bin'
            images = convert_from_path(pdf_path, dpi=300, fmt='jpeg', poppler_path=poppler_path, thread_count=3)
            for i, image in enumerate(images):
                # 以 PDF 文件名作为前缀生成图片文件名
                image_path = os.path.join(output_dir, f'{pdf_basename}_page_{i + 1}.png')
                image.save(image_path, 'PNG')

                # 更新进度条
                progress = (i + 1) / total_pages * 100
                self.progress_bar['value'] = progress
                self.root.update_idletasks()

            # 隐藏进度条
            self.progress_bar.pack_forget()

            self.status.config(text=f'转换完成,保存至:{output_dir}')
            messagebox.showinfo("完成", f"成功转换{len(images)}页!")
        except Exception as e:
            if self.progress_bar:
                self.progress_bar.pack_forget()
            self.status.config(text=f'错误:{str(e)}')
            messagebox.showerror("错误", f"转换失败:{str(e)}")

    def start_merge_pdf(self):
        thread = threading.Thread(target=self.merge_pdf)
        thread.start()

    def merge_pdf(self):
        try:
            file_paths = filedialog.askopenfilenames(
                title='选择要合并的PDF文件',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not file_paths:
                return

            output_path = filedialog.asksaveasfilename(
                title='保存合并后的PDF',
                defaultextension='.pdf',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not output_path:
                return

            self.status.config(text='正在合并PDF...')
            self.progress_bar = Progressbar(self.root, orient="horizontal", length=300, mode="determinate")
            self.progress_bar.pack(pady=10)

            merger = PdfMerger()
            total_files = len(file_paths)
            for i, pdf in enumerate(file_paths):
                merger.append(pdf)
                progress = (i + 1) / total_files * 100
                self.progress_bar['value'] = progress
                self.root.update_idletasks()

            merger.write(output_path)
            merger.close()
            self.progress_bar.pack_forget()
            self.status.config(text=f'成功合并{len(file_paths)}个PDF文件')
            messagebox.showinfo("完成", "PDF合并成功!")
        except Exception as e:
            if self.progress_bar:
                self.progress_bar.pack_forget()
            self.status.config(text=f'错误:{str(e)}')
            messagebox.showerror("错误", f"合并失败:{str(e)}")

    def start_split_pdf(self):
        thread = threading.Thread(target=self.split_pdf)
        thread.start()

    def split_pdf(self):
        try:
            pdf_path = filedialog.askopenfilename(
                title='选择PDF文件',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not pdf_path:
                return

            output_dir = filedialog.askdirectory(title='选择保存目录')
            if not output_dir:
                return

            split_mode = messagebox.askquestion("拆分方式", "是否拆分为单页PDF?选择否可设置每组的页数")
            pages_per_file = 1

            if split_mode == 'no':
                pages_per_file = simpledialog.askinteger("分组页数", "请输入每组包含的页数", minvalue=1)
                if not pages_per_file:
                    return

            self.status.config(text='正在拆分PDF...')
            self.progress_bar = Progressbar(self.root, orient="horizontal", length=300, mode="determinate")
            self.progress_bar.pack(pady=10)

            reader = PdfReader(pdf_path)
            total_pages = len(reader.pages)
            for i in range(0, total_pages, pages_per_file):
                merger = PdfMerger()
                end = min(i + pages_per_file, total_pages)
                merger.append(pdf_path, pages=(i, end))
                basename = os.path.splitext(os.path.basename(pdf_path))[0]
                output_path = os.path.join(output_dir, f'{basename}_split_{i // pages_per_file + 1}.pdf')
                merger.write(output_path)
                merger.close()
                progress = (i + min(pages_per_file, total_pages - i)) / total_pages * 100
                self.progress_bar['value'] = progress
                self.root.update_idletasks()

            self.progress_bar.pack_forget()
            self.status.config(text=f'拆分完成,保存至:{output_dir}')
            messagebox.showinfo("完成", f"成功拆分{total_pages}页为{total_pages // pages_per_file + (total_pages % pages_per_file > 0)}个文件!")
        except Exception as e:
            if self.progress_bar:
                self.progress_bar.pack_forget()
            self.status.config(text=f'错误:{str(e)}')
            messagebox.showerror("错误", f"拆分失败:{str(e)}")

    def encrypt_pdf(self):
        try:
            pdf_path = filedialog.askopenfilename(
                title='选择PDF文件',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not pdf_path:
                return

            output_path = filedialog.asksaveasfilename(
                title='保存加密PDF',
                defaultextension='.pdf',
                filetypes=[('加密PDF文件', '*.pdf')]
            )
            if not output_path:
                return

            password = simpledialog.askstring("PDF加密", "请输入加密密码:", show='*')
            if not password:
                self.status.config(text='操作已取消')
                return

            self.status.config(text='正在加密PDF...')
            self.progress_bar = Progressbar(self.root, orient="horizontal", length=300, mode="determinate")
            self.progress_bar.pack(pady=10)
            
            reader = PdfReader(pdf_path)
            writer = PdfWriter()
            total_pages = len(reader.pages)
            # 复制所有页面到新writer
            for i, page in enumerate(reader.pages):
                writer.add_page(page)
                progress = (i + 1) / total_pages * 100
                self.progress_bar['value'] = progress
                self.root.update_idletasks()

            # 设置加密参数
            permissions_flag = (4 | 16 | 8 | 32)
            writer.encrypt(
                user_password=password,
                owner_password=password,
                use_128bit=True,
                permissions_flag=permissions_flag
            )

            # 保存加密文件
            with open(output_path, 'wb') as f:
                writer.write(f)
            self.progress_bar.pack_forget()
            self.status.config(text=f'成功加密保存至:{output_path}')
            messagebox.showinfo("完成", "PDF文件加密成功!")
        except Exception as e:
            if self.progress_bar:
                self.progress_bar.pack_forget()
            self.status.config(text=f'错误:{str(e)}')
            messagebox.showerror("错误", f"加密失败:{str(e)}")

    def decrypt_pdf(self):
        try:
            pdf_path = filedialog.askopenfilename(
                title='选择加密PDF文件',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not pdf_path:
                return

            password = simpledialog.askstring("PDF解密", "请输入解密密码:", show='*')
            if not password:
                self.status.config(text='操作已取消')
                return

            output_path = filedialog.asksaveasfilename(
                title='保存解密PDF',
                defaultextension='.pdf',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not output_path:
                return

            self.status.config(text='正在解密PDF...')
            self.progress_bar = Progressbar(self.root, orient="horizontal", length=300, mode="determinate")
            self.progress_bar.pack(pady=10)

            reader = PdfReader(pdf_path)
            if reader.is_encrypted:
                if reader.decrypt(password) == 0:
                    raise Exception("密码错误或文件损坏")

            writer = PdfWriter()
            total_pages = len(reader.pages)
            for i, page in enumerate(reader.pages):
                writer.add_page(page)
                progress = (i + 1) / total_pages * 100
                self.progress_bar['value'] = progress
                self.root.update_idletasks()

            writer.encrypt("", "", use_128bit=False)

            with open(output_path, 'wb') as f:
                writer.write(f)
            self.progress_bar.pack_forget()
            self.status.config(text=f'成功解密保存至:{output_path}')
            messagebox.showinfo("完成", "PDF文件解密成功!")
        except Exception as e:
            if self.progress_bar:
                self.progress_bar.pack_forget()
            self.status.config(text=f'错误:{str(e)}')
            messagebox.showerror("错误", f"解密失败:{str(e)}")

    def start_word_to_pdf(self):
        thread = threading.Thread(target=self.word_to_pdf)
        thread.start()

    def word_to_pdf(self):
        try:
            docx_path = filedialog.askopenfilename(
                title='选择Word文档',
                filetypes=[('Word文件', '*.docx *.doc')]
            )
            if not docx_path:
                return

            output_path = filedialog.asksaveasfilename(
                title='保存PDF文件',
                initialfile=os.path.splitext(os.path.basename(docx_path))[0] + '.pdf',
                defaultextension='.pdf',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not output_path:
                return

            self.status.config(text='正在转换Word文档...')
            self.progress_bar = Progressbar(self.root, orient="horizontal", length=300, mode="determinate")
            self.progress_bar.pack(pady=10)

            try:
                word = win32com.client.Dispatch('Word.Application')
                doc = word.Documents.Open(docx_path)
                try:
                    doc.SaveAs(output_path, FileFormat=17)
                except Exception as e:
                    # 记录详细的错误信息
                    import traceback
                    error_log = traceback.format_exc()
                    print(f"保存文件时出错: {error_log}")
                    raise e
                finally:
                    # 确保文档关闭
                    doc.Close()
                    # 确保 Word 应用程序退出
                    word.Quit()

                self.progress_bar['value'] = 100
                self.root.update_idletasks()
                self.progress_bar.pack_forget()
                self.status.config(text=f'成功转换:{os.path.basename(output_path)}')
                messagebox.showinfo("完成", "Word转PDF成功!")
            except Exception as e:
                if self.progress_bar:
                    self.progress_bar.pack_forget()
                error_msg = f"转换失败:{str(e)}\n请检查:\n1. 是否安装Microsoft Office\n2. Python与Office同为32位或64位版本\n3. 文件路径不要包含特殊字符"
                self.status.config(text='错误:' + error_msg)
                messagebox.showerror("错误", error_msg)
        except Exception as e:
            # 记录详细的错误信息
            import traceback
            error_log = traceback.format_exc()
            print(f"调用 word_to_pdf 方法时出错: {error_log}")

    def start_excel_to_pdf(self):
        thread = threading.Thread(target=self.excel_to_pdf)
        thread.start()

    def excel_to_pdf(self):
        try:
            file_path = filedialog.askopenfilename(
                title='选择Excel文件',
                filetypes=[('Excel文件', '*.xls *.xlsx')]
            )
            if not file_path:
                return

            output_path = filedialog.asksaveasfilename(
                title='保存PDF文件',
                initialfile=os.path.splitext(os.path.basename(file_path))[0] + '.pdf',
                defaultextension='.pdf',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not output_path:
                return

            self.status.config(text='正在转换Excel...')
            self.progress_bar = Progressbar(self.root, orient="horizontal", length=300, mode="determinate")
            self.progress_bar.pack(pady=10)

            excel = win32com.client.Dispatch('Excel.Application')
            doc = excel.Workbooks.Open(file_path)
            doc.ExportAsFixedFormat(0, output_path)
            doc.Close()
            excel.Quit()
            self.progress_bar['value'] = 100
            self.root.update_idletasks()
            self.progress_bar.pack_forget()
            self.status.config(text=f'转换完成: {os.path.basename(output_path)}')
            messagebox.showinfo("完成", "Excel转PDF成功!")
        except Exception as e:
            if self.progress_bar:
                self.progress_bar.pack_forget()
            self.status.config(text=f'错误:{str(e)}')
            messagebox.showerror("错误", f"转换失败:{str(e)}")

    def start_ppt_to_pdf(self):
        thread = threading.Thread(target=self.ppt_to_pdf)
        thread.start()

    def ppt_to_pdf(self):
        try:
            file_path = filedialog.askopenfilename(
                title='选择PPT文件',
                filetypes=[('PPT文件', '*.ppt *.pptx')]
            )
            if not file_path:
                return

            output_path = filedialog.asksaveasfilename(
                title='保存PDF文件',
                initialfile=os.path.splitext(os.path.basename(file_path))[0] + '.pdf',
                defaultextension='.pdf',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not output_path:
                return

            self.status.config(text='正在转换PPT...')
            self.progress_bar = Progressbar(self.root, orient="horizontal", length=300, mode="determinate")
            self.progress_bar.pack(pady=10)

            powerpoint = win32com.client.Dispatch('PowerPoint.Application')
            doc = powerpoint.Presentations.Open(file_path)
            doc.SaveAs(output_path, 32)  # 32是PDF格式
            doc.Close()
            powerpoint.Quit()
            self.progress_bar['value'] = 100
            self.root.update_idletasks()
            self.progress_bar.pack_forget()
            self.status.config(text=f'转换完成: {os.path.basename(output_path)}')
            messagebox.showinfo("完成", "PPT转PDF成功!")
        except Exception as e:
            if self.progress_bar:
                self.progress_bar.pack_forget()
            self.status.config(text=f'错误:{str(e)}')
            messagebox.showerror("错误", f"转换失败:{str(e)}")

    def start_images_to_pdf(self):
        thread = threading.Thread(target=self.images_to_pdf)
        thread.start()

    def images_to_pdf(self):
        try:
            file_paths = filedialog.askopenfilenames(
                title='选择图片文件',
                filetypes=[('图片文件', '*.jpg *.jpeg *.png')]
            )
            if not file_paths:
                return

            now = datetime.now().strftime('%Y%m%d%H%M%S')
            output_path = filedialog.asksaveasfilename(
                title='保存PDF文件',
                initialfile= f'merged_{now}.pdf',
                defaultextension='.pdf',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not output_path:
                return

            self.status.config(text='正在生成PDF...')
            self.progress_bar = Progressbar(self.root, orient="horizontal", length=300, mode="determinate")
            self.progress_bar.pack(pady=10)

            images = [Image.open(f) for f in file_paths]
            total_images = len(images)
            if images:
                images[0].save(
                    output_path, "PDF", resolution=100.0,
                    save_all=True, append_images=images[1:]
                )
            self.progress_bar['value'] = 100
            self.root.update_idletasks()
            self.progress_bar.pack_forget()
            self.status.config(text=f'成功生成PDF:{os.path.basename(output_path)}')
            messagebox.showinfo("完成", "图片转PDF成功!")
        except Exception as e:
            if self.progress_bar:
                self.progress_bar.pack_forget()
            self.status.config(text=f'错误:{str(e)}')
            messagebox.showerror("错误", f"转换失败:{str(e)}")

    def get_images_from_pdf(self):
        try:
            pdf_path = filedialog.askopenfilename(
                title='选择PDF文件',
                filetypes=[('PDF文件', '*.pdf')]
            )
            if not pdf_path:
                return

            output_dir = filedialog.askdirectory(title='选择图片保存目录')
            if not output_dir:
                return

            self.status.config(text='正在转换PDF...')
            self.progress_bar = Progressbar(self.root, orient="horizontal", length=300, mode="determinate")
            self.progress_bar.pack(pady=10)

            reader = PdfReader(pdf_path)
            total_pages = len(reader.pages)

            # 获取 PDF 文件名(不含扩展名)
            pdf_basename = os.path.splitext(os.path.basename(pdf_path))[0]

            for i, page in enumerate(reader.pages):
                for img in page.images:
                    # 过滤文件名中的不合法字符
                    valid_name = ''.join(c for c in img.name if c.isalnum() or c in (' ', '.', '_')).rstrip()
                    # 将图像模式转换为支持 PNG 格式的模式
                    image = Image.open(io.BytesIO(img.data))
                    if image.mode == 'PA':
                        image = image.convert('RGBA')
                    # 以 PDF 文件名作为前缀生成图片文件名
                    image_path = os.path.join(output_dir, f'{pdf_basename}_page_{i + 1}_{valid_name}')
                    image.save(image_path, 'PNG')
                progress = (i + 1) / total_pages * 100
                self.progress_bar['value'] = progress
                self.root.update_idletasks()

            self.progress_bar.pack_forget()
            self.status.config(text=f'转换完成,保存至:{output_dir}')
            messagebox.showinfo("完成", f"成功转换{len(reader.pages)}页!")
        except Exception as e:
            if self.progress_bar:
                self.progress_bar.pack_forget()
            self.status.config(text=f'错误:{str(e)}')
            messagebox.showerror("错误", f"转换失败:{str(e)}")


if __name__ == "__main__":
    app = FileConverterApp()
    app.root.mainloop()

感兴趣的同学,赶紧去体验吧!

优弧于2025-03-03 11:26发布的图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值