好友
阅读权限10
听众
最后登录1970-1-1
|
昔年科技
发表于 2025-5-30 13:48
本帖最后由 昔年科技 于 2025-6-2 17:24 编辑
起源今天需要用一下某办公软件的PDF转图片功能,居然有水印,无水印还要开XX。哎,打工人,真不容易,于是便想用Python写一个小工具。这个PDF批量转PNG工具是一款高效、易用的桌面应用程序,专为需要将PDF文档转换为高质量PNG图像的用户设计。基于Python开发,采用PyQt5构建直观的图形界面,支持批量处理多个PDF文件,满足办公、设计、出版等领域的文档转换需求。
核心功能1. 批量转换能力- 多文件处理:可一次性选择并转换整个文件夹内的所有PDF文件
- 自动命名:保留原文件名,自动添加页码后缀(如"文档1_page1.png")
2. 灵活输出选项- 分页模式:将PDF每页转换为单独的PNG文件
- 合并模式:将PDF所有页面垂直拼接为一张长图输出
- DPI调节:支持100-1200DPI设置(推荐300-600DPI保证清晰度)
3. 专业级转换质量- 基于PyMuPDF(fitz)引擎,保持原始文档的:
技术特点高效架构- 多线程处理:转换过程不阻塞界面操作
- 进度可视化:实时显示文件处理进度和当前页码
- 错误处理:自动捕获并提示转换异常
使用场景- 文档归档:将合同/报告转换为可搜索的图像存档
- 网页设计:提取PDF中的设计素材为透明背景PNG
- 电子出版:准备适用于电子书的图像内容
- 学术研究:转换论文中的图表为可编辑格式
使用方法- 选择PDF文件或文件夹
- 设置输出目录
- 选择转换模式(分页/合并)
- 调整DPI质量参数
- 点击"开始转换"按钮
性能参数
[td]项目 | 规格 | | 单页转换速度 | 约0.5-2秒/页(取决于DPI设置) | | 输出质量 | 24位真彩色PNG,支持透明背景 | | 最大文件支持 | 理论无限制(实测处理过500+页文档) | | 输出尺寸精度 | 精确到1/72英寸(PDF标准单位) | 大概就这样了 ,
方便大家使用 已打包成品 蓝奏:https://wwov.lanzoum.com/ihOe12xjripa 密码:52pjie
附上源码,欢迎大神指导
[Python] 纯文本查看 复制代码 import os
import sys
import fitz # PyMuPDF
from PIL import Image
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QLabel, QLineEdit, QPushButton,
QFileDialog, QRadioButton, QButtonGroup,
QSpinBox, QProgressBar, QMessageBox, QGroupBox)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
class PdfConverterThread(QThread):
"""PDF转换线程(支持批量处理)"""
progress_updated = pyqtSignal(int, str)
conversion_finished = pyqtSignal(bool, str)
file_progress_updated = pyqtSignal(int, int) # 当前文件进度,总文件数
def __init__(self, input_path, output_folder, mode, dpi, is_folder=False):
super().__init__()
self.input_path = input_path
self.output_folder = output_folder
self.mode = mode
self.dpi = dpi
self.is_folder = is_folder
self._is_running = True
def run(self):
try:
if self.is_folder:
# 处理文件夹中的所有PDF文件
pdf_files = [f for f in os.listdir(self.input_path)
if f.lower().endswith('.pdf')]
total_files = len(pdf_files)
for file_idx, pdf_file in enumerate(pdf_files):
if not self._is_running:
break
pdf_path = os.path.join(self.input_path, pdf_file)
# 为每个PDF创建单独的输出子文件夹
output_subfolder = os.path.join(
self.output_folder,
os.path.splitext(pdf_file)[0]
)
os.makedirs(output_subfolder, exist_ok=True)
self.file_progress_updated.emit(file_idx + 1, total_files)
self.process_pdf(pdf_path, output_subfolder)
if self._is_running:
self.conversion_finished.emit(
True,
f"批量转换完成!共处理 {total_files} 个PDF文件"
)
else:
# 处理单个PDF文件
os.makedirs(self.output_folder, exist_ok=True)
self.process_pdf(self.input_path, self.output_folder)
if self._is_running:
self.conversion_finished.emit(
True,
f"转换完成!文件保存在: {self.output_folder}"
)
except Exception as e:
self.conversion_finished.emit(False, f"转换失败: {str(e)}")
def process_pdf(self, pdf_path, output_folder):
"""处理单个PDF文件"""
pdf_document = fitz.open(pdf_path)
total_pages = len(pdf_document)
base_name = os.path.splitext(os.path.basename(pdf_path))[0]
# 计算缩放因子,基于DPI
zoom = self.dpi / 72
mat = fitz.Matrix(zoom, zoom)
if self.mode == 'separate':
# 分页输出模式
for page_num in range(total_pages):
if not self._is_running:
break
page = pdf_document.load_page(page_num)
pix = page.get_pixmap(matrix=mat)
output_path = os.path.join(
output_folder,
f"{base_name}_page_{page_num + 1}.png"
)
pix.save(output_path)
progress = int((page_num + 1) / total_pages * 100)
self.progress_updated.emit(
progress,
f"正在转换 {base_name} 第 {page_num + 1}/{total_pages} 页..."
)
elif self.mode == 'merge':
# 合并输出模式
images = []
for page_num in range(total_pages):
if not self._is_running:
break
page = pdf_document.load_page(page_num)
pix = page.get_pixmap(matrix=mat)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
images.append(img)
progress = int((page_num + 1) / total_pages * 100)
self.progress_updated.emit(
progress,
f"正在处理 {base_name} 第 {page_num + 1}/{total_pages} 页..."
)
if self._is_running and images:
# 计算总高度和最大宽度
total_height = sum(img.height for img in images)
max_width = max(img.width for img in images)
# 创建新图像
merged_image = Image.new('RGB', (max_width, total_height))
# 拼接图像
y_offset = 0
for img in images:
merged_image.paste(img, (0, y_offset))
y_offset += img.height
output_path = os.path.join(output_folder, f"{base_name}_merged.png")
merged_image.save(output_path)
pdf_document.close()
def stop(self):
self._is_running = False
class PdfToPngConverter(QMainWindow):
"""PDF转PNG转换器主窗口(支持文件夹批量处理)"""
def __init__(self):
super().__init__()
self.setWindowTitle("PDF转PNG转换器 (批量版)")
self.setGeometry(100, 100, 600, 450)
# 初始化UI
self.init_ui()
# 转换线程
self.converter_thread = None
def init_ui(self):
"""初始化用户界面"""
main_widget = QWidget()
self.setCentralWidget(main_widget)
layout = QVBoxLayout()
main_widget.setLayout(layout)
# 输入选择
input_group = QGroupBox("输入选择")
input_layout = QVBoxLayout()
# 选择模式
mode_btn_group = QButtonGroup(self)
single_file_btn = QRadioButton("单个PDF文件")
single_file_btn.setChecked(True)
mode_btn_group.addButton(single_file_btn)
folder_btn = QRadioButton("PDF文件夹(批量处理)")
mode_btn_group.addButton(folder_btn)
input_layout.addWidget(single_file_btn)
input_layout.addWidget(folder_btn)
# 文件/文件夹选择
file_layout = QHBoxLayout()
file_layout.addWidget(QLabel("路径:"))
self.path_edit = QLineEdit()
self.path_edit.setPlaceholderText("请选择PDF文件或文件夹")
file_layout.addWidget(self.path_edit)
browse_btn = QPushButton("浏览...")
browse_btn.clicked.connect(self.browse_path)
file_layout.addWidget(browse_btn)
input_layout.addLayout(file_layout)
input_group.setLayout(input_layout)
layout.addWidget(input_group)
# 输出设置
output_group = QGroupBox("输出设置")
output_layout = QVBoxLayout()
# 输出文件夹
out_folder_layout = QHBoxLayout()
out_folder_layout.addWidget(QLabel("输出文件夹:"))
self.output_edit = QLineEdit()
self.output_edit.setPlaceholderText("请选择输出文件夹")
out_folder_layout.addWidget(self.output_edit)
out_browse_btn = QPushButton("浏览...")
out_browse_btn.clicked.connect(self.browse_output)
out_folder_layout.addWidget(out_browse_btn)
output_layout.addLayout(out_folder_layout)
# 输出模式
out_mode_layout = QHBoxLayout()
out_mode_layout.addWidget(QLabel("输出模式:"))
self.separate_radio = QRadioButton("分页输出")
self.separate_radio.setChecked(True)
out_mode_layout.addWidget(self.separate_radio)
self.merge_radio = QRadioButton("合并输出")
out_mode_layout.addWidget(self.merge_radio)
output_layout.addLayout(out_mode_layout)
# DPI设置
dpi_layout = QHBoxLayout()
dpi_layout.addWidget(QLabel("DPI 数值越高,图片越清晰[推荐 300-600]:"))
self.dpi_spin = QSpinBox()
self.dpi_spin.setRange(100, 1200)
self.dpi_spin.setValue(300)
dpi_layout.addWidget(self.dpi_spin)
output_layout.addLayout(dpi_layout)
output_group.setLayout(output_layout)
layout.addWidget(output_group)
# 进度显示
self.file_progress = QLabel("")
self.file_progress.setAlignment(Qt.AlignCenter)
layout.addWidget(self.file_progress)
self.progress_bar = QProgressBar()
layout.addWidget(self.progress_bar)
self.status_label = QLabel("准备就绪")
self.status_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.status_label)
# 操作按钮
btn_layout = QHBoxLayout()
self.convert_btn = QPushButton("开始转换")
self.convert_btn.clicked.connect(self.start_conversion)
btn_layout.addWidget(self.convert_btn)
self.cancel_btn = QPushButton("取消")
self.cancel_btn.clicked.connect(self.cancel_conversion)
self.cancel_btn.setEnabled(False)
btn_layout.addWidget(self.cancel_btn)
layout.addLayout(btn_layout)
def browse_path(self):
"""浏览文件或文件夹"""
if self.sender().parent().findChildren(QRadioButton)[1].isChecked():
# 选择文件夹
folder = QFileDialog.getExistingDirectory(self, "选择PDF文件夹")
if folder:
self.path_edit.setText(folder)
# 自动设置输出文件夹
self.output_edit.setText(os.path.join(folder, "PNG_Output"))
else:
# 选择文件
file, _ = QFileDialog.getOpenFileName(
self, "选择PDF文件", "", "PDF文件 (*.pdf)"
)
if file:
self.path_edit.setText(file)
# 自动设置输出文件夹
folder = os.path.dirname(file)
name = os.path.splitext(os.path.basename(file))[0]
self.output_edit.setText(os.path.join(folder, f"{name}_PNG"))
def browse_output(self):
"""浏览输出文件夹"""
folder = QFileDialog.getExistingDirectory(self, "选择输出文件夹")
if folder:
self.output_edit.setText(folder)
def start_conversion(self):
"""开始转换"""
input_path = self.path_edit.text().strip()
output_folder = self.output_edit.text().strip()
is_folder = self.sender().parent().findChildren(QRadioButton)[1].isChecked()
# 验证输入
if not input_path:
QMessageBox.warning(self, "警告", "请选择PDF文件/文件夹")
return
if is_folder:
if not os.path.isdir(input_path):
QMessageBox.warning(self, "警告", "文件夹不存在")
return
else:
if not os.path.isfile(input_path):
QMessageBox.warning(self, "警告", "PDF文件不存在")
return
if not output_folder:
QMessageBox.warning(self, "警告", "请选择输出文件夹")
return
# 获取转换选项
mode = 'separate' if self.separate_radio.isChecked() else 'merge'
dpi = self.dpi_spin.value()
# 更新UI状态
self.convert_btn.setEnabled(False)
self.cancel_btn.setEnabled(True)
self.progress_bar.setValue(0)
self.status_label.setText("准备开始转换...")
self.file_progress.setText("")
# 创建并启动转换线程
self.converter_thread = PdfConverterThread(
input_path, output_folder, mode, dpi, is_folder
)
self.converter_thread.progress_updated.connect(self.update_progress)
self.converter_thread.conversion_finished.connect(self.conversion_finished)
self.converter_thread.file_progress_updated.connect(self.update_file_progress)
self.converter_thread.start()
def update_progress(self, progress, message):
"""更新进度"""
self.progress_bar.setValue(progress)
self.status_label.setText(message)
def update_file_progress(self, current, total):
"""更新文件处理进度"""
self.file_progress.setText(f"正在处理文件: {current}/{total}")
def conversion_finished(self, success, message):
"""转换完成"""
if success:
QMessageBox.information(self, "输出完成", message)
else:
QMessageBox.critical(self, "输出错误", message)
self.reset_ui()
def cancel_conversion(self):
"""取消转换"""
if self.converter_thread and self.converter_thread.isRunning():
self.converter_thread.stop()
self.converter_thread.wait()
self.status_label.setText("转换已取消")
self.reset_ui()
def reset_ui(self):
"""重置UI状态"""
self.convert_btn.setEnabled(True)
self.cancel_btn.setEnabled(False)
self.progress_bar.setValue(0)
self.file_progress.setText("")
def closeEvent(self, event):
"""窗口关闭事件"""
if self.converter_thread and self.converter_thread.isRunning():
self.converter_thread.stop()
self.converter_thread.wait()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
converter = PdfToPngConverter()
converter.show()
sys.exit(app.exec_())
这款工具特别适合需要定期处理大量PDF转图像任务的用户,相比在线转换工具,它提供了更好的隐私保护(所有处理在本地完成)和更稳定的批量处理能力。 |
免费评分
-
| 参与人数 1 | 吾爱币 +7 |
热心值 +1 |
收起
理由
|
苏紫方璇
| + 7 |
+ 1 |
欢迎分析讨论交流,吾爱破解论坛有你更精彩! |
查看全部评分
|