import sysimport osimport jsonfrom PyQt6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog, QTextEdit, QProgressBar, QGroupBox, QMessageBox, QLineEdit, QGridLayout)from PyQt6.QtCore import Qt, QThread, pyqtSignalfrom PyQt6.QtGui import QFont# --- 配置文件路径 ---CONFIG_FILE = "watermark_remover_config.json"# --- 配置管理函数 ---def load_config(): """加载配置文件""" default_config = { "api_key": "", #API Key "output_dir": "watermark_removed", "output_n": 1, # 默认输出1张图片 "last_folder": "" #上次选择的文件夹路径 } if os.path.exists(CONFIG_FILE): try: with open(CONFIG_FILE, 'r', encoding='utf-8') as f: config = json.load(f) # 确保所有必要的键都存在 for key in default_config: if key not in config: config[key] = default_config[key] return config except Exception as e: print(f"读取配置文件失败: {str(e)},使用默认配置") return default_config else: return default_configdef save_config(config): """保存配置文件""" try: with open(CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=2) return True except Exception as e: print(f"保存配置文件失败: {str(e)}") return False# --- 工作线程类 ---class WatermarkRemovalWorker(QThread): """水印去除工作线程""" progress_signal = pyqtSignal(str) progress_value_signal = pyqtSignal(int) # 新增:发送进度值信号 result_signal = pyqtSignal(dict) error_signal = pyqtSignal(str) def __init__(self, image_paths, api_key): super().__init__() self.image_paths = image_paths self.api_key = api_key self.output_n = 1 # 固定输出1张图片 self.running = True def run(self): """线程执行函数""" # 这是一个示例实现,实际的水印去除逻辑需要根据具体情况实现 total_images = len(self.image_paths) for idx, image_path in enumerate(self.image_paths): if not self.running: break try: # 更新进度值(基于已处理的图片数量) progress_value = int((idx / total_images) * 100) self.progress_value_signal.emit(progress_value) self.progress_signal.emit(f"正在处理第 {idx + 1}/{len(self.image_paths)} 张图片: {os.path.basename(image_path)}") self.progress_signal.emit("正在调用AI模型处理图片...") # 模拟API调用 self.msleep(500) # 加载配置获取输出目录 config = load_config() output_dir = config.get("output_dir", "watermark_removed") os.makedirs(output_dir, exist_ok=True) # 生成输出文件名 original_filename = os.path.basename(image_path) name, ext = os.path.splitext(original_filename) filename = f"{output_dir}/{name}_cleaned{ext}" import shutil shutil.copy2(image_path, filename) result = { 'success': True, 'original_file': image_path, 'processed_files': [filename], 'original_size': "1000x1000", # 示例值 'file_size': "1.23MB", # 示例值 'new_size': "1000x1000" # 示例值 } self.result_signal.emit(result) except Exception as e: error_msg = f"处理图片 {os.path.basename(image_path)} 时出错: {str(e)}" self.error_signal.emit(error_msg) continue # 最后完成时设置为100% self.progress_value_signal.emit(100) self.progress_signal.emit("处理完成!") def stop(self): """停止线程""" self.running = False# --- 主界面类 ---class WatermarkRemovalApp(QMainWindow): """水印去除应用主窗口""" def __init__(self): super().__init__() self.image_files = [] self.worker = None self.current_image_index = 0 # 新增:跟踪当前处理的图片索引 # 加载配置 self.config = load_config() self.api_key = self.config.get("api_key", "") self.init_ui() def init_ui(self): """初始化UI""" self.setWindowTitle("智能水印去除工具") self.setGeometry(100, 100, 800, 650) # 设置样式 self.setStyleSheet(""" QMainWindow { background-color: #f0f2f5; } QGroupBox { font-size: 14px; font-weight: bold; border: 2px solid #d1d9e6; border-radius: 8px; margin-top: 10px; padding-top: 10px; background-color: white; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; color: #2c3e50; } QPushButton { font-size: 14px; padding: 10px 20px; border-radius: 6px; border: none; font-weight: bold; } QPushButton:hover { opacity: 0.9; } QPushButton:disabled { background-color: #bdc3c7; }#startButton { background-color: #2ecc71; color: white; font-size: 16px; padding: 15px 30px; }#folderButton { background-color: #3498db; color: white; }#saveKeyButton { background-color: #9b59b6; color: white; padding: 10px 15px; min-width: 120px; } QLabel { font-size: 14px; } QLineEdit { font-size: 14px; padding: 10px; border: 1px solid #d1d9e6; border-radius: 6px; background-color: white; } QTextEdit { border: 1px solid #d1d9e6; border-radius: 6px; padding: 8px; font-size: 13px; background-color: white; } QProgressBar { border: 1px solid #d1d9e6; border-radius: 6px; text-align: center; background-color: white; font-weight: bold; } QProgressBar::chunk { background-color: #3498db; border-radius: 6px; } """) # 创建中心部件 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) main_layout.setSpacing(15) main_layout.setContentsMargins(15, 15, 15, 15) # API设置组 api_group = QGroupBox("API设置") api_layout = QVBoxLayout() # API Key输入框和保存按钮(水平布局) api_key_layout = QHBoxLayout() api_key_label = QLabel("API Key:") self.api_key_edit = QLineEdit() self.api_key_edit.setText(self.api_key) self.api_key_edit.textChanged.connect(self.on_api_key_changed) self.api_key_edit.setPlaceholderText("请输入您的API Key") # 保存API Key按钮 save_key_button = QPushButton("💾 保存API Key") save_key_button.setObjectName("saveKeyButton") save_key_button.clicked.connect(self.save_api_key) api_key_layout.addWidget(api_key_label) api_key_layout.addWidget(self.api_key_edit) api_key_layout.addWidget(save_key_button) # 设置比例,让输入框占据更多空间 api_key_layout.setStretch(0, 1) # 标签 api_key_layout.setStretch(1, 4) # 输入框 api_key_layout.setStretch(2, 1) # 按钮 api_layout.addLayout(api_key_layout) api_group.setLayout(api_layout) main_layout.addWidget(api_group) # 图片管理组 image_group = QGroupBox("图片管理") image_layout = QVBoxLayout() # 文件夹选择部分(使用网格布局) folder_layout = QGridLayout() folder_layout.setSpacing(10) # 文件夹路径输入框 folder_label = QLabel("文件夹路径:") self.folder_edit = QLineEdit() self.folder_edit.setPlaceholderText("请选择或输入图片文件夹路径") self.folder_edit.setText(self.config.get("last_folder", "")) # 文件夹选择按钮 self.folder_button = QPushButton("📂 浏览文件夹") self.folder_button.setObjectName("folderButton") self.folder_button.clicked.connect(self.add_folder) # 将控件添加到网格布局 folder_layout.addWidget(folder_label, 0, 0) folder_layout.addWidget(self.folder_edit, 0, 1) folder_layout.addWidget(self.folder_button, 0, 2) # 设置列宽比例 folder_layout.setColumnStretch(0, 1) # 标签列 folder_layout.setColumnStretch(1, 3) # 输入框列 folder_layout.setColumnStretch(2, 1) # 按钮列 # 添加到主布局 image_layout.addLayout(folder_layout) image_group.setLayout(image_layout) main_layout.addWidget(image_group) # 处理控制组 control_group = QGroupBox("处理控制") control_layout = QVBoxLayout() # 显示当前设置信息 info_label = QLabel("💡 当前设置:输出1张无水印图片,自动优化尺寸") info_label.setStyleSheet("color: #3498db; padding: 5px; font-size: 13px;") control_layout.addWidget(info_label) # 进度条 self.progress_bar = QProgressBar() self.progress_bar.setTextVisible(True) self.progress_bar.setFormat("%p% (%v/%m)") # 设置显示格式:百分比 (当前/总数) control_layout.addWidget(self.progress_bar) self.start_button = QPushButton("🚀 开始去除水印") self.start_button.setObjectName("startButton") self.start_button.clicked.connect(self.start_processing) control_layout.addWidget(self.start_button) control_group.setLayout(control_layout) main_layout.addWidget(control_group) # 日志显示 log_group = QGroupBox("处理日志") log_layout = QVBoxLayout() self.log_text = QTextEdit() self.log_text.setReadOnly(True) self.log_text.setMaximumHeight(200) log_layout.addWidget(self.log_text) log_group.setLayout(log_layout) main_layout.addWidget(log_group) main_layout.addStretch() def add_folder(self): """添加文件夹中的所有图片""" # 从输入框获取当前路径,或使用上次选择的文件夹 current_path = self.folder_edit.text().strip() if not os.path.exists(current_path): current_path = self.config.get("last_folder", "") folder = QFileDialog.getExistingDirectory( self, "选择图片文件夹", current_path ) if folder: # 更新输入框 self.folder_edit.setText(folder) # 保存到配置 self.config["last_folder"] = folder save_config(self.config) # 清空之前的图片列表 self.image_files.clear() # 查找支持的图片文件 image_extensions = ['.png', '.jpg', '.jpeg', '.bmp', '.webp', '.gif'] image_files = [] from pathlib import Path for ext in image_extensions: image_files.extend(list(Path(folder).glob(f'**/*{ext}'))) image_files.extend(list(Path(folder).glob(f'**/*{ext.upper()}'))) if image_files: # 过滤掉可能存在的重复文件 unique_files = list(set(image_files)) unique_files.sort() # 按文件名排序 self.image_files.extend([str(f) for f in unique_files]) self.log_message(f"从文件夹添加了 {len(unique_files)} 张图片") # 显示文件夹信息 folder_info = f"文件夹: {folder}" self.log_message(folder_info) else: QMessageBox.warning(self, "警告", "选择的文件夹中没有找到支持的图片文件") def clear_image_list(self): """清空图片列表""" self.image_files.clear() self.folder_edit.clear() self.log_message("已清空图片列表") def on_api_key_changed(self): """API Key变化时更新""" self.api_key = self.api_key_edit.text().strip() def save_api_key(self): """保存API Key到配置文件""" self.api_key = self.api_key_edit.text().strip() if not self.api_key: QMessageBox.warning(self, "警告", "API Key不能为空!") return # 更新配置 self.config["api_key"] = self.api_key # 保存配置 if save_config(self.config): QMessageBox.information(self, "成功", "API Key已保存!") self.log_message("API Key已保存到配置文件") else: QMessageBox.warning(self, "警告", "保存API Key失败") def log_message(self, message): """添加日志消息""" import time timestamp = time.strftime("%H:%M:%S", time.localtime()) self.log_text.append(f"[{timestamp}] {message}") self.log_text.verticalScrollBar().setValue( self.log_text.verticalScrollBar().maximum() ) def start_processing(self): """开始处理图片""" if not self.image_files: QMessageBox.warning(self, "警告", "请先选择要处理的图片文件夹!") return if not self.api_key: QMessageBox.warning(self, "警告", "请输入API Key!") return # 重置进度条 self.progress_bar.setMaximum(100) # 改为百分比进度 self.progress_bar.setValue(0) self.current_image_index = 0 # 创建并启动工作线程 self.worker = WatermarkRemovalWorker( self.image_files, self.api_key ) # 连接信号 self.worker.progress_signal.connect(self.update_progress_message) self.worker.progress_value_signal.connect(self.update_progress_value) # 新增 self.worker.result_signal.connect(self.on_processing_complete) self.worker.error_signal.connect(self.on_processing_error) self.worker.finished.connect(self.on_worker_finished) # 更新UI状态 self.start_button.setEnabled(False) # 开始处理 self.log_message(f"开始处理 {len(self.image_files)} 张图片...") self.worker.start() def update_progress_message(self, message): """更新进度消息(不改变进度值)""" self.log_message(message) def update_progress_value(self, value): """更新进度条值""" self.progress_bar.setValue(value) def on_processing_complete(self, result): """处理完成回调""" success = result['success'] original_file = result['original_file'] processed_files = result['processed_files'] # 增加当前图片索引 self.current_image_index += 1 if success: message = f"✓ 完成处理: {os.path.basename(original_file)}" if processed_files: message += f" -> 已保存到: {processed_files[0]}" self.log_message(message) else: self.log_message(f"✗ 处理失败: {os.path.basename(original_file)}") def on_processing_error(self, error_msg): """处理错误回调""" self.log_message(f"❌ 错误: {error_msg}") # 即使出错,也要增加索引 self.current_image_index += 1 def on_worker_finished(self): """工作线程完成""" self.start_button.setEnabled(True) self.progress_bar.setValue(100) self.log_message("所有图片处理完成!") # 询问是否打开结果文件夹 output_dir = self.config.get("output_dir", "watermark_removed") if os.path.exists(output_dir): reply = QMessageBox.question( self, "处理完成", "处理完成!是否打开结果文件夹?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: import subprocess try: if sys.platform == "win32": os.startfile(output_dir) elif sys.platform == "darwin": subprocess.run(["open", output_dir]) else: subprocess.run(["xdg-open", output_dir]) except Exception as e: self.log_message(f"无法打开文件夹: {str(e)}") def closeEvent(self, event): """关闭事件处理""" # 保存当前API Key current_api_key = self.api_key_edit.text().strip() if current_api_key and current_api_key != self.config.get("api_key"): self.config["api_key"] = current_api_key save_config(self.config) # 保存当前文件夹路径 current_folder = self.folder_edit.text().strip() if current_folder and current_folder != self.config.get("last_folder"): self.config["last_folder"] = current_folder save_config(self.config) if self.worker and self.worker.isRunning(): reply = QMessageBox.question( self, "确认退出", "处理正在进行中,确定要退出吗?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: self.worker.stop() self.worker.wait() event.accept() else: event.ignore() else: event.accept()# --- 主程序入口 ---def main(): app = QApplication(sys.argv) app.setStyle('Fusion') window = WatermarkRemovalApp() window.show() sys.exit(app.exec())if __name__ == "__main__": main()