AI图片重命名工具:提高效率_优化管理_必备神器

排序方法:点击其中一张图片,再点击另一个图片可互换位置

图片[1]-AI图片重命名工具:提高效率_优化管理_必备神器
import sys
import os
import time
import tempfile
import shutil
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QListWidget, QListWidgetItem, QLabel, QTextEdit,
    QComboBox, QFileDialog, QLineEdit, QMessageBox, QAbstractItemView,
    QStatusBar, QAction, QMenu, QFrame
)
from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QFont
from PyQt5.QtCore import Qt, QSize, QSettings, QUrl
 
from PIL import Image
 
APP_ICON_PATH = 'icon.png'
 
class ImageBatchProcessor(QMainWindow):
    DEFAULT_FONT_SIZE = 10
    SUPPORTED_EXTENSIONS = ('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.tiff')
 
    def __init__(self):
        super().__init__()
         
        self.settings = self.init_settings()
        self.temp_dir = None
        self.item_to_move = None # 用于“点击-移动”功能的状态变量
         
        self.initUI()
        self.load_settings()
         
        self.setAcceptDrops(True)
 
    def init_settings(self):
        config_file_name = 'config.ini'
        if getattr(sys, 'frozen', False):
            application_path = os.path.dirname(sys.executable)
        else:
            application_path = os.path.dirname(os.path.abspath(__file__))
         
        self.config_path = os.path.join(application_path, config_file_name)
        return QSettings(self.config_path, QSettings.IniFormat)
 
    def initUI(self):
        self.setWindowTitle('批量图片重命名与格式转换工具')
        self.setGeometry(100, 100, 1000, 650)
        if os.path.exists(APP_ICON_PATH): self.setWindowIcon(QIcon(APP_ICON_PATH))
        self.create_menus()
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QHBoxLayout(central_widget)
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.statusBar.showMessage('准备就绪。单击选择,按Delete删除,单击移动。')
         
        left_layout = QVBoxLayout()
        left_label = QLabel('<h3>1. 添加并排序图片</h3>')
        self.image_list_widget = QListWidget()
        self.image_list_widget.setDragDropMode(QAbstractItemView.NoDragDrop)
        self.image_list_widget.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.image_list_widget.itemClicked.connect(self.handle_item_click)
        self.image_list_widget.currentItemChanged.connect(self.update_preview)
        self.image_list_widget.setIconSize(QSize(64, 64))
         
        top_buttons_layout = QHBoxLayout()
        self.add_button = QPushButton('添加图片'); self.add_button.clicked.connect(self.add_images)
        self.paste_button = QPushButton('粘贴 (Ctrl+V)'); self.paste_button.clicked.connect(self.paste_from_clipboard)
        self.clear_button = QPushButton('清空列表'); self.clear_button.clicked.connect(self.clear_all)
        top_buttons_layout.addWidget(self.add_button); top_buttons_layout.addWidget(self.paste_button); top_buttons_layout.addWidget(self.clear_button)
         
        sort_group_label = QLabel("<b>预排序:</b>")
        sort_buttons_layout = QHBoxLayout()
        self.sort_name_asc_btn = QPushButton("名称 ↑"); self.sort_name_asc_btn.clicked.connect(lambda: self.sort_items(by='name'))
        self.sort_name_desc_btn = QPushButton("名称 ↓"); self.sort_name_desc_btn.clicked.connect(lambda: self.sort_items(by='name', reverse=True))
        self.sort_date_asc_btn = QPushButton("时间 ↑"); self.sort_date_asc_btn.clicked.connect(lambda: self.sort_items(by='mtime'))
        self.sort_date_desc_btn = QPushButton("时间 ↓"); self.sort_date_desc_btn.clicked.connect(lambda: self.sort_items(by='mtime', reverse=True))
        self.sort_name_asc_btn.setToolTip("按文件名升序排列"); self.sort_name_desc_btn.setToolTip("按文件名降序排列")
        self.sort_date_asc_btn.setToolTip("按修改时间升序排列 (旧->新)"); self.sort_date_desc_btn.setToolTip("按修改时间降序排列 (新->旧)")
        sort_buttons_layout.addWidget(sort_group_label); sort_buttons_layout.addWidget(self.sort_name_asc_btn); sort_buttons_layout.addWidget(self.sort_name_desc_btn); sort_buttons_layout.addWidget(self.sort_date_asc_btn); sort_buttons_layout.addWidget(self.sort_date_desc_btn)
         
        left_layout.addWidget(left_label); left_layout.addWidget(self.image_list_widget); left_layout.addLayout(top_buttons_layout)
        line = QFrame(); line.setFrameShape(QFrame.HLine); line.setFrameShadow(QFrame.Sunken)
        left_layout.addWidget(line); left_layout.addLayout(sort_buttons_layout)
         
        center_layout = QVBoxLayout(); preview_label_title = QLabel('<h3>图片预览</h3>'); self.preview_label = QLabel('请先在左侧选择一张图片'); self.preview_label.setAlignment(Qt.AlignCenter); self.preview_label.setFixedSize(350, 350); self.preview_label.setStyleSheet("border: 1px solid #ccc; background-color: #f0f0f0;"); center_layout.addWidget(preview_label_title); center_layout.addWidget(self.preview_label); center_layout.addStretch()
        right_layout = QVBoxLayout(); name_label = QLabel('<h3>2. 输入新名称 (每行一个)</h3>'); self.name_input = QTextEdit(); self.name_input.setPlaceholderText('新名字1\n新名字2\n新名字3\n...'); settings_label = QLabel('<h3>3. 输出设置</h3>'); format_layout = QHBoxLayout(); format_label = QLabel('输出格式:'); self.format_combo = QComboBox(); self.format_combo.addItems(['.jpg', '.png', '.bmp', '.gif', '.tiff']); format_layout.addWidget(format_label); format_layout.addWidget(self.format_combo); folder_layout = QHBoxLayout(); folder_label = QLabel('输出文件夹:'); self.output_path_line = QLineEdit(); self.output_path_line.setReadOnly(True); self.output_folder_button = QPushButton('浏览...'); self.output_folder_button.clicked.connect(self.select_output_folder); folder_layout.addWidget(folder_label); folder_layout.addWidget(self.output_path_line); folder_layout.addWidget(self.output_folder_button); self.start_button = QPushButton('开始处理'); self.start_button.setMinimumHeight(40); self.start_button.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;"); self.start_button.clicked.connect(self.start_processing); right_layout.addWidget(name_label); right_layout.addWidget(self.name_input); right_layout.addSpacing(20); right_layout.addWidget(settings_label); right_layout.addLayout(format_layout); right_layout.addLayout(folder_layout); right_layout.addStretch(); right_layout.addWidget(self.start_button)
        main_layout.addLayout(left_layout, 2); main_layout.addLayout(center_layout, 2); main_layout.addLayout(right_layout, 2)
 
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Delete: self.delete_selected_items()
        elif event.key() == Qt.Key_Escape: self.cancel_move()
        elif event.matches(QKeySequence.Paste): self.paste_from_clipboard()
        else: super().keyPressEvent(event)
 
    def delete_selected_items(self):
        selected_items = self.image_list_widget.selectedItems()
        if not selected_items: return
        reply = QMessageBox.question(self, '确认删除', f'确定要从列表中删除这 {len(selected_items)} 个项目吗?\n(此操作不可撤销)', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            for item in selected_items:
                file_path = item.data(Qt.UserRole)
                if self.temp_dir and self.temp_dir in file_path:
                    try: os.remove(file_path)
                    except OSError as e: print(f"删除临时文件失败: {e}")
                row = self.image_list_widget.row(item); self.image_list_widget.takeItem(row)
            self.statusBar.showMessage(f"成功删除 {len(selected_items)} 个项目。")
            if self.item_to_move in selected_items: self.item_to_move = None
 
    def handle_item_click(self, clicked_item):
        if not self.item_to_move:
            self.item_to_move = clicked_item; font = self.item_to_move.font(); font.setBold(True); self.item_to_move.setFont(font)
            self.statusBar.showMessage(f"已选择 '{os.path.basename(self.item_to_move.data(Qt.UserRole))}'。请点击目标位置以移动。")
        else:
            font = self.item_to_move.font(); font.setBold(False); self.item_to_move.setFont(font)
            if self.item_to_move is not clicked_item:
                from_row = self.image_list_widget.row(self.item_to_move); to_row = self.image_list_widget.row(clicked_item)
                item = self.image_list_widget.takeItem(from_row); self.image_list_widget.insertItem(to_row, item)
                self.image_list_widget.setCurrentItem(item)
            self.item_to_move = None; self.statusBar.showMessage("移动操作完成。")
 
    def cancel_move(self):
        if self.item_to_move:
            font = self.item_to_move.font(); font.setBold(False); self.item_to_move.setFont(font)
            self.item_to_move = None; self.statusBar.showMessage("移动操作已取消。")
 
    def sort_items(self, by='name', reverse=False):
        self.cancel_move()
        item_data_list = []
        for i in range(self.image_list_widget.count()):
            item = self.image_list_widget.item(i)
            item_data_list.append((item.data(Qt.UserRole), item.text()))
        if not item_data_list: return
 
        if by == 'name': key_func = lambda data: data[1].lower()
        elif by == 'mtime': key_func = lambda data: os.path.getmtime(data[0])
        else: return
 
        item_data_list.sort(key=key_func, reverse=reverse)
        self.image_list_widget.clear()
 
        for file_path, display_text in item_data_list:
            new_item = QListWidgetItem(display_text)
            new_item.setData(Qt.UserRole, file_path)
            new_item.setIcon(QIcon(file_path))
            self.image_list_widget.addItem(new_item)
        self.statusBar.showMessage(f"列表已按 {'降序' if reverse else '升序'} 排列。")
 
    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls(): event.acceptProposedAction()
        else: event.ignore()
 
    def dropEvent(self, event):
        if event.mimeData().hasUrls():
            urls = event.mimeData().urls(); self._process_file_urls(urls, "拖拽"); event.acceptProposedAction()
 
    def paste_from_clipboard(self):
        clipboard = QApplication.clipboard(); mime_data = clipboard.mimeData()
        if mime_data.hasImage(): self._paste_image_data(clipboard.image())
        elif mime_data.hasUrls(): self._process_file_urls(mime_data.urls(), "粘贴")
        else: self.statusBar.showMessage('剪贴板中没有可识别的图片或图片文件。')
 
    def _paste_image_data(self, q_image):
        if q_image.isNull(): self.statusBar.showMessage('无法获取剪贴板中的图片数据。'); return
        if self.temp_dir is None: self.temp_dir = tempfile.mkdtemp(prefix="ImageTool_")
        timestamp = int(time.time() * 1000); temp_filename = f"pasted_image_{timestamp}.png"
        temp_filepath = os.path.join(self.temp_dir, temp_filename); q_image.save(temp_filepath, 'PNG')
        self._add_image_item(temp_filepath); self.statusBar.showMessage(f'已从剪贴板粘贴图片: {temp_filename}')
 
    def _process_file_urls(self, urls, source_action="添加"):
        added_count = 0
        for url in urls:
            if url.isLocalFile():
                file_path = url.toLocalFile()
                if os.path.isfile(file_path) and file_path.lower().endswith(self.SUPPORTED_EXTENSIONS):
                    self._add_image_item(file_path); added_count += 1
        if added_count > 0: self.statusBar.showMessage(f'通过{source_action}添加了 {added_count} 张图片。')
        else: self.statusBar.showMessage(f'没有通过{source_action}添加有效的图片文件。')
 
    def _add_image_item(self, file_path):
        item = QListWidgetItem(os.path.basename(file_path)); item.setData(Qt.UserRole, file_path)
        item.setIcon(QIcon(file_path)); self.image_list_widget.addItem(item)
        self.image_list_widget.scrollToItem(item)
 
    def add_images(self):
        files, _ = QFileDialog.getOpenFileNames(self, "选择图片文件", "", f"Image Files (*{' *'.join(self.SUPPORTED_EXTENSIONS)})")
        if files: self._process_file_urls([QUrl.fromLocalFile(f) for f in files], "选择文件")
 
    def closeEvent(self, event):
        self.save_settings()
        if self.temp_dir and os.path.exists(self.temp_dir):
            try: shutil.rmtree(self.temp_dir)
            except Exception as e: print(f"清理临时文件夹失败: {e}")
        super(ImageBatchProcessor, self).closeEvent(event)
 
    def create_menus(self):
        menu_bar = self.menuBar(); settings_menu = menu_bar.addMenu('设置 (&S)'); font_menu = QMenu('字体大小', self); increase_font_action = QAction('增大字体 (+)', self); increase_font_action.triggered.connect(lambda: self.adjust_font_size(1)); increase_font_action.setShortcut('Ctrl++'); decrease_font_action = QAction('减小字体 (-)', self); decrease_font_action.triggered.connect(lambda: self.adjust_font_size(-1)); decrease_font_action.setShortcut('Ctrl+-'); reset_font_action = QAction('重置为默认', self); reset_font_action.triggered.connect(self.reset_font_size); reset_font_action.setShortcut('Ctrl+0'); font_menu.addAction(increase_font_action); font_menu.addAction(decrease_font_action); font_menu.addSeparator(); font_menu.addAction(reset_font_action); settings_menu.addMenu(font_menu)
 
    def adjust_font_size(self, delta):
        font = QApplication.font(); current_size = font.pointSize(); new_size = current_size + delta; 
        if 5 < new_size < 30: font.setPointSize(new_size); QApplication.setFont(font); self.statusBar.showMessage(f"字体大小已设置为 {new_size}pt")
 
    def reset_font_size(self):
        font = QApplication.font(); font.setPointSize(self.DEFAULT_FONT_SIZE); QApplication.setFont(font); self.statusBar.showMessage(f"字体大小已重置为 {self.DEFAULT_FONT_SIZE}pt")
 
    def load_settings(self):
        self.statusBar.showMessage(f"从 {os.path.basename(self.config_path)} 加载配置..."); geometry = self.settings.value("geometry", self.saveGeometry()); self.restoreGeometry(geometry); state = self.settings.value("windowState", self.saveState()); self.restoreState(state); font_size = self.settings.value("fontSize", self.DEFAULT_FONT_SIZE, type=int); font = QApplication.font(); font.setPointSize(font_size); QApplication.setFont(font); self.statusBar.showMessage(f"配置已加载,当前字体: {font_size}pt")
 
    def save_settings(self):
        self.settings.setValue("geometry", self.saveGeometry()); self.settings.setValue("windowState", self.saveState()); self.settings.setValue("fontSize", QApplication.font().pointSize()); self.statusBar.showMessage(f"配置已保存到 {os.path.basename(self.config_path)}")
 
    def update_preview(self, current_item, previous_item):
        if not current_item: self.preview_label.clear(); self.preview_label.setText('请先在左侧选择一张图片'); return
        path = current_item.data(Qt.UserRole); pixmap = QPixmap(path); scaled_pixmap = pixmap.scaled(self.preview_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation); self.preview_label.setPixmap(scaled_pixmap)
 
    def select_output_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "选择输出文件夹"); 
        if folder: self.output_path_line.setText(folder); self.statusBar.showMessage(f'输出文件夹已选择: {folder}')
 
    def clear_all(self):
        self.cancel_move()
        if self.temp_dir and os.path.exists(self.temp_dir): shutil.rmtree(self.temp_dir); self.temp_dir = None
        self.image_list_widget.clear(); self.name_input.clear(); self.output_path_line.clear(); self.preview_label.clear(); self.preview_label.setText('请先在左侧选择一张图片'); self.statusBar.showMessage('已清空所有内容。')
 
    def start_processing(self):
        self.cancel_move()
        image_count = self.image_list_widget.count()
        if image_count == 0: QMessageBox.warning(self, '错误', '请先添加图片!'); return
        new_names = [line for line in self.name_input.toPlainText().splitlines() if line.strip()]
        if len(new_names) != image_count: QMessageBox.warning(self, '错误', f'图片数量 ({image_count}) 与新名称数量 ({len(new_names)}) 不匹配!'); return
        output_dir = self.output_path_line.text()
        if not output_dir or not os.path.isdir(output_dir): QMessageBox.warning(self, '错误', '请选择一个有效的输出文件夹!'); return
        output_format = self.format_combo.currentText(); processed_count = 0
        for i in range(image_count):
            try:
                item = self.image_list_widget.item(i); original_path = item.data(Qt.UserRole); new_name_base = new_names[i]; new_filename = f"{new_name_base}{output_format}"; new_filepath = os.path.join(output_dir, new_filename); self.statusBar.showMessage(f'正在处理: {os.path.basename(original_path)} -> {new_filename}')
                with Image.open(original_path) as img:
                    if img.mode in ('RGBA', 'P') and output_format.lower() in ['.jpg', '.jpeg']: img = img.convert('RGB')
                    img.save(new_filepath)
            except Exception as e: QMessageBox.critical(self, '处理失败', f'处理文件 {os.path.basename(original_path)} 时发生错误:\n{str(e)}'); self.statusBar.showMessage('处理中断。'); return
            processed_count += 1
        QMessageBox.information(self, '成功', f'成功处理了 {processed_count} 张图片!\n文件已保存到: {output_dir}')
 
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = ImageBatchProcessor()
    ex.show()
    sys.exit(app.exec_())
------本页内容已结束,喜欢请分享------
温馨提示:由于项目或工具都有失效性,如遇到不能做的项目或不能使用的工具,可以根据关键词在站点搜索相关内容,查看最近更新的或者在网页底部给我们留言反馈。
© 版权声明
THE END
喜欢就支持一下吧
点赞1023 分享