17  Bulk File Rename Tool Python

18 RP 이름 바꾸기

RP Renamer는 Python과 PyQt5로 구축된 대량 파일 이름 바꾸기 도구입니다.

18.1 애플리케이션 실행

RP Renamer를 실행하려면 소스 코드를 다운로드해야 합니다. 그런 다음 터미널이나 명령줄 창을 열고 다음 단계를 실행합니다.

  1. Python 가상 환경을 생성하고 활성화합니다.
$ cd rprename_project/
$ python3 -m venv ./venv
$ source venv/bin/activate
(venv) $
  1. 종속성을 설치합니다.
(venv) $ pip install -r requirements.txt
  1. 애플리케이션을 실행합니다.
(venv) $ python3 rprenamer.py

참고: 이 애플리케이션은 Python 3.8.5 및 PyQt 5.15.2로 코딩 및 테스트되었습니다.

18.2 출시 내역

  • 0.1.0
    • 진행중인 작업

18.3 저자 소개

레오다니스 포조 라모스 – (lpozo78?) – leodanis@realpython.com

18.4 파일: source_code_final/rprename/__init__.py

이 모듈은 RP Renamer 기본 창을 제공합니다.

# -*- coding: utf-8 -*-

"""This module provides the RP Renamer main window."""

__version__ = "0.1.0"

18.5 파일: source_code_final/rprename/app.py

이 모듈은 RP Renamer 애플리케이션을 제공합니다.

# -*- coding: utf-8 -*-

"""This module provides the RP Renamer application."""

import sys

from PyQt5.QtWidgets import QApplication

from .views import Window


def main():
    # Create the application
    app = QApplication(sys.argv)
    # Create and show the main window
    win = Window()
    win.show()
    # Run the event loop
    sys.exit(app.exec())

18.6 파일: source_code_final/rprename/rename.py

이 모듈은 여러 파일의 이름을 바꿀 수 있는 Renamer 클래스를 제공합니다.

# -*- coding: utf-8 -*-

"""This module provides the Renamer class to rename multiple files."""

import time
from pathlib import Path

from PyQt5.QtCore import QObject, pyqtSignal


class Renamer(QObject):
    # Define custom signals
    progressed = pyqtSignal(int)
    renamedFile = pyqtSignal(Path)
    finished = pyqtSignal()

    def __init__(self, files, prefix):
        super().__init__()
        self._files = files
        self._prefix = prefix

    def renameFiles(self):
        for fileNumber, file in enumerate(self._files, 1):
            newFile = file.parent.joinpath(
                f"{self._prefix}{str(fileNumber)}{file.suffix}"
            )
            file.rename(newFile)
            time.sleep(0.1)  # Comment this line to rename files faster.
            self.progressed.emit(fileNumber)
            self.renamedFile.emit(newFile)
        self.progressed.emit(0)  # Reset the progress
        self.finished.emit()

18.7 파일: source_code_final/rprename/ui/__init__.py

이 모듈은 RP Renamer GUI를 제공합니다.

# -*- coding: utf-8 -*-

"""This module provides the RP Renamer GUI."""

18.8 파일: source_code_final/rprename/ui/window.py

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'window.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
#
# WARNING! All changes made in this file will be lost!


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Window(object):
    def setupUi(self, Window):
        Window.setObjectName("Window")
        Window.resize(720, 480)
        self.gridLayout = QtWidgets.QGridLayout(Window)
        self.gridLayout.setObjectName("gridLayout")
        self.label = QtWidgets.QLabel(Window)
        self.label.setMinimumSize(QtCore.QSize(0, 15))
        self.label.setMaximumSize(QtCore.QSize(16777215, 15))
        self.label.setObjectName("label")
        self.gridLayout.addWidget(self.label, 0, 0, 1, 3)
        self.dirEdit = QtWidgets.QLineEdit(Window)
        self.dirEdit.setMinimumSize(QtCore.QSize(0, 30))
        self.dirEdit.setMaximumSize(QtCore.QSize(16777215, 30))
        self.dirEdit.setReadOnly(True)
        self.dirEdit.setObjectName("dirEdit")
        self.gridLayout.addWidget(self.dirEdit, 1, 0, 1, 2)
        self.loadFilesButton = QtWidgets.QPushButton(Window)
        self.loadFilesButton.setMinimumSize(QtCore.QSize(0, 30))
        self.loadFilesButton.setMaximumSize(QtCore.QSize(16777215, 30))
        self.loadFilesButton.setObjectName("loadFilesButton")
        self.gridLayout.addWidget(self.loadFilesButton, 1, 2, 1, 1)
        self.splitter = QtWidgets.QSplitter(Window)
        self.splitter.setOrientation(QtCore.Qt.Horizontal)
        self.splitter.setObjectName("splitter")
        self.layoutWidget = QtWidgets.QWidget(self.splitter)
        self.layoutWidget.setObjectName("layoutWidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout.setObjectName("verticalLayout")
        self.label_2 = QtWidgets.QLabel(self.layoutWidget)
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.label_2.setFont(font)
        self.label_2.setObjectName("label_2")
        self.verticalLayout.addWidget(self.label_2)
        self.srcFileList = QtWidgets.QListWidget(self.layoutWidget)
        self.srcFileList.setObjectName("srcFileList")
        self.verticalLayout.addWidget(self.srcFileList)
        self.layoutWidget1 = QtWidgets.QWidget(self.splitter)
        self.layoutWidget1.setObjectName("layoutWidget1")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.layoutWidget1)
        self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.label_3 = QtWidgets.QLabel(self.layoutWidget1)
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.label_3.setFont(font)
        self.label_3.setObjectName("label_3")
        self.verticalLayout_2.addWidget(self.label_3)
        self.dstFileList = QtWidgets.QListWidget(self.layoutWidget1)
        self.dstFileList.setObjectName("dstFileList")
        self.verticalLayout_2.addWidget(self.dstFileList)
        self.gridLayout.addWidget(self.splitter, 2, 0, 1, 3)
        self.label_4 = QtWidgets.QLabel(Window)
        self.label_4.setMinimumSize(QtCore.QSize(0, 15))
        self.label_4.setMaximumSize(QtCore.QSize(16777215, 15))
        self.label_4.setObjectName("label_4")
        self.gridLayout.addWidget(self.label_4, 3, 0, 1, 3)
        self.prefixEdit = QtWidgets.QLineEdit(Window)
        self.prefixEdit.setMinimumSize(QtCore.QSize(0, 30))
        self.prefixEdit.setMaximumSize(QtCore.QSize(16777215, 30))
        self.prefixEdit.setObjectName("prefixEdit")
        self.gridLayout.addWidget(self.prefixEdit, 4, 0, 1, 1)
        self.extensionLabel = QtWidgets.QLabel(Window)
        self.extensionLabel.setMinimumSize(QtCore.QSize(0, 30))
        self.extensionLabel.setMaximumSize(QtCore.QSize(16777215, 30))
        self.extensionLabel.setObjectName("extensionLabel")
        self.gridLayout.addWidget(self.extensionLabel, 4, 1, 1, 1)
        self.renameFilesButton = QtWidgets.QPushButton(Window)
        self.renameFilesButton.setMinimumSize(QtCore.QSize(0, 30))
        self.renameFilesButton.setMaximumSize(QtCore.QSize(16777215, 30))
        self.renameFilesButton.setObjectName("renameFilesButton")
        self.gridLayout.addWidget(self.renameFilesButton, 4, 2, 1, 1)
        self.progressBar = QtWidgets.QProgressBar(Window)
        self.progressBar.setProperty("value", 0)
        self.progressBar.setObjectName("progressBar")
        self.gridLayout.addWidget(self.progressBar, 5, 0, 1, 3)

        self.retranslateUi(Window)
        QtCore.QMetaObject.connectSlotsByName(Window)

    def retranslateUi(self, Window):
        _translate = QtCore.QCoreApplication.translate
        Window.setWindowTitle(_translate("Window", "RP Renamer"))
        self.label.setText(_translate("Window", "Last Source Directory:"))
        self.loadFilesButton.setText(_translate("Window", "&Load Files"))
        self.label_2.setText(_translate("Window", "Files to Rename"))
        self.label_3.setText(_translate("Window", "Renamed Files"))
        self.label_4.setText(_translate("Window", "Filename Prefix:"))
        self.prefixEdit.setPlaceholderText(
            _translate("Window", "Rename your files to...")
        )
        self.extensionLabel.setText(_translate("Window", "*.jpg"))
        self.renameFilesButton.setText(_translate("Window", "&Rename"))

18.9 파일: source_code_final/rprename/views.py

이 모듈은 RP Renamer 기본 창을 제공합니다.

# -*- coding: utf-8 -*-

"""This module provides the RP Renamer main window."""

from collections import deque
from pathlib import Path

from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QFileDialog, QWidget

from .rename import Renamer
from .ui.window import Ui_Window

FILTERS = ";;".join(
    (
        "PNG Files (*.png)",
        "JPEG Files (*.jpeg)",
        "JPG Files (*.jpg)",
        "GIF Files (*.gif)",
        "Text Files (*.txt)",
        "Python Files (*.py)",
    )
)


class Window(QWidget, Ui_Window):
    def __init__(self):
        super().__init__()
        self._files = deque()
        self._filesCount = len(self._files)
        self._setupUI()
        self._connectSignalsSlots()

    def _setupUI(self):
        self.setupUi(self)
        self._updateStateWhenNoFiles()

    def _updateStateWhenNoFiles(self):
        self._filesCount = len(self._files)
        self.loadFilesButton.setEnabled(True)
        self.loadFilesButton.setFocus(True)
        self.renameFilesButton.setEnabled(False)
        self.prefixEdit.clear()
        self.prefixEdit.setEnabled(False)

    def _connectSignalsSlots(self):
        self.loadFilesButton.clicked.connect(self.loadFiles)
        self.renameFilesButton.clicked.connect(self.renameFiles)
        self.prefixEdit.textChanged.connect(self._updateStateWhenReady)

    def _updateStateWhenReady(self):
        if self.prefixEdit.text():
            self.renameFilesButton.setEnabled(True)
        else:
            self.renameFilesButton.setEnabled(False)

    def loadFiles(self):
        self.dstFileList.clear()
        if self.dirEdit.text():
            initDir = self.dirEdit.text()
        else:
            initDir = str(Path.home())
        files, filter = QFileDialog.getOpenFileNames(
            self, "Choose Files to Rename", initDir, filter=FILTERS
        )
        if len(files) > 0:
            fileExtension = filter[filter.index("*") : -1]
            self.extensionLabel.setText(fileExtension)
            srcDirName = str(Path(files[0]).parent)
            self.dirEdit.setText(srcDirName)
            for file in files:
                self._files.append(Path(file))
                self.srcFileList.addItem(file)
            self._filesCount = len(self._files)
            self._updateStateWhenFilesLoaded()

    def _updateStateWhenFilesLoaded(self):
        self.prefixEdit.setEnabled(True)
        self.prefixEdit.setFocus(True)

    def renameFiles(self):
        self._runRenamerThread()
        self._updateStateWhileRenaming()

    def _updateStateWhileRenaming(self):
        self.loadFilesButton.setEnabled(False)
        self.renameFilesButton.setEnabled(False)

    def _runRenamerThread(self):
        prefix = self.prefixEdit.text()
        self._thread = QThread()
        self._renamer = Renamer(
            files=tuple(self._files),
            prefix=prefix,
        )
        self._renamer.moveToThread(self._thread)
        # Rename
        self._thread.started.connect(self._renamer.renameFiles)
        # Update state
        self._renamer.renamedFile.connect(self._updateStateWhenFileRenamed)
        self._renamer.progressed.connect(self._updateProgressBar)
        self._renamer.finished.connect(self._updateStateWhenNoFiles)
        # Clean up
        self._renamer.finished.connect(self._thread.quit)
        self._renamer.finished.connect(self._renamer.deleteLater)
        self._thread.finished.connect(self._thread.deleteLater)
        # Run the thread
        self._thread.start()

    def _updateStateWhenFileRenamed(self, newFile):
        self._files.popleft()
        self.srcFileList.takeItem(0)
        self.dstFileList.addItem(str(newFile))

    def _updateProgressBar(self, fileNumber):
        progressPercent = int(fileNumber / self._filesCount * 100)
        self.progressBar.setValue(progressPercent)

18.10 파일: source_code_final/rprenamer.py

이 모듈은 RP Renamer 진입점 스크립트를 제공합니다.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# rprenamer.py

"""This module provides the RP Renamer entry point script."""

from rprename.app import main

if __name__ == "__main__":
    main()