PPT转图片拼贴工具 v4.3

软件介绍

这个软件就是将PPT文件转换为图片并且拼接起来。

效果展示

支持导入文件和支持导入文件夹,也支持手动输入文件/文件夹路径

软件界面

这一次提供了源码和开箱即用版本,exe就是直接用就可以了。

软件源码

python 复制代码
import os
import re
import sys
import win32com.client
from PIL import Image
from typing import List, Union
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QFileDialog, QProgressBar, QTextEdit, QScrollBar, QFrame, QStyleFactory
from PyQt5.QtGui import QFont, QIcon
from PyQt5.QtCore import Qt, QThread, pyqtSignal
import pythoncom
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices


class PPTtoImageConverter:
    """PPT转图片拼贴的核心功能类"""

    def __init__(self):
        pass

    def convert_ppt_to_png(self, ppt_path: str, output_folder: str) -> None:
        """将单个PPT文件转换为PNG图片"""
        pythoncom.CoInitialize()
        try:
            ppt_app = win32com.client.Dispatch("PowerPoint.Application")
        except Exception as e:
            pythoncom.CoUninitialize()
            raise RuntimeError(f"无法启动 PowerPoint 应用程序: {e}")

        if not os.path.exists(ppt_path):
            pythoncom.CoUninitialize()
            raise FileNotFoundError(f"PPT 文件不存在: {ppt_path}")

        try:
            presentation = ppt_app.Presentations.Open(ppt_path, WithWindow=False)
            presentation.SaveAs(output_folder, 18)  # 18 代表 PNG 格式
            presentation.Close()
        finally:
            ppt_app.Quit()
            pythoncom.CoUninitialize()

    def create_collage(self, input_folder: str, output_folder: str, ppt_name: str,
                       row_size: int = 3, col_gap: int = 10, row_gap: int = 10) -> None:
        """从PNG图片创建拼贴画"""
        files = os.listdir(input_folder)
        slide_files = [f for f in files if re.match(r"幻灯片\d+\.png", f, re.IGNORECASE)]

        if not slide_files:
            raise RuntimeError(f"未找到幻灯片图片文件")

        slide_files.sort(key=lambda x: int(re.search(r'\d+', x).group()))

        try:
            images = [Image.open(os.path.join(input_folder, f)) for f in slide_files]
        except Exception as e:
            raise RuntimeError(f"加载图片时出错: {e}")

        if not images:
            raise RuntimeError("没有可处理的图片")

        width, height = images[0].size

        first_img = images[0].resize(
            (width * row_size + col_gap * (row_size - 1),
             height * row_size + int(col_gap * (row_size - 1) * height / width)),
            Image.LANCZOS
        )

        remaining_images = images[1:]

        rows = (len(remaining_images) + row_size - 1) // row_size
        canvas_width = first_img.width
        canvas_height = first_img.height + rows * (height + row_gap)

        collage_image = Image.new("RGB", (canvas_width, canvas_height), (255, 255, 255))

        collage_image.paste(first_img, (0, 0))

        for i, img in enumerate(remaining_images):
            row = i // row_size
            col = i % row_size
            x = col * (width + col_gap)
            y = first_img.height + row * (height + row_gap)
            collage_image.paste(img, (x, y))

        collage_path = os.path.join(output_folder, f"{ppt_name}.png")
        collage_image.save(collage_path)

        for f in slide_files:
            os.remove(os.path.join(input_folder, f))

    def process_ppt_item(self, item_path: str, output_folder: str,
                         row_size: int = 3, col_gap: int = 10, row_gap: int = 10,
                         progress_callback=None) -> None:
        """处理单个PPT文件或文件夹"""
        processed_count = 0
        total_files = 0

        if os.path.isfile(item_path):
            if item_path.lower().endswith(('.ppt', '.pptx')):
                total_files = 1
                try:
                    ppt_filename = os.path.basename(item_path)
                    ppt_name = os.path.splitext(ppt_filename)[0]

                    # 为每个PPT创建单独的临时文件夹
                    temp_folder = os.path.join(output_folder, f"temp_{ppt_name}")
                    os.makedirs(temp_folder, exist_ok=True)

                    self.convert_ppt_to_png(item_path, temp_folder)

                    self.create_collage(temp_folder, output_folder, ppt_name, row_size, col_gap, row_gap)

                    # 清理临时文件夹
                    for f in os.listdir(temp_folder):
                        os.remove(os.path.join(temp_folder, f))
                    os.rmdir(temp_folder)

                    message = f"✓ 处理完成: {ppt_name}.png"
                    if progress_callback:
                        progress_callback(message, 100)
                    processed_count = 1
                except Exception as e:
                    message = f"⚠️ 处理失败: {os.path.basename(item_path)} - {str(e)}"
                    if progress_callback:
                        progress_callback(message, 100)
            else:
                message = f"⚠️ 跳过非PPT文件: {os.path.basename(item_path)}"
                if progress_callback:
                    progress_callback(message, 100)
        elif os.path.isdir(item_path):
            message = f"处理文件夹: {item_path}"
            if progress_callback:
                progress_callback(message, 0)

            ppt_files = [f for f in os.listdir(item_path)
                         if f.lower().endswith(('.ppt', '.pptx'))]
            total_files = len(ppt_files)

            for i, filename in enumerate(ppt_files):
                file_path = os.path.join(item_path, filename)
                try:
                    ppt_name = os.path.splitext(filename)[0]

                    # 为每个PPT创建单独的临时文件夹
                    temp_folder = os.path.join(output_folder, f"temp_{ppt_name}")
                    os.makedirs(temp_folder, exist_ok=True)

                    self.convert_ppt_to_png(file_path, temp_folder)

                    self.create_collage(temp_folder, output_folder, ppt_name, row_size, col_gap, row_gap)

                    # 清理临时文件夹
                    for f in os.listdir(temp_folder):
                        os.remove(os.path.join(temp_folder, f))
                    os.rmdir(temp_folder)

                    message = f"✓ 处理完成 ({i + 1}/{total_files}): {ppt_name}.png"
                    progress = int((i + 1) / total_files * 100)
                    if progress_callback:
                        progress_callback(message, progress)
                    processed_count += 1
                except Exception as e:
                    message = f"⚠️ 处理失败 ({i + 1}/{total_files}): {filename} - {str(e)}"
                    if progress_callback:
                        progress_callback(message, int(i / total_files * 100))
        else:
            message = f"⚠️ 路径不存在或无法访问: {item_path}"
            if progress_callback:
                progress_callback(message, 100)

        return processed_count, total_files


class ProcessingThread(QThread):
    progress_signal = pyqtSignal(str, int)

    def __init__(self, converter, input_path, output_path, row_size, col_gap, row_gap):
        super().__init__()
        self.converter = converter
        self.input_path = input_path
        self.output_path = output_path
        self.row_size = row_size
        self.col_gap = col_gap
        self.row_gap = row_gap

    def run(self):
        try:
            self.converter.process_ppt_item(
                self.input_path,
                self.output_path,
                self.row_size,
                self.col_gap,
                self.row_gap,
                self.update_progress
            )
            self.progress_signal.emit("✅ 所有文件处理完成!", 100)
        except Exception as e:
            self.progress_signal.emit(f"❌ 处理过程中发生错误: {str(e)}", 100)

    def update_progress(self, message, progress):
        self.progress_signal.emit(message, progress)


class PPTtoImageGUI(QWidget):
    """PPT转图片拼贴的图形界面类"""

    def __init__(self):
        super().__init__()
        self.initUI()
        self.converter = PPTtoImageConverter()
        self.processing = False
        self.process_thread = None

    def initUI(self):
        self.setWindowTitle("PPT转图片拼贴工具@阿幸")
        self.setGeometry(100, 100, 700, 550)
        
        # 设置应用程序图标
        self.setWindowIcon(QIcon("PTT.ico"))  # 请确保app.ico文件存在于程序运行目录下

        main_layout = QVBoxLayout()

        # 输入路径选择
        input_layout = QHBoxLayout()
        input_label = QLabel("输入路径:")
        input_label.setFont(QFont("Microsoft YaHei", 10))
        self.input_path_edit = QLineEdit()
        self.input_path_edit.setFont(QFont("Microsoft YaHei", 10))
        
        # 浏览按钮 - 文件选择
        browse_file_button = QPushButton("选择文件")
        browse_file_button.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        browse_file_button.setFixedWidth(150)
        browse_file_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #1976D2;
            }
        """)
        browse_file_button.clicked.connect(self.browse_file)
        
        # 浏览按钮 - 文件夹选择
        browse_folder_button = QPushButton("选择文件夹")
        browse_folder_button.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        browse_folder_button.setFixedWidth(150)
        browse_folder_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #1976D2;
            }
        """)
        browse_folder_button.clicked.connect(self.browse_folder)
        
        input_layout.addWidget(input_label)
        input_layout.addWidget(self.input_path_edit)
        input_layout.addWidget(browse_file_button)
        input_layout.addWidget(browse_folder_button)
        main_layout.addLayout(input_layout)

        # 输出路径选择
        output_layout = QHBoxLayout()
        output_label = QLabel("输出路径:")
        output_label.setFont(QFont("Microsoft YaHei", 10))
        self.output_path_edit = QLineEdit()
        self.output_path_edit.setFont(QFont("Microsoft YaHei", 10))
        browse_output_button = QPushButton("浏览")
        browse_output_button.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        browse_output_button.setFixedWidth(150)
        browse_output_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #1976D2;
            }
        """)
        browse_output_button.clicked.connect(self.browse_output_path)
        output_layout.addWidget(output_label)
        output_layout.addWidget(self.output_path_edit)
        output_layout.addWidget(browse_output_button)
        main_layout.addLayout(output_layout)

        # 设置参数
        params_frame = QFrame()
        params_frame.setFrameShape(QFrame.StyledPanel)
        params_layout = QVBoxLayout(params_frame)
        params_label = QLabel("拼贴设置")
        params_label.setFont(QFont("Microsoft YaHei", 10))
        params_layout.addWidget(params_label)

        # 每行图片数量
        row_size_layout = QHBoxLayout()
        row_size_label = QLabel("每行图片数量:")
        row_size_label.setFont(QFont("Microsoft YaHei", 10))
        self.row_size_edit = QLineEdit()
        self.row_size_edit.setFont(QFont("Microsoft YaHei", 10))
        self.row_size_edit.setText("3")
        row_size_minus_button = QPushButton("-")
        row_size_minus_button.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        row_size_minus_button.setFixedWidth(50)
        row_size_minus_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #1976D2;
            }
        """)
        row_size_minus_button.clicked.connect(lambda: self.decrement_var(self.row_size_edit, 1, 10))
        row_size_plus_button = QPushButton("+")
        row_size_plus_button.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        row_size_plus_button.setFixedWidth(50)
        row_size_plus_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #1976D2;
            }
        """)
        row_size_plus_button.clicked.connect(lambda: self.increment_var(self.row_size_edit, 1, 10))
        row_size_layout.addWidget(row_size_label)
        row_size_layout.addWidget(self.row_size_edit)
        row_size_layout.addWidget(row_size_minus_button)
        row_size_layout.addWidget(row_size_plus_button)
        params_layout.addLayout(row_size_layout)

        # 列间距
        col_gap_layout = QHBoxLayout()
        col_gap_label = QLabel("列间距(像素):")
        col_gap_label.setFont(QFont("Microsoft YaHei", 10))
        self.col_gap_edit = QLineEdit()
        self.col_gap_edit.setFont(QFont("Microsoft YaHei", 10))
        self.col_gap_edit.setText("10")
        col_gap_minus_button = QPushButton("-")
        col_gap_minus_button.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        col_gap_minus_button.setFixedWidth(50)
        col_gap_minus_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #1976D2;
            }
        """)
        col_gap_minus_button.clicked.connect(lambda: self.decrement_var(self.col_gap_edit, 0, 50))
        col_gap_plus_button = QPushButton("+")
        col_gap_plus_button.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        col_gap_plus_button.setFixedWidth(50)
        col_gap_plus_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #1976D2;
            }
        """)
        col_gap_plus_button.clicked.connect(lambda: self.increment_var(self.col_gap_edit, 0, 50))
        col_gap_layout.addWidget(col_gap_label)
        col_gap_layout.addWidget(self.col_gap_edit)
        col_gap_layout.addWidget(col_gap_minus_button)
        col_gap_layout.addWidget(col_gap_plus_button)
        params_layout.addLayout(col_gap_layout)

        # 行间距
        row_gap_layout = QHBoxLayout()
        row_gap_label = QLabel("行间距(像素):")
        row_gap_label.setFont(QFont("Microsoft YaHei", 10))
        self.row_gap_edit = QLineEdit()
        self.row_gap_edit.setFont(QFont("Microsoft YaHei", 10))
        self.row_gap_edit.setText("10")
        row_gap_minus_button = QPushButton("-")
        row_gap_minus_button.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        row_gap_minus_button.setFixedWidth(50)
        row_gap_minus_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #1976D2;
            }
        """)
        row_gap_minus_button.clicked.connect(lambda: self.decrement_var(self.row_gap_edit, 0, 50))
        row_gap_plus_button = QPushButton("+")
        row_gap_plus_button.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        row_gap_plus_button.setFixedWidth(50)
        row_gap_plus_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #1976D2;
            }
        """)
        row_gap_plus_button.clicked.connect(lambda: self.increment_var(self.row_gap_edit, 0, 50))
        row_gap_layout.addWidget(row_gap_label)
        row_gap_layout.addWidget(self.row_gap_edit)
        row_gap_layout.addWidget(row_gap_minus_button)
        row_gap_layout.addWidget(row_gap_plus_button)
        params_layout.addLayout(row_gap_layout)

        main_layout.addWidget(params_frame)

        # 处理按钮 - 使用水平布局放置两个按钮
        button_layout = QHBoxLayout()

        self.process_button = QPushButton("开始处理")
        self.process_button.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        self.process_button.setFixedWidth(120)
        self.process_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #1976D2;
            }
        """)
        self.process_button.clicked.connect(self.start_processing)
        button_layout.addWidget(self.process_button, alignment=Qt.AlignCenter)

        # 添加"关于阿幸"按钮
        self.about_button = QPushButton("关于阿幸")
        self.about_button.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        self.about_button.setFixedWidth(120)
        self.about_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3;
                color: white;
                border: none;
                padding: 5px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #388E3C;
            }
        """)
        self.about_button.clicked.connect(self.open_axing_website)
        button_layout.addWidget(self.about_button, alignment=Qt.AlignCenter)

        main_layout.addLayout(button_layout)

        # 进度条
        progress_layout = QHBoxLayout()
        progress_label = QLabel("处理进度:")
        progress_label.setFont(QFont("Microsoft YaHei", 10))
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setValue(0)
        progress_layout.addWidget(progress_label)
        progress_layout.addWidget(self.progress_bar)
        main_layout.addLayout(progress_layout)

        # 日志区域
        log_layout = QHBoxLayout()
        log_label = QLabel("处理日志:")
        log_label.setFont(QFont("Microsoft YaHei", 10))
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        log_scrollbar = QScrollBar(Qt.Vertical)
        self.log_text.setVerticalScrollBar(log_scrollbar)
        log_layout.addWidget(log_label)
        log_layout.addWidget(self.log_text)
        main_layout.addLayout(log_layout)

        self.setLayout(main_layout)

        # 设置默认路径
        self.input_path_edit.setText(r"D:\Desktop\文件存储\1")
        self.output_path_edit.setText(r"D:\Desktop\文件存储\1")

    def browse_file(self):
        """浏览并选择单个PPT文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择PPT文件", "", "PowerPoint 文件 (*.ppt *.pptx)"
        )
        if file_path:
            self.input_path_edit.setText(file_path)

    def browse_folder(self):
        """浏览并选择文件夹"""
        folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹")
        if folder_path:
            self.input_path_edit.setText(folder_path)

    def browse_output_path(self):
        """浏览并选择输出路径"""
        path = QFileDialog.getExistingDirectory(self, "选择输出文件夹")
        if path:
            self.output_path_edit.setText(path)

    def start_processing(self):
        """开始处理PPT文件"""
        if self.processing:
            return

        input_path = self.input_path_edit.text()
        output_path = self.output_path_edit.text()

        if not input_path or not output_path:
            print("请设置输入路径和输出路径")
            return

        if not os.path.exists(input_path):
            print(f"输入路径不存在: {input_path}")
            return

        os.makedirs(output_path, exist_ok=True)

        self.log_text.clear()
        self.progress_bar.setValue(0)

        row_size = int(self.row_size_edit.text())
        col_gap = int(self.col_gap_edit.text())
        row_gap = int(self.row_gap_edit.text())

        self.processing = True
        self.process_button.setText("处理中...")
        self.process_button.setEnabled(False)

        self.process_thread = ProcessingThread(self.converter, input_path, output_path, row_size, col_gap, row_gap)
        self.process_thread.progress_signal.connect(self.update_progress)
        self.process_thread.finished.connect(self.process_finished)
        self.process_thread.start()

    def update_progress(self, message, progress):
        self.log_text.append(message)
        self.progress_bar.setValue(progress)

    def process_finished(self):
        self.processing = False
        self.process_button.setText("开始处理")
        self.process_button.setEnabled(True)

    def increment_var(self, edit, min_val, max_val):
        """增加变量值,不超过最大值"""
        current = int(edit.text())
        if current < max_val:
            edit.setText(str(current + 1))

    def decrement_var(self, edit, min_val, max_val):
        """减少变量值,不小于最小值"""
        current = int(edit.text())
        if current > min_val:
            edit.setText(str(current - 1))

    def open_axing_website(self):
        """打开关于阿幸的网站"""
        url = QUrl("https://a-xing.top/")
        QDesktopServices.openUrl(url)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle(QStyleFactory.create('Fusion'))
    gui = PPTtoImageGUI()
    gui.show()
    sys.exit(app.exec_())

源码下载

https://pan.quark.cn/s/f8bf2904e8c3

相关推荐
weixin_408099673 分钟前
OCR 识别率提升实战:模糊 / 倾斜 / 反光图片全套优化方案(附 Python / Java / PHP 代码)
图像处理·人工智能·后端·python·ocr·api·抠图
翻斗包菜5 分钟前
Python 网络编程从入门到精通:TCP/UDP/Socket 实战详解
网络·python·tcp/ip
七颗糖很甜17 分钟前
雨滴谱数据深度解析——从原始变量到科学产品的Python实现【下篇】
python·算法·pandas
爱码小白19 分钟前
MySQL 常用数据类型的系统总结
数据库·python·算法
xcbrand23 分钟前
专精特新品牌全案公司有哪些
大数据·人工智能·python
橘子编程27 分钟前
GoF 23 种设计模式完整知识总结与使用教程
java·c语言·开发语言·python·设计模式
枫叶林FYL30 分钟前
【Python高级工程与架构实战】项目五:生产级LLM Agent框架:基于PydanticAI的类型安全企业级实现
python·安全·架构
ths51230 分钟前
Python 正则表达式学习笔记(小白超详细版)(一)
python·正则表达式
飞Link35 分钟前
pprint 全量技术手册:复杂数据结构的结构化输出引擎
开发语言·前端·python
培风图南以星河揽胜40 分钟前
幻想简历!博主本人期望的 AI Agent 全栈简历:Java + Python + Vue3 跨语言实战,代码已开源!
java·人工智能·python