软件介绍
这是批量将word、Excel、PPT转换为PDF格式的软件,不过PPT转换为PDF需要电脑安装了office,目前这个我还没有解决没有office也可以安装的方法。
软件使用
软件使用是比较简单的,导入文件/文件夹,在自定义输出路径
点击这里的【分析文件】
然后就可以转换了
转换完成
软件源码
我吧软件源码也一起放在这里了,如果有大佬可以解决不安装office也可以将PPT转换为PDF的话欢迎在评论区留下你的代码
当然,为了防止小白不懂怎么打包,我也将成品放在文末了。
import os
import sys
import gc
import time
import pythoncom
import win32com.client
from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QLabel, QLineEdit, QTextEdit, QVBoxLayout, QHBoxLayout, QWidget, QFileDialog, QProgressBar, QMessageBox,QGroupBox, QFormLayout, QListWidget,QListWidgetItem, QMenu, QAction)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject
from PyQt5.QtGui import QFont, QColorclass ConversionWorker(QObject):"""转换工作线程,负责后台执行转换任务"""progress_updated = pyqtSignal(int, str) # 进度更新信号 (进度值, 状态文本)conversion_finished = pyqtSignal(str) # 转换完成信号 (完成消息)log_message = pyqtSignal(str) # 日志消息信号def __init__(self, file_items, output_path):super().__init__()self.file_items = file_items # 包含路径和文件名的字典列表self.output_path = output_pathself.is_running = Truedef stop(self):"""停止转换任务"""self.is_running = Falsedef run(self):"""执行转换任务"""try:# 初始化COM组件pythoncom.CoInitialize()# 确保PDF文件夹存在pdf_folder = self.output_pathif not os.path.exists(pdf_folder):os.makedirs(pdf_folder)self.log_message.emit(f"已创建PDF文件夹: {pdf_folder}")# 分类文件words = []excels = []ppts = []for item in self.file_items:file_path = item['path']filename = item['name']lower_filename = filename.lower()if lower_filename.endswith(('.doc', '.docx')):words.append({"path": file_path, "name": filename})elif lower_filename.endswith(('.xls', '.xlsx')):excels.append({"path": file_path, "name": filename})elif lower_filename.endswith(('.ppt', '.pptx')):ppts.append({"path": file_path, "name": filename})total_files = len(words) + len(excels) + len(ppts)processed_files = 0# 初始化转换统计变量word_success, word_failed = 0, 0excel_success, excel_failed = 0, 0ppt_success, ppt_failed = 0, 0# 转换Word文件if words and self.is_running:self.log_message.emit("\n【开始 Word -> PDF 转换】")word_success, word_failed = self.convert_words(words)processed_files += len(words)progress = int((processed_files / total_files) * 100) if total_files > 0 else 100self.progress_updated.emit(progress, f"已完成Word转换: {word_success}成功, {word_failed}失败")# 转换Excel文件if excels and self.is_running:self.log_message.emit("\n【开始 Excel -> PDF 转换】")excel_success, excel_failed = self.convert_excels(excels)processed_files += len(excels)progress = int((processed_files / total_files) * 100) if total_files > 0 else 100self.progress_updated.emit(progress, f"已完成Excel转换: {excel_success}成功, {excel_failed}失败")# 转换PPT文件if ppts and self.is_running:self.log_message.emit("\n【开始 PPT -> PDF 转换】")ppt_success, ppt_failed = self.convert_ppts(ppts)processed_files += len(ppts)progress = int((processed_files / total_files) * 100) if total_files > 0 else 100self.progress_updated.emit(progress, f"已完成PPT转换: {ppt_success}成功, {ppt_failed}失败")if self.is_running:total_success = word_success + excel_success + ppt_successtotal_failed = word_failed + excel_failed + ppt_failedself.conversion_finished.emit(f"转换完成!\n总计: {total_files}个文件\n成功: {total_success}个\n失败: {total_failed}个\nPDF文件保存位置: {self.output_path}")else:self.conversion_finished.emit("转换已取消")except Exception as e:self.log_message.emit(f"转换过程出错: {str(e)}")self.conversion_finished.emit(f"转换出错: {str(e)}")finally:# 释放COM组件pythoncom.CoUninitialize()gc.collect()def convert_words(self, words):"""转换Word文件为PDF"""success = 0failed = 0word = Nonedoc = Nonetry:self.log_message.emit("打开Word进程...")word = win32com.client.Dispatch("Word.Application")word.Visible = 0word.DisplayAlerts = Falsefor i, item in enumerate(words):if not self.is_running:breakfilename = item["name"]file_path = item["path"]self.log_message.emit(f"正在转换: {filename} ({i+1}/{len(words)})")from_file = os.path.join(file_path, filename)to_filename = self.change_suffix_to_pdf(filename)to_file = self.to_file_join(self.output_path, to_filename)try:doc = word.Documents.Open(from_file)doc.SaveAs(to_file, 17) # 17 表示PDF格式self.log_message.emit(f"转换完成: {to_filename}")success += 1except Exception as e:self.log_message.emit(f"转换失败 {filename}: {str(e)}")failed += 1finally:if doc:doc.Close()doc = Nonetime.sleep(0.5)except Exception as e:self.log_message.emit(f"Word转换出错: {str(e)}")failed += len(words) - successfinally:if doc:try:doc.Close()except:passif word:try:word.Quit()except:passself.log_message.emit("关闭Word进程")time.sleep(1)return success, faileddef convert_excels(self, excels):"""转换Excel文件为PDF"""success = 0failed = 0excel = Nonewb = Nonews = Nonetry:self.log_message.emit("打开Excel进程...")excel = win32com.client.Dispatch("Excel.Application")excel.Visible = 0excel.DisplayAlerts = Falsefor i, item in enumerate(excels):if not self.is_running:breakfilename = item["name"]file_path = item["path"]self.log_message.emit(f"正在转换: {filename} ({i+1}/{len(excels)})")from_file = os.path.join(file_path, filename)try:wb = excel.Workbooks.Open(from_file)sheet_count = wb.Worksheets.Countfor j in range(sheet_count):ws = wb.Worksheets(j+1)to_filename = self.add_worksheet_order(filename, j+1)to_file = self.to_file_join(self.output_path, to_filename)ws.ExportAsFixedFormat(0, to_file) # 0 表示PDF格式self.log_message.emit(f"转换完成: {to_filename}")success += 1time.sleep(0.5)except Exception as e:self.log_message.emit(f"转换失败 {filename}: {str(e)}")failed += 1finally:if ws:try:ws = Noneexcept:passif wb:try:wb.Close(SaveChanges=False)wb = Noneexcept:passtime.sleep(0.5)except Exception as e:self.log_message.emit(f"Excel转换出错: {str(e)}")failed += len(excels) - successfinally:if ws:try:ws = Noneexcept:passif wb:try:wb.Close(SaveChanges=False)except:passif excel:try:excel.Quit()except:passself.log_message.emit("关闭Excel进程")time.sleep(1)return success, faileddef convert_ppts(self, ppts):"""转换PPT文件为PDF(增强版错误处理)"""success = 0failed = 0powerpoint = Noneppt = Nonetry:self.log_message.emit("打开PowerPoint进程...")# 创建PowerPoint实例powerpoint = win32com.client.Dispatch("PowerPoint.Application")# 尝试设置可见性try:powerpoint.Visible = 1 # 显示窗口self.log_message.emit("PowerPoint窗口已显示")except Exception as e:self.log_message.emit(f"无法设置PowerPoint可见性: {str(e)}")powerpoint.DisplayAlerts = 0 # 禁用警告for i, item in enumerate(ppts):if not self.is_running:breakfilename = item["name"]file_path = item["path"]self.log_message.emit(f"正在转换: {filename} ({i+1}/{len(ppts)})")from_file = os.path.abspath(os.path.join(file_path, filename))to_filename = self.change_suffix_to_pdf(filename)to_file = os.path.abspath(self.to_file_join(self.output_path, to_filename))# 检查文件是否存在if not os.path.exists(from_file):self.log_message.emit(f"文件不存在: {from_file}")failed += 1continue# 检查文件是否可访问if not os.access(from_file, os.R_OK):self.log_message.emit(f"没有文件读取权限: {from_file}")failed += 1continue# 确保输出目录可写if not os.access(self.output_path, os.W_OK):self.log_message.emit(f"输出目录没有写入权限: {self.output_path}")failed += 1continue# 确保输出文件不存在if os.path.exists(to_file):try:os.remove(to_file)self.log_message.emit(f"已删除已存在的文件: {to_filename}")except Exception as e:self.log_message.emit(f"无法删除已存在的文件 {to_filename}: {str(e)}")failed += 1continuetry:# 尝试多种方式打开文件try:# 方式1: 标准打开ppt = powerpoint.Presentations.Open(FileName=from_file,ReadOnly=True,WithWindow=True # 显示窗口可能解决权限问题)except:self.log_message.emit("尝试备用方式打开文件...")# 方式2: 备用打开方式ppt = powerpoint.Presentations.Open(FileName=from_file,ReadOnly=False,WithWindow=True)time.sleep(2) # 更长延迟确保文件加载if not ppt:raise Exception("无法打开演示文稿")if ppt.Slides.Count == 0:self.log_message.emit(f"跳过空文件: {filename}")failed += 1continue# 尝试多种转换方法conversion_methods = [# 方法1: SaveAs PDF格式lambda: ppt.SaveAs(to_file, 32),# 方法2: ExportAsFixedFormat PDF格式lambda: ppt.ExportAsFixedFormat(to_file, 2, Intent=1),# 方法3: 另存为PDF的另一种参数组合lambda: ppt.SaveAs(to_file, 32, EmbedTrueTypeFonts=True)]converted = Falsefor method_idx, convert_method in enumerate(conversion_methods, 1):try:self.log_message.emit(f"尝试转换方法 {method_idx}...")convert_method()time.sleep(3) # 更长延迟确保转换完成# 检查文件是否生成if os.path.exists(to_file) and os.path.getsize(to_file) > 0:self.log_message.emit(f"转换完成: {to_filename}")success += 1converted = Truebreakexcept Exception as e:self.log_message.emit(f"转换方法 {method_idx} 失败: {str(e)}")# 删除可能的空文件if os.path.exists(to_file) and os.path.getsize(to_file) == 0:os.remove(to_file)if not converted:raise Exception("所有转换方法均失败")except Exception as e:self.log_message.emit(f"转换失败 {filename}: {str(e)}")failed += 1finally:if ppt:try:ppt.Close()ppt = Noneexcept Exception as e:self.log_message.emit(f"关闭PPT时出错: {str(e)}")time.sleep(1)except Exception as e:self.log_message.emit(f"PPT转换出错: {str(e)}")failed += len(ppts) - successfinally:if ppt:try:ppt.Close()except:passif powerpoint:try:# 先关闭所有演示文稿for presentation in powerpoint.Presentations:presentation.Close()# 退出程序powerpoint.Quit()powerpoint = Noneexcept Exception as e:self.log_message.emit(f"关闭PowerPoint时出错: {str(e)}")# 强制释放资源gc.collect()time.sleep(1)self.log_message.emit("关闭PowerPoint进程")return success, failed@staticmethoddef change_suffix_to_pdf(file):"""修改文件后缀为PDF"""return file[:file.rfind('.')] + ".pdf"@staticmethoddef add_worksheet_order(file, i):"""为Excel工作表添加序号"""return file[:file.rfind('.')] + f"_工作表{i}.pdf"@staticmethoddef to_file_join(output_path, file):"""生成PDF文件路径"""return os.path.join(output_path, file)class FileListWidget(QListWidget):"""自定义文件列表部件,支持右键删除"""def __init__(self, parent=None):super().__init__(parent)self.setContextMenuPolicy(Qt.CustomContextMenu)self.customContextMenuRequested.connect(self.show_context_menu)def show_context_menu(self, position):"""显示右键菜单"""menu = QMenu()delete_action = QAction("删除选中项", self)delete_action.triggered.connect(self.delete_selected_items)delete_action.setFont(QFont("Microsoft YaHei", 10))menu.addAction(delete_action)menu.exec_(self.mapToGlobal(position))def delete_selected_items(self):"""删除选中的项"""for item in self.selectedItems():row = self.row(item)self.takeItem(row)class OfficeToPdfConverter(QMainWindow):"""Office转PDF转换器主窗口"""def __init__(self):super().__init__()# 定义颜色常量(保持原有按钮风格)self.BUTTON_BLUE = QColor(33, 150, 243) # 按钮蓝色(#2196F3)self.BUTTON_GREEN = QColor(76, 175, 80) # 按钮绿色(#4CAF50)self.BUTTON_RED = QColor(244, 67, 54) # 按钮红色(#f44336)self.LINE_EDIT_BG = QColor(245, 245, 245) # 文本框背景色(浅灰色)self.worker = Noneself.thread = Noneself.file_items = [] # 存储文件信息的列表self.init_ui()def init_ui(self):"""初始化用户界面"""# 设置窗口基本属性self.setWindowTitle("Office转PDF转换器@阿幸")self.setGeometry(100, 100, 900, 800)self.setMinimumSize(700, 600)# 创建主布局main_layout = QVBoxLayout()main_layout.setContentsMargins(20, 20, 20, 20)main_layout.setSpacing(15)# 创建标题title_label = QLabel("Office文件转PDF转换器")title_font = QFont("SimHei", 16, QFont.Bold)title_label.setFont(title_font)title_label.setAlignment(Qt.AlignCenter)main_layout.addWidget(title_label)# 创建文件选择区域file_selection_group = QGroupBox("添加文件或文件夹")file_selection_layout = QVBoxLayout()# 文件操作按钮(保持原有风格)file_btn_layout = QHBoxLayout()add_file_btn = self.create_button("添加文件", self.add_files, self.BUTTON_BLUE)add_folder_btn = self.create_button("添加目录", self.add_folder, self.BUTTON_BLUE)clear_btn = self.create_button("清空列表", self.clear_file_list, self.BUTTON_RED)file_btn_layout.addWidget(add_file_btn)file_btn_layout.addWidget(add_folder_btn)file_btn_layout.addWidget(clear_btn)# 文件列表self.file_list_widget = FileListWidget()self.file_list_widget.setAlternatingRowColors(True)self.file_list_widget.setToolTip("右键可删除选中项")file_selection_layout.addLayout(file_btn_layout)file_selection_layout.addWidget(self.file_list_widget)file_selection_group.setLayout(file_selection_layout)main_layout.addWidget(file_selection_group)# 输出路径选择区域output_path_group = QGroupBox("选择PDF输出文件夹")output_path_layout = QHBoxLayout()self.output_path_edit = QLineEdit()self.setup_line_edit_bg(self.output_path_edit)self.output_path_edit.setPlaceholderText("请选择PDF文件的保存位置")# 默认输出路径为当前目录下的pdf子文件夹default_output = os.path.join(os.getcwd(), 'pdf')self.output_path_edit.setText(default_output)browse_output_btn = self.create_button("浏览...", self.browse_output_folder, self.BUTTON_BLUE)output_path_layout.addWidget(self.output_path_edit)output_path_layout.addWidget(browse_output_btn)output_path_group.setLayout(output_path_layout)main_layout.addWidget(output_path_group)# 创建文件统计区域stats_group = QGroupBox("文件统计")stats_layout = QFormLayout()self.word_count = QLabel("0")self.excel_count = QLabel("0")self.ppt_count = QLabel("0")self.total_count = QLabel("0")# 设置统计区域字体font = QFont("Microsoft YaHei", 10)self.word_count.setFont(font)self.excel_count.setFont(font)self.ppt_count.setFont(font)self.total_count.setFont(font)stats_layout.addRow("Word文件 (.doc, .docx):", self.word_count)stats_layout.addRow("Excel文件 (.xls, .xlsx):", self.excel_count)stats_layout.addRow("PPT文件 (.ppt, .pptx):", self.ppt_count)stats_layout.addRow("总计可转换文件:", self.total_count)stats_group.setLayout(stats_layout)main_layout.addWidget(stats_group)# 创建进度条区域progress_layout = QVBoxLayout()self.progress_bar = QProgressBar()self.progress_bar.setValue(0)self.progress_status = QLabel("准备就绪")self.progress_status.setFont(QFont("Microsoft YaHei", 10))self.progress_status.setAlignment(Qt.AlignCenter)progress_layout.addWidget(self.progress_bar)progress_layout.addWidget(self.progress_status)main_layout.addLayout(progress_layout)# 创建按钮区域(保持原有风格)btn_layout = QHBoxLayout()self.analyze_btn = self.create_button("分析文件", self.analyze_files, self.BUTTON_BLUE, 100)self.convert_btn = self.create_button("开始转换", self.start_conversion, self.BUTTON_GREEN, 100)self.convert_btn.setEnabled(False)self.cancel_btn = self.create_button("取消", self.cancel_conversion, self.BUTTON_RED, 100)self.cancel_btn.setEnabled(False)btn_layout.addWidget(self.analyze_btn)btn_layout.addWidget(self.convert_btn)btn_layout.addWidget(self.cancel_btn)main_layout.addLayout(btn_layout)# 创建日志区域log_group = QGroupBox("转换日志")log_layout = QVBoxLayout()self.log_text = QTextEdit()self.log_text.setReadOnly(True)self.log_text.setLineWrapMode(QTextEdit.WidgetWidth)self.log_text.setFont(QFont("Microsoft YaHei", 10))self.setup_line_edit_bg(self.log_text)log_layout.addWidget(self.log_text)log_group.setLayout(log_layout)main_layout.addWidget(log_group)# 设置中心部件central_widget = QWidget()central_widget.setLayout(main_layout)self.setCentralWidget(central_widget)# 状态栏信息self.statusBar().showMessage("就绪")def create_button(self, text, callback, color, fixed_width=80):"""创建统一风格的按钮(保持原有风格)"""button = QPushButton(text)button.setFont(QFont("Microsoft YaHei", 10, QFont.Bold))button.setFixedWidth(fixed_width)button.clicked.connect(callback)self.setup_button_style(button, color)return buttondef setup_line_edit_bg(self, widget):"""设置文本框背景颜色(保持原有风格)"""palette = widget.palette()palette.setColor(palette.Base, self.LINE_EDIT_BG)palette.setColor(palette.Text, QColor(0, 0, 0))widget.setPalette(palette)widget.setAutoFillBackground(True)def setup_button_style(self, button, base_color):"""设置按钮样式(保持原有风格)"""button.setStyleSheet(f"""QPushButton {{background-color: rgb({base_color.red()}, {base_color.green()}, {base_color.blue()});color: white;border: none;padding: 5px;border-radius: 4px;}}QPushButton:hover {{background-color: rgb({int(base_color.red()*0.8)}, {int(base_color.green()*0.8)}, {int(base_color.blue()*0.8)});}}QPushButton:pressed {{background-color: rgb({int(base_color.red()*0.7)}, {int(base_color.green()*0.7)}, {int(base_color.blue()*0.7)});}}QPushButton:disabled {{background-color: rgb(160, 160, 160);}}""")button.setFocusPolicy(Qt.NoFocus)def add_files(self):"""添加单个或多个文件"""file_paths, _ = QFileDialog.getOpenFileNames(self, "选择Office文件", os.getcwd(), "Office文件 (*.doc *.docx *.xls *.xlsx *.ppt *.pptx)")if file_paths:added_count = 0for file_path in file_paths:file_dir = os.path.dirname(file_path)file_name = os.path.basename(file_path)# 检查是否已在列表中is_duplicate = Falsefor i in range(self.file_list_widget.count()):if self.file_list_widget.item(i).data(Qt.UserRole) == file_path:is_duplicate = Truebreakif not is_duplicate:item = QListWidgetItem(file_name)item.setData(Qt.UserRole, file_path)self.file_list_widget.addItem(item)self.file_items.append({"path": file_dir, "name": file_name})added_count += 1if added_count > 0:self.log_text.append(f"已添加 {added_count} 个文件")self.analyze_btn.setEnabled(True)def add_folder(self):"""添加目录中的所有Office文件"""folder = QFileDialog.getExistingDirectory(self, "选择文件夹", os.getcwd())if folder:office_extensions = ('.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx')added_count = 0for root, _, files in os.walk(folder):for file in files:if file.lower().endswith(office_extensions):file_path = os.path.join(root, file)# 检查是否已在列表中is_duplicate = Falsefor i in range(self.file_list_widget.count()):if self.file_list_widget.item(i).data(Qt.UserRole) == file_path:is_duplicate = Truebreakif not is_duplicate:item = QListWidgetItem(file)item.setData(Qt.UserRole, file_path)self.file_list_widget.addItem(item)self.file_items.append({"path": root, "name": file})added_count += 1if added_count > 0:self.log_text.append(f"从目录中添加了 {added_count} 个文件")self.analyze_btn.setEnabled(True)else:QMessageBox.information(self, "提示", "所选目录中没有找到可转换的Office文件")def clear_file_list(self):"""清空文件列表"""if self.file_list_widget.count() > 0:self.file_list_widget.clear()self.file_items = []self.log_text.append("已清空文件列表")self.reset_stats()self.analyze_btn.setEnabled(False)self.convert_btn.setEnabled(False)else:QMessageBox.information(self, "提示", "文件列表已为空")def reset_stats(self):"""重置文件统计"""self.word_count.setText("0")self.excel_count.setText("0")self.ppt_count.setText("0")self.total_count.setText("0")def analyze_files(self):"""分析文件类型并统计数量"""word_count = 0excel_count = 0ppt_count = 0for item in self.file_items:filename = item["name"].lower()if filename.endswith(('.doc', '.docx')):word_count += 1elif filename.endswith(('.xls', '.xlsx')):excel_count += 1elif filename.endswith(('.ppt', '.pptx')):ppt_count += 1total = word_count + excel_count + ppt_countself.word_count.setText(str(word_count))self.excel_count.setText(str(excel_count))self.ppt_count.setText(str(ppt_count))self.total_count.setText(str(total))self.log_text.append(f"文件分析完成: Word {word_count}个, Excel {excel_count}个, PPT {ppt_count}个, 总计 {total}个")if total > 0:self.convert_btn.setEnabled(True)else:self.convert_btn.setEnabled(False)QMessageBox.warning(self, "警告", "没有可转换的文件")def browse_output_folder(self):"""选择输出文件夹"""folder = QFileDialog.getExistingDirectory(self, "选择PDF输出文件夹", os.getcwd())if folder:self.output_path_edit.setText(folder)def start_conversion(self):"""开始转换任务"""output_path = self.output_path_edit.text().strip()if not output_path:QMessageBox.warning(self, "警告", "请选择PDF输出文件夹")returnif not self.file_items:QMessageBox.warning(self, "警告", "请先添加文件")return# 禁用相关按钮self.analyze_btn.setEnabled(False)self.convert_btn.setEnabled(False)self.cancel_btn.setEnabled(True)# 初始化进度条self.progress_bar.setValue(0)self.progress_status.setText("准备开始转换...")# 创建线程和工作对象self.thread = QThread()self.worker = ConversionWorker(self.file_items, output_path)self.worker.moveToThread(self.thread)# 连接信号槽self.thread.started.connect(self.worker.run)self.worker.progress_updated.connect(self.update_progress)self.worker.log_message.connect(self.log_text.append)self.worker.conversion_finished.connect(self.conversion_complete)self.worker.conversion_finished.connect(self.thread.quit)self.thread.finished.connect(self.thread.deleteLater)self.thread.finished.connect(lambda: self.worker.deleteLater())# 开始转换self.thread.start()self.log_text.append("===== 开始转换 =====")def update_progress(self, value, status):"""更新进度条和状态"""self.progress_bar.setValue(value)self.progress_status.setText(status)def conversion_complete(self, message):"""转换完成处理"""self.log_text.append("===== 转换结束 =====")QMessageBox.information(self, "转换结果", message)# 恢复按钮状态self.analyze_btn.setEnabled(True)self.convert_btn.setEnabled(True)self.cancel_btn.setEnabled(False)self.progress_status.setText("转换完成")def cancel_conversion(self):"""取消转换任务"""if self.worker and self.thread and self.thread.isRunning():if QMessageBox.question(self, "确认取消", "确定要取消转换吗?", QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:self.worker.stop()self.progress_status.setText("正在取消...")self.cancel_btn.setEnabled(False)if __name__ == "__main__":app = QApplication(sys.argv)app.setStyle("Fusion")window = OfficeToPdfConverter()window.show()sys.exit(app.exec_())
软件下载
夸克
迅雷