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_())
|