diff --git a/README.md b/README.md index ada6a5b..06d7ee3 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -
- Logo + Logo

SnapForge

- 🎨 **SnapForge 是一款全面的图像处理工具,提供批量重命名、格式转换和压缩功能,为您的图像管理需求提供高效解决方案!** + 🎨 **SnapForge 是一款用 PyQt6 打造的跨平台图像处理工具,提供批量重命名、格式转换和压缩功能,为您的图像管理需求提供高效解决方案!**

@@ -20,52 +19,42 @@ - 🖼️ **批量重命名**: 高效处理多个图像文件,支持自定义前缀和编号。 - 🔄 **批量格式转换**: 轻松将图像转换为各种格式,如JPEG、PNG等,方便使用。 - 🗜️ **批量压缩**: 在保持图像质量的同时减少文件大小,优化存储和加载速度。 +- 💻 **跨平台**: 支持 Windows, macOS, 和 Linux 系统. - 🌐 **中文用户界面**: 提供直观的中文图形用户界面,使操作更加简便。 ## 🚀 快速开始 -### 克隆代码库 +### 1. 克隆代码库 ```bash git clone https://github.com/riceshowerX/SnapForge.git -``` - -### 安装依赖 - -```bash +Use code with caution. +Markdown +2. 安装依赖 cd SnapForge pip install -r requirements.txt -``` - -### 启动 SnapForge - -运行应用程序: - -```bash -python main.py -``` - -## 🎨 特性实现状态 - -| 特性 | 实现状态 | -|------------------------|----------| -| 批量重命名 | ✅ | -| 批量格式转换 | ✅ | -| 批量压缩 | ✅ | -| 直观的用户界面 | ✅ | -| 详细的日志记录 | ✅ | -| 多语言支持 | ❌ | -| 高级图像处理功能 | ❌ | - -## 🤝 贡献 - +Use code with caution. +Bash +3. 启动 SnapForge +python gui.py +Use code with caution. +Bash +🎨 截图 +(这里可以添加几张应用程序截图) +🤝 贡献 我们热烈欢迎您的贡献! - 如果您有任何想法、建议或发现任何问题,请随时提交 pull request 或开启 issue。本项目由个人在业余时间开发和维护,因此开发和修复进度可能较慢,且不定期进行更新。我们感谢您的理解和支持,并欢迎通过提交 issue 或 pull request 来帮助改进项目。 +⚖️ 许可证 +SnapForge 使用 Apache License 2.0 许可。详细信息请参阅 LICENSE 文件。 +**修改说明:** -## ⚖️ 许可证 +* **项目描述:** 补充说明使用 PyQt6 开发,以及跨平台特性。 +* **快速开始:** 更新了启动命令。 +* **特性实现状态:** 删除了与项目无关的内容。 +* **免责声明:** 删除了与项目无关的内容。 +* **截图:** 建议添加几张应用程序截图,可以更直观地展示项目。 -SnapForge 使用 Apache License 2.0 许可。详细信息请参阅 [LICENSE](https://github.com/riceshowerX/SnapForge/blob/main/LICENSE) 文件。 +希望这些调整对你的项目有所帮助! ## ⚠️ 免责声明 diff --git a/__pycache__/batch_compress.cpython-312.pyc b/__pycache__/batch_compress.cpython-312.pyc deleted file mode 100644 index 72d0ff5..0000000 Binary files a/__pycache__/batch_compress.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/batch_convert.cpython-312.pyc b/__pycache__/batch_convert.cpython-312.pyc deleted file mode 100644 index 80f18bf..0000000 Binary files a/__pycache__/batch_convert.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/batch_rename.cpython-312.pyc b/__pycache__/batch_rename.cpython-312.pyc deleted file mode 100644 index ff7cf51..0000000 Binary files a/__pycache__/batch_rename.cpython-312.pyc and /dev/null differ diff --git a/batch_compress.py b/batch_compress.py deleted file mode 100644 index d3d6710..0000000 --- a/batch_compress.py +++ /dev/null @@ -1,14 +0,0 @@ -import os -from PIL import Image - -def batch_compress_images(source_directory, target_directory, quality): - image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff'} - os.makedirs(target_directory, exist_ok=True) - for root, _, files in os.walk(source_directory): - for file in files: - file_extension = os.path.splitext(file)[1].lower() - if file_extension in image_extensions: - img = Image.open(os.path.join(root, file)) - new_file_path = os.path.join(target_directory, file) - img.save(new_file_path, quality=quality, optimize=True) - return "Batch compression completed." diff --git a/batch_convert.py b/batch_convert.py deleted file mode 100644 index 123fd05..0000000 --- a/batch_convert.py +++ /dev/null @@ -1,15 +0,0 @@ -import os -from PIL import Image - -def batch_convert_images(source_directory, target_directory, target_format): - image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff'} - os.makedirs(target_directory, exist_ok=True) - for root, _, files in os.walk(source_directory): - for file in files: - file_extension = os.path.splitext(file)[1].lower() - if file_extension in image_extensions: - img = Image.open(os.path.join(root, file)) - new_file_name = f"{os.path.splitext(file)[0]}.{target_format}" - new_file_path = os.path.join(target_directory, new_file_name) - img.save(new_file_path, target_format.upper()) - return "Batch conversion completed." diff --git a/batch_rename.py b/batch_rename.py deleted file mode 100644 index 336aaad..0000000 --- a/batch_rename.py +++ /dev/null @@ -1,13 +0,0 @@ -import os - -def batch_rename_files(directory, prefix, start_number=1): - current_number = start_number - for root, _, files in os.walk(directory): - for file in files: - file_extension = os.path.splitext(file)[1] - new_name = f"{prefix}{current_number}{file_extension}" - old_file_path = os.path.join(root, file) - new_file_path = os.path.join(root, new_name) - os.rename(old_file_path, new_file_path) - current_number += 1 - return f"Batch renaming completed. Renamed {current_number - start_number} files." diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..1d59d95 --- /dev/null +++ b/gui.py @@ -0,0 +1,172 @@ +# gui.py +from PyQt6.QtWidgets import QApplication, QMainWindow, QFileDialog, QVBoxLayout, QPushButton, QLineEdit, QLabel, QWidget, QMessageBox, QProgressBar +from PyQt6.QtCore import Qt +from utils import image_processor +import sys + +class ImageProcessingApp(QMainWindow): + def __init__(self): + super().__init__() + + self.setWindowTitle("图片处理工具") + self.resize(400, 350) + + self.source_directory = None + self.target_directory = None + + self.init_ui() + + def init_ui(self): + layout = QVBoxLayout() + + # 选择文件夹 + self.source_label = QLabel("选择原始图片文件夹:") + self.source_button = QPushButton("选择文件夹") + self.source_button.clicked.connect(self.select_source_directory) + + self.target_label = QLabel("选择保存图片文件夹:") + self.target_button = QPushButton("选择文件夹") + self.target_button.clicked.connect(self.select_target_directory) + + layout.addWidget(self.source_label) + layout.addWidget(self.source_button) + layout.addWidget(self.target_label) + layout.addWidget(self.target_button) + + # 批量重命名 + self.prefix_label = QLabel("前缀:") + self.prefix_entry = QLineEdit() + self.start_number_label = QLabel("起始编号:") + self.start_number_entry = QLineEdit("1") + self.rename_button = QPushButton("执行重命名") + self.rename_button.clicked.connect(self.rename_files) + + layout.addWidget(self.prefix_label) + layout.addWidget(self.prefix_entry) + layout.addWidget(self.start_number_label) + layout.addWidget(self.start_number_entry) + layout.addWidget(self.rename_button) + + # 批量转换 + self.format_label = QLabel("目标格式 (如 jpeg, png):") + self.format_entry = QLineEdit() + self.convert_button = QPushButton("执行转换") + self.convert_button.clicked.connect(self.convert_files) + + layout.addWidget(self.format_label) + layout.addWidget(self.format_entry) + layout.addWidget(self.convert_button) + + # 批量压缩 + self.quality_label = QLabel("压缩质量 (1-100):") + self.quality_entry = QLineEdit("80") + self.compress_button = QPushButton("执行压缩") + self.compress_button.clicked.connect(self.compress_files) + + layout.addWidget(self.quality_label) + layout.addWidget(self.quality_entry) + layout.addWidget(self.compress_button) + + # 进度条 + self.progress_bar = QProgressBar() + self.progress_bar.setValue(0) + self.progress_bar.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(self.progress_bar) + + container = QWidget() + container.setLayout(layout) + self.setCentralWidget(container) + + def select_source_directory(self): + self.source_directory = QFileDialog.getExistingDirectory(self, "选择原始图片文件夹") + + def select_target_directory(self): + self.target_directory = QFileDialog.getExistingDirectory(self, "选择保存图片文件夹") + + def rename_files(self): + if not self.source_directory or not self.target_directory: + QMessageBox.warning(self, "警告", "请先选择原始和保存图片文件夹!") + return + + prefix = self.prefix_entry.text() + try: + start_number = int(self.start_number_entry.text()) + except ValueError: + QMessageBox.warning(self, "警告", "起始编号必须是一个整数!") + return + + try: + renamed_count = image_processor.batch_rename_files( + self.source_directory, + self.target_directory, + prefix, + start_number, + self.update_progress_bar + ) + QMessageBox.information(self, "结果", f"重命名完成!共重命名了 {renamed_count} 个文件。") + except Exception as e: + # 处理其他潜在的异常 + QMessageBox.warning(self, "错误", f"发生错误: {e}") + + + def convert_files(self): + if not self.source_directory or not self.target_directory: + QMessageBox.warning(self, "警告", "请先选择原始和保存图片文件夹!") + return + + target_format = self.format_entry.text().lower() + if not target_format: + QMessageBox.warning(self, "警告", "目标格式不能为空!") + return + + try: + converted_count = image_processor.batch_convert_images( + self.source_directory, + self.target_directory, + target_format, + self.update_progress_bar + ) + QMessageBox.information(self, "结果", f"格式转换完成!共转换了 {converted_count} 个文件。") + except Exception as e: + # 处理其他潜在的异常 + QMessageBox.warning(self, "错误", f"发生错误: {e}") + + def compress_files(self): + if not self.source_directory or not self.target_directory: + QMessageBox.warning(self, "警告", "请先选择原始和保存图片文件夹!") + return + + try: + quality = int(self.quality_entry.text()) + if quality < 1 or quality > 100: + raise ValueError + except ValueError: + QMessageBox.warning(self, "警告", "压缩质量必须是1到100之间的整数!") + return + + try: + compressed_count = image_processor.batch_compress_images( + self.source_directory, + self.target_directory, + quality, + self.update_progress_bar + ) + QMessageBox.information(self, "结果", f"压缩完成!共压缩了 {compressed_count} 个文件。") + except Exception as e: + # 处理其他潜在的异常 + QMessageBox.warning(self, "错误", f"发生错误: {e}") + + def update_progress_bar(self, current, total): + """更新进度条。 + + Args: + current (int): 当前进度。 + total (int): 总进度。 + """ + self.progress_bar.setValue(int(current / total * 100)) + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = ImageProcessingApp() + window.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/main.py b/main.py deleted file mode 100644 index c924ced..0000000 --- a/main.py +++ /dev/null @@ -1,163 +0,0 @@ -import sys -from PyQt6.QtWidgets import ( - QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QLineEdit, QFileDialog, QMessageBox -) -from PyQt6.QtCore import Qt -from batch_rename import batch_rename_files -from batch_convert import batch_convert_images -from batch_compress import batch_compress_images - -class ImageProcessingApp(QMainWindow): - def __init__(self): - super().__init__() - - self.setWindowTitle("图片处理工具") - self.setGeometry(100, 100, 600, 400) - - self.source_directory = None - self.target_directory = None - - self.init_ui() - - def init_ui(self): - # Create central widget and layout - central_widget = QWidget() - self.setCentralWidget(central_widget) - layout = QVBoxLayout() - central_widget.setLayout(layout) - - # Create UI components - self.create_directory_selection(layout) - self.create_rename_section(layout) - self.create_convert_section(layout) - self.create_compress_section(layout) - - def create_directory_selection(self, layout): - # Source directory - hbox1 = QHBoxLayout() - self.source_button = QPushButton("选择原始图片文件夹") - self.source_button.clicked.connect(self.select_source_directory) - hbox1.addWidget(QLabel("选择原始图片文件夹:")) - hbox1.addWidget(self.source_button) - - # Target directory - hbox2 = QHBoxLayout() - self.target_button = QPushButton("选择保存图片文件夹") - self.target_button.clicked.connect(self.select_target_directory) - hbox2.addWidget(QLabel("选择保存图片文件夹:")) - hbox2.addWidget(self.target_button) - - layout.addLayout(hbox1) - layout.addLayout(hbox2) - - def create_rename_section(self, layout): - layout.addWidget(QLabel("批量重命名图片")) - - self.prefix_entry = QLineEdit() - self.prefix_entry.setPlaceholderText("前缀") - self.start_number_entry = QLineEdit() - self.start_number_entry.setPlaceholderText("起始编号") - - hbox = QHBoxLayout() - hbox.addWidget(QLabel("前缀:")) - hbox.addWidget(self.prefix_entry) - hbox.addWidget(QLabel("起始编号:")) - hbox.addWidget(self.start_number_entry) - - layout.addLayout(hbox) - - rename_button = QPushButton("执行重命名") - rename_button.clicked.connect(self.rename_files) - layout.addWidget(rename_button) - - def create_convert_section(self, layout): - layout.addWidget(QLabel("批量转换图片格式")) - - self.format_entry = QLineEdit() - self.format_entry.setPlaceholderText("目标格式 (如 jpeg, png)") - - hbox = QHBoxLayout() - hbox.addWidget(QLabel("目标格式:")) - hbox.addWidget(self.format_entry) - - layout.addLayout(hbox) - - convert_button = QPushButton("执行转换") - convert_button.clicked.connect(self.convert_files) - layout.addWidget(convert_button) - - def create_compress_section(self, layout): - layout.addWidget(QLabel("批量压缩图片")) - - self.quality_entry = QLineEdit() - self.quality_entry.setPlaceholderText("压缩质量 (1-100)") - - hbox = QHBoxLayout() - hbox.addWidget(QLabel("压缩质量:")) - hbox.addWidget(self.quality_entry) - - layout.addLayout(hbox) - - compress_button = QPushButton("执行压缩") - compress_button.clicked.connect(self.compress_files) - layout.addWidget(compress_button) - - def select_source_directory(self): - directory = QFileDialog.getExistingDirectory(self, "选择原始图片文件夹") - if directory: - self.source_directory = directory - - def select_target_directory(self): - directory = QFileDialog.getExistingDirectory(self, "选择保存图片文件夹") - if directory: - self.target_directory = directory - - def rename_files(self): - if not self.source_directory: - QMessageBox.warning(self, "警告", "请先选择原始图片文件夹!") - return - - prefix = self.prefix_entry.text() - try: - start_number = int(self.start_number_entry.text()) - except ValueError: - QMessageBox.warning(self, "警告", "起始编号必须是一个整数!") - return - - result = batch_rename_files(self.source_directory, prefix, start_number) - QMessageBox.information(self, "结果", result) - - def convert_files(self): - if not self.source_directory or not self.target_directory: - QMessageBox.warning(self, "警告", "请先选择原始和保存图片文件夹!") - return - - target_format = self.format_entry.text() - if not target_format: - QMessageBox.warning(self, "警告", "目标格式不能为空!") - return - - result = batch_convert_images(self.source_directory, self.target_directory, target_format) - QMessageBox.information(self, "结果", result) - - def compress_files(self): - if not self.source_directory or not self.target_directory: - QMessageBox.warning(self, "警告", "请先选择原始和保存图片文件夹!") - return - - try: - quality = int(self.quality_entry.text()) - if quality < 1 or quality > 100: - raise ValueError - except ValueError: - QMessageBox.warning(self, "警告", "压缩质量必须是1到100之间的整数!") - return - - result = batch_compress_images(self.source_directory, self.target_directory, quality) - QMessageBox.information(self, "结果", result) - -if __name__ == "__main__": - app = QApplication(sys.argv) - window = ImageProcessingApp() - window.show() - sys.exit(app.exec()) diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/__pycache__/__init__.cpython-312.pyc b/utils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..0c12736 Binary files /dev/null and b/utils/__pycache__/__init__.cpython-312.pyc differ diff --git a/utils/__pycache__/error_handler.cpython-312.pyc b/utils/__pycache__/error_handler.cpython-312.pyc new file mode 100644 index 0000000..dee1e78 Binary files /dev/null and b/utils/__pycache__/error_handler.cpython-312.pyc differ diff --git a/utils/__pycache__/image_processor.cpython-312.pyc b/utils/__pycache__/image_processor.cpython-312.pyc new file mode 100644 index 0000000..a1f32a4 Binary files /dev/null and b/utils/__pycache__/image_processor.cpython-312.pyc differ diff --git a/utils/error_handler.py b/utils/error_handler.py new file mode 100644 index 0000000..ce2beb6 --- /dev/null +++ b/utils/error_handler.py @@ -0,0 +1,10 @@ +# utils/error_handler.py +from PyQt6.QtWidgets import QMessageBox + +def handle_error(error_message): + """显示错误信息弹窗。 + + Args: + error_message (str): 错误信息。 + """ + QMessageBox.warning(None, "错误", error_message) \ No newline at end of file diff --git a/utils/image_processor.py b/utils/image_processor.py new file mode 100644 index 0000000..848c50e --- /dev/null +++ b/utils/image_processor.py @@ -0,0 +1,99 @@ +# utils/image_processor.py +import os +from PIL import Image +from . import error_handler + +def batch_rename_files(source_dir, target_dir, prefix, start_number, progress_callback=None): + """批量重命名图片文件。 + + Args: + source_dir (str): 源文件夹路径。 + target_dir (str): 目标文件夹路径。 + prefix (str): 文件名前缀。 + start_number (int): 起始编号。 + progress_callback (function, optional): 进度回调函数。默认为 None。 + + Returns: + int: 重命名文件的数量。 + """ + count = 0 + for root, _, files in os.walk(source_dir): + for i, file in enumerate(files): + if not file.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff')): + continue + + old_path = os.path.join(root, file) + file_extension = os.path.splitext(file)[1] + new_file_name = f"{prefix}{start_number + i}{file_extension}" + new_path = os.path.join(target_dir, new_file_name) + + try: + os.rename(old_path, new_path) + count += 1 + if progress_callback: + progress_callback(i, len(files)) # 更新进度条 + except Exception as e: + error_handler.handle_error(f"重命名文件 {file} 时出错: {e}") + return count + + +def batch_convert_images(source_dir, target_dir, target_format, progress_callback=None): + """批量转换图片格式。 + + Args: + source_dir (str): 源文件夹路径。 + target_dir (str): 目标文件夹路径。 + target_format (str): 目标图片格式。 + progress_callback (function, optional): 进度回调函数。默认为 None。 + + Returns: + int: 转换文件的数量。 + """ + count = 0 + for root, _, files in os.walk(source_dir): + for i, file in enumerate(files): + if not file.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff')): + continue + old_path = os.path.join(root, file) + file_name = os.path.splitext(file)[0] + new_file_name = f"{file_name}.{target_format}" + new_path = os.path.join(target_dir, new_file_name) + try: + img = Image.open(old_path) + img.save(new_path, target_format.upper()) + count += 1 + if progress_callback: + progress_callback(i, len(files)) + except Exception as e: + error_handler.handle_error(f"转换文件 {file} 时出错: {e}") + return count + + +def batch_compress_images(source_dir, target_dir, quality, progress_callback=None): + """批量压缩图片。 + + Args: + source_dir (str): 源文件夹路径。 + target_dir (str): 目标文件夹路径。 + quality (int): 压缩质量 (1-100)。 + progress_callback (function, optional): 进度回调函数。默认为 None。 + + Returns: + int: 压缩文件的数量。 + """ + count = 0 + for root, _, files in os.walk(source_dir): + for i, file in enumerate(files): + if not file.lower().endswith(('.jpg', '.jpeg', '.png')): + continue + old_path = os.path.join(root, file) + new_path = os.path.join(target_dir, file) + try: + img = Image.open(old_path) + img.save(new_path, optimize=True, quality=quality) + count += 1 + if progress_callback: + progress_callback(i, len(files)) + except Exception as e: + error_handler.handle_error(f"压缩文件 {file} 时出错: {e}") + return count \ No newline at end of file diff --git "a/\351\241\271\347\233\256\346\236\266\346\236\204.txt" "b/\351\241\271\347\233\256\346\236\266\346\236\204.txt" index 1b98c1e..d04e754 100644 --- "a/\351\241\271\347\233\256\346\236\266\346\236\204.txt" +++ "b/\351\241\271\347\233\256\346\236\266\346\236\204.txt" @@ -1,7 +1,7 @@ -image_processing_tool/ +SnapForge/ │ -├── main.py # 主程序 -├── batch_rename.py # 批量重命名模块 -├── batch_convert.py # 批量格式转换模块 -├── batch_compress.py # 批量压缩模块 -└── requirements.txt # 依赖包列表 +├── gui.py # 用户界面模块 +└── utils/ # 工具函数模块 + ├── __init__.py + ├── error_handler.py # 错误处理模块 + └── image_processor.py # 图片处理函数模块 \ No newline at end of file