用ds生成小工具image_converter


目录
  1. 1. 程序主代码
  2. 2. 安装依赖并运行
  3. 3. 打包成单一的exe文件

deepseek能做很多事,本文借助ds开发一个windows小工具,图片转换器image_converter。

程序主代码

提问:用python完成把一个目录下的图片转换格式到另一个目录,并提供图形界面,打包成单个文件的windows程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
import os
import sys
import json
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QFileDialog, QComboBox,
QProgressBar, QMessageBox
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QStandardPaths
from PIL import Image

# 图片转换线程
class ImageConverter(QThread):
progress_updated = pyqtSignal(int)
conversion_complete = pyqtSignal(str)
error_occurred = pyqtSignal(str)

def __init__(self, src_dir, dest_dir, output_format, parent=None):
super().__init__(parent)
self.src_dir = src_dir
self.dest_dir = dest_dir
self.output_format = output_format.lower()
self.running = True

def run(self):
try:
if not os.path.exists(self.src_dir):
self.error_occurred.emit(f"源目录不存在: {self.src_dir}")
return

os.makedirs(self.dest_dir, exist_ok=True)

supported_formats = ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'tiff', 'webp']
if self.output_format not in supported_formats:
self.error_occurred.emit(f"不支持的格式: {self.output_format}")
return

image_files = [f for f in os.listdir(self.src_dir)
if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.tiff', '.webp'))]

total_files = len(image_files)
if total_files == 0:
self.error_occurred.emit("未找到支持的图片文件")
return

for i, filename in enumerate(image_files):
if not self.running:
break

try:
img_path = os.path.join(self.src_dir, filename)
img = Image.open(img_path)

# 透明背景转JPG处理
if img.mode in ('RGBA', 'LA') and self.output_format in ('jpg', 'jpeg'):
background = Image.new('RGB', img.size, (255, 255, 255))
background.paste(img, mask=img.split()[-1])
img = background

output_filename = f"{os.path.splitext(filename)[0]}.{self.output_format}"
output_path = os.path.join(self.dest_dir, output_filename)
img.save(output_path, quality=95)

self.progress_updated.emit(int((i + 1) / total_files * 100))

except Exception as e:
self.error_occurred.emit(f"处理 {filename} 失败: {str(e)}")
continue

if self.running:
self.conversion_complete.emit(f"转换完成! 共处理 {total_files} 个文件")

except Exception as e:
self.error_occurred.emit(f"发生错误: {str(e)}")

def stop(self):
self.running = False

# 主窗口
class ImageConverterApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("图片格式转换工具")
self.setGeometry(100, 100, 500, 300)
# 设置窗口图标(关键代码)
self.setWindowIcon(self.load_icon("icon.ico"))
self.settings_file = self.get_settings_path()
self.init_ui()
self.load_settings()
self.converter_thread = None
def load_icon(self, icon_name):
"""动态加载图标(兼容开发模式和打包模式)"""
if getattr(sys, 'frozen', False): # 打包后
base_path = sys._MEIPASS
else: # 开发模式
base_path = os.path.dirname(__file__)
icon_path = os.path.join(base_path, icon_name)
return QIcon(icon_path)
def get_settings_path(self):
"""获取跨平台的配置文件路径"""
config_dir = QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation)
os.makedirs(config_dir, exist_ok=True)
return os.path.join(config_dir, "image_converter_settings.json")

def init_ui(self):
main_widget = QWidget()
layout = QVBoxLayout()

# 源目录
src_layout = QHBoxLayout()
src_layout.addWidget(QLabel("源目录:"))
self.src_line_edit = QLineEdit()
src_layout.addWidget(self.src_line_edit)
self.src_browse_btn = QPushButton("浏览...")
self.src_browse_btn.clicked.connect(self.browse_src_dir)
src_layout.addWidget(self.src_browse_btn)
layout.addLayout(src_layout)

# 目标目录
dest_layout = QHBoxLayout()
dest_layout.addWidget(QLabel("目标目录:"))
self.dest_line_edit = QLineEdit()
dest_layout.addWidget(self.dest_line_edit)
self.dest_browse_btn = QPushButton("浏览...")
self.dest_browse_btn.clicked.connect(self.browse_dest_dir)
dest_layout.addWidget(self.dest_browse_btn)
layout.addLayout(dest_layout)

# 输出格式
format_layout = QHBoxLayout()
format_layout.addWidget(QLabel("输出格式:"))
self.format_combo = QComboBox()
self.format_combo.addItems(['JPG', 'PNG', 'BMP', 'GIF', 'TIFF', 'WEBP'])
format_layout.addWidget(self.format_combo)
layout.addLayout(format_layout)

# 进度条
self.progress_bar = QProgressBar()
layout.addWidget(self.progress_bar)

# 按钮
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)

# 状态栏
self.status_label = QLabel("准备就绪")
self.status_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.status_label)

main_widget.setLayout(layout)
self.setCentralWidget(main_widget)

def browse_src_dir(self):
dir_path = QFileDialog.getExistingDirectory(self, "选择源目录")
if dir_path:
self.src_line_edit.setText(dir_path)
# 自动设置目标目录
if not self.dest_line_edit.text():
self.dest_line_edit.setText(os.path.join(dir_path, "converted"))

def browse_dest_dir(self):
dir_path = QFileDialog.getExistingDirectory(self, "选择目标目录")
if dir_path:
self.dest_line_edit.setText(dir_path)

def load_settings(self):
"""加载上次的设置"""
try:
if os.path.exists(self.settings_file):
with open(self.settings_file, 'r') as f:
settings = json.load(f)
self.src_line_edit.setText(settings.get('src_dir', ''))
self.dest_line_edit.setText(settings.get('dest_dir', ''))
self.format_combo.setCurrentText(settings.get('format', 'JPG'))
except Exception as e:
print(f"加载设置失败: {e}")

def save_settings(self):
"""保存当前设置"""
try:
settings = {
'src_dir': self.src_line_edit.text(),
'dest_dir': self.dest_line_edit.text(),
'format': self.format_combo.currentText()
}
with open(self.settings_file, 'w') as f:
json.dump(settings, f)
except Exception as e:
print(f"保存设置失败: {e}")

def start_conversion(self):
src_dir = self.src_line_edit.text()
dest_dir = self.dest_line_edit.text()
output_format = self.format_combo.currentText().lower()

if not src_dir or not dest_dir:
QMessageBox.warning(self, "警告", "请选择源目录和目标目录!")
return

self.progress_bar.setValue(0)
self.status_label.setText("正在转换...")
self.convert_btn.setEnabled(False)
self.cancel_btn.setEnabled(True)

self.converter_thread = ImageConverter(src_dir, dest_dir, output_format)
self.converter_thread.progress_updated.connect(self.update_progress)
self.converter_thread.conversion_complete.connect(self.conversion_finished)
self.converter_thread.error_occurred.connect(self.show_error)
self.converter_thread.start()

def update_progress(self, value):
self.progress_bar.setValue(value)

def conversion_finished(self, message):
self.status_label.setText(message)
self.convert_btn.setEnabled(True)
self.cancel_btn.setEnabled(False)
self.save_settings() # 转换完成后保存设置
QMessageBox.information(self, "完成", message)

def show_error(self, error_message):
self.status_label.setText(f"错误: {error_message}")
self.convert_btn.setEnabled(True)
self.cancel_btn.setEnabled(False)
QMessageBox.critical(self, "错误", error_message)

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("转换已取消")

def closeEvent(self, event):
self.save_settings()
if self.converter_thread and self.converter_thread.isRunning():
reply = QMessageBox.question(
self, '确认退出',
'转换正在进行中,确定退出吗?',
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.Yes:
self.converter_thread.stop()
self.converter_thread.wait()
event.accept()
else:
event.ignore()
else:
event.accept()


if __name__ == "__main__":
app = QApplication(sys.argv)
window = ImageConverterApp()
window.show()
sys.exit(app.exec_())

保存为image_converter.py。

安装依赖并运行

pip install pyinstaller pillow pyqt5
此时已经可以运行py文件了。
python image_converter.py
同一目录下放一个icon.ico的图标文件,可在网络上随便找一个。
程序主界面如下:

打包成单一的exe文件

  • upx
    由于使用了upx压缩以减小可执行文件大小,需下载upx放到py文件同一目录下。
    这里使用的是windows x64版本,
    https://github.com/upx/upx/releases/download/v5.0.0/upx-5.0.0-win64.zip,解压出upx.exe。
  • 打包
    1
    pyinstaller --onefile --windowed --icon="%cd%\icon.ico" --add-data "icon.ico;." image_converter.py
    得到dist/image_converter.exe,31, 332 kb。

预览:image_converter.exe


ds能解决很多问题,不只是生成小app。