こんにちは、タナカです。
この記事では、PyQt5でrenameアプリを作る方法を説明します。
皆さんは、普段大量の画像を扱っていて画像のファイル名を一括で変更したいときはないでしょうか。
私は、機械学習や画像処理を専門として仕事をしているのですが、取得した画像のファイル名にある特定の文字を追加したり、特定の文字を除いたり、連番にしたりすることが多いです。
元々はプログラムを書いて行っていましたが、もっと直感的に操作したり、汎用的に使えるものが欲しいと考え、renameアプリを作りました。
- PyQt5で簡易renameアプリを作ったので紹介します
1. renameアプリのイメージ
アプリのイメージはこのようになります。あくまで個人用に作っているので、完ぺきではない点だけご了承ください。

2. renameアプリの機能
今回作成したrenameアプリは以下のことを実行することができます。
- ファイル名の前後に特定の文字をつけることができる
- ファイル名の前後に連番(1~)をつけることができる
- ファイル名を一から決めることができる(そのとき連番がつく)
- ファイル名から特定の文字を削除することができる
2-1. ファイル名の前後に特定の文字をつける
file selectからファイル名を変更したいファイルを選択します。
付け加えたい文字を入力し、前 or 後のチェックボックスを選択します。
reviewをクリックすることで変更後のプレビューを見ることができます。
変更後の名前が良ければ、runをクリックして実行します。

試しに「test_」と前につくようにrenameしてみました。
後ろにもつけることが可能です。
2-2. ファイル名の前後に連番(1~)をつける
file selectからファイル名を変更したいファイルを選択します。
付け加えたい文字を入力し、前 or 後のチェックボックスを選択します。
連番のチェックボックスをクリックします。
reviewをクリックすることで変更後のプレビューを見ることができます。
変更後の名前が良ければ、runをクリックして実行します。

試しに「_test_」と入力し、後ろに連番で付け加えてみました。
2-3. ファイル名を一から決める
file selectからファイル名を変更したいファイルを選択します。
変更したい文字を入力します。
all_changeのチェックボックスをクリックします。
reviewをクリックすることで変更後のプレビューを見ることができます。(そのとき自動的に連番がつきます)
変更後の名前が良ければ、runをクリックして実行します。

この例ではわかりにくいですが、「0」という文字に連番がつくようにしています。
2-4. ファイル名から特定の文字を削除する
file selectからファイル名を変更したいファイルを選択します。
削除したい文字を入力します。
deleteのチェックボックスをクリックします。
reviewをクリックすることで変更後のプレビューを見ることができます。
変更後の名前が良ければ、runをクリックして実行します。

試しに「0」という文字を削除してみました。
3. ソースコード
今回作成した全ソースコードを載せておきます。
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout, QLabel, QLineEdit, QPushButton, QComboBox, QMessageBox, QCheckBox, QSizePolicy
from PyQt5 import QtGui
from PyQt5 import QtCore
from PyQt5 import QtWidgets
# 標準モジュール
import sys
import os
import re
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
# 初期値
self.dirname = os.path.dirname(os.path.abspath(__file__))
self.font = QtGui.QFont('Arial', 10)
self.files = None
self.new_file_flag = False
self.old_file_flag = False
self.run_count = 0
self.undo_able_flag = False
self.top_flag = True
self.bottom_flag = False
self.serialNumber_flag = False
self.all_change_flag = False
self.delete_flag = False
self.change_name = None
self.counter = None
self.p = 0
self.setGeometry(100, 100, 900, 480)
# レイアウト
self.grid = QGridLayout()
self.setLayout(self.grid)
self.Hbox = QtWidgets.QHBoxLayout()
self.grid.addLayout(self.Hbox, 1, 0)
# widgetsの配置
self.createWidgets()
# checkAction
self.topCheckbox.stateChanged.connect(self.topCheckBoxChangedAction)
self.bottomCheckbox.stateChanged.connect(self.bottomCheckBoxChangedAction)
self.serialNumberCheckbox.stateChanged.connect(self.serialNumberCheckBoxChangedAction)
self.allChangeCheckbox.stateChanged.connect(self.allChangeCheckBoxChangedAction)
self.deleteCheckbox.stateChanged.connect(self.deleteCheckBoxChangedAction)
def selectFiles(self):
self.run_count = 0
self.undo_able_flag = False
# renameしたいファイルを選択
self.files = QtWidgets.QFileDialog.getOpenFileNames(self, 'select files')[0]
self.counter = len(self.files)
# ファイル情報リスト
self.file_list = [FileInfo() for i in range(self.counter)]
# ラベル情報リスト
self.label_list = [LabelInfo() for i in range(self.counter)]
# if counter > 10:
# for i in range(10):
# base_name = os.path.basename(self.files[i])
# self.label_list[i].label.setText(base_name)
# self.grid.addWidget(self.label_list[i].label)
# if i == 9:
# self.grid.addWidget(QLabel(':'))
# else:
self.table.setRowCount(self.counter)
for i in range(self.counter):
self.base_name = os.path.basename(self.files[i])
# self.label_list[i].label.setText(base_name)
# self.grid.addWidget(self.label_list[i].label)
# old file nameを保持
self.file_list[i].old_file_name = self.files[i]
self.table.setItem(i, 0, QtWidgets.QTableWidgetItem(str(self.base_name)))
self.old_file_flag = True
def review(self):
if self.counter:
if self.addText.text() != '':
for i in range(self.counter):
self.base_name = os.path.basename(self.files[i])
self.dir_name = os.path.dirname(self.files[i])
# top_flagがTrueの場合
if self.top_flag:
self.change_name = os.path.join(self.dir_name, self.addText.text() + self.base_name)
self.display_name = self.addText.text() + self.base_name
# serialNumber_flagがTrueの場合
if self.serialNumber_flag:
self.change_name = os.path.join(self.dir_name, self.addText.text() + str(i + 1) + self.base_name)
self.display_name = self.addText.text() + str(i + 1) + self.base_name
# all_change_flagがTrueの場合
if self.all_change_flag:
base_name, ext = os.path.splitext(self.base_name)
self.change_name = os.path.join(self.dir_name, str(i + 1) + '_' + self.addText.text() + ext)
self.display_name = str(i + 1) + '_' + self.addText.text() + ext
# bottom_flagがTrueの場合
if self.bottom_flag:
base_name, ext = os.path.splitext(self.base_name)
self.change_name = os.path.join(self.dir_name, base_name + self.addText.text()) + ext
self.display_name = base_name + self.addText.text() + ext
# serialNumber_flagがTrueの場合
if self.serialNumber_flag:
self.change_name = os.path.join(self.dir_name, base_name + self.addText.text() + str(i + 1))
self.display_name = base_name + self.addText.text() + str(i + 1) + ext
# all_change_flagがTrueの場合
if self.all_change_flag:
base_name, ext = os.path.splitext(self.base_name)
self.change_name = os.path.join(self.dir_name, self.addText.text() + str(i + 1) + '_' + ext)
self.display_name = self.addText.text() + '_' + str(i + 1) + ext
# delete_flagがTrueの場合
if self.delete_flag:
base_name, ext = os.path.splitext(self.base_name)
self.change_name = os.path.join(self.dir_name, re.sub(self.addText.text(), '', base_name) + ext)
self.display_name = re.sub(self.addText.text(), '', base_name) + ext
# new file nameを保持
self.file_list[i].new_file_name = self.change_name
self.table.setItem(i, 1, QtWidgets.QTableWidgetItem(str(self.display_name)))
self.new_file_flag = True
else:
for i in range(self.counter):
self.base_name = os.path.basename(self.files[i])
self.table.setItem(i, 1, QtWidgets.QTableWidgetItem(str(self.base_name)))
self.new_file_flag = False
def run(self):
if self.new_file_flag and self.old_file_flag:
for i in range(self.counter):
try:
os.rename(self.file_list[i].old_file_name, self.file_list[i].new_file_name)
except Exception as ex:
print('error')
self.undo_able_flag = True
self.run_count += 1
print(self.run_count)
def undo(self):
if self.undo_able_flag:
if self.change_name:
for i in range(self.counter):
os.rename(self.file_list[i].new_file_name, self.file_list[i].old_file_name)
self.undo_able_flag = False
self.run_count = 1
# top_check
def topCheckBoxChangedAction(self, state):
if (QtCore.Qt.Checked == state):
self.top_flag = True
self.bottom_flag = False
self.bottomCheckbox.setChecked(False)
else:
self.top_flag = False
self.bottom_flag = True
self.bottomCheckbox.setChecked(True)
# bottom_check
def bottomCheckBoxChangedAction(self, state):
if (QtCore.Qt.Checked == state):
self.top_flag = False
self.bottom_flag = True
self.topCheckbox.setChecked(False)
else:
self.top_flag = True
self.bottom_flag = False
self.topCheckbox.setChecked(True)
# serial_check
def serialNumberCheckBoxChangedAction(self, state):
if (QtCore.Qt.Checked == state):
self.serialNumber_flag = True
else:
self.serialNumber_flag = False
# all_change_check
def allChangeCheckBoxChangedAction(self, state):
if (QtCore.Qt.Checked == state):
self.all_change_flag = True
else:
self.all_change_flag = False
# delete_check
def deleteCheckBoxChangedAction(self, state):
if (QtCore.Qt.Checked == state):
self.delete_flag = True
else:
self.delete_flag = False
def createWidgets(self):
self.getFile_button = QPushButton('file select')
self.getFile_button.setFont(self.font)
self.Hbox.addWidget(self.getFile_button)
self.getFile_button.clicked.connect(self.selectFiles)
self.addText = QLineEdit(self)
self.Hbox.addWidget(self.addText)
self.topCheckbox = QCheckBox("前")
self.Hbox.addWidget(self.topCheckbox)
self.topCheckbox.setChecked(True)
self.bottomCheckbox = QCheckBox("後")
self.Hbox.addWidget(self.bottomCheckbox)
self.serialNumberCheckbox = QCheckBox("連番")
self.Hbox.addWidget(self.serialNumberCheckbox)
self.allChangeCheckbox = QCheckBox("all_change")
self.Hbox.addWidget(self.allChangeCheckbox)
self.deleteCheckbox = QCheckBox("delete")
self.Hbox.addWidget(self.deleteCheckbox)
self.review_button = QPushButton('review')
self.review_button.setFont(self.font)
self.Hbox.addWidget(self.review_button)
self.review_button.clicked.connect(self.review)
self.run_button = QPushButton('run')
self.run_button.setFont(self.font)
self.Hbox.addWidget(self.run_button)
self.run_button.clicked.connect(self.run)
self.table = QtWidgets.QTableWidget()
self.table.setColumnCount(2)
self.table.setHorizontalHeaderLabels(['before rename', 'after rename'])
self.table.setRowCount(10)
self.grid.addWidget(self.table)
self.table.horizontalHeader().setDefaultSectionSize(400)
self.undo_button = QPushButton('undo')
self.undo_button.setFont(self.font)
self.Hbox.addWidget(self.undo_button)
self.undo_button.clicked.connect(self.undo)
class FileInfo():
'''
FileInfoクラス
'''
def __init__(self):
# old file name
self.old_file_name = None
# new file name
self.new_file_name = None
class LabelInfo():
'''
LabelInfoクラス
'''
def __init__(self):
# ラベル
self.label = QLabel()
# フォント
self.label.setFont(QtGui.QFont('Arial', 10))
def main():
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
main()
まとめ
作業効率化アイテムの1つとして、renameアプリを作ってみました。
今の仕事で画像をかなり使うので、こういう名称を付け加えたり、連番でファイル名を変更するアプリはかなり使いどころがあります。
興味がある方は、是非一度使ってみてください。
また、もっとこうしたほうがいいよとか機能の追加などのアイディアをいただけると幸いです。