用ds生成小工具image_converter

• 18 分钟阅读 • python · deepseek

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

程序主代码

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

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文件

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。

文章标签: python, deepseek

上一篇 : 用ds生成markdown编辑器
下一篇 : docker alpine里的crond
留言
阅读进度 0%