Pythonでフォルダの変更を監視・連番リネームするライブラリ
何日か前に作って公開するか悩んでた奴の紹介。
c:
直下でやったらえらい目にあったなどの苦情は懸念されるが、このブログ頭にデカデカ表記している文言とそもそもブログ見に来てるやつそんなにいないから大丈夫を頼りに公開に踏み込む。
機能はタイトル通りフォルダ内の変更を監視・連番リネームができる。
この連番リネームが上記c:
直下など、ソフトを動かすのに必要なファイルまでリネームしてしまう可能性があるので、絶対に自分で変更対象のファイルがどんなものなのか把握しているフォルダでのみ使用するように。
一応、管理者権限のある状態で実行できないようにしているのでc:
直下なんかだとPermissionError
してくれると思うが試す勇気はない!
例えば画像ファイルや、メモ書きなんかしか入っていないフォルダで使うことを想定してのライブラリ。
完成品は既にgitに上がっているので、駆け足で紹介。
実装についてもそこで確認してもらえれば。
コードは完全にWindows
用のもの。
明確にWindows
でしか使えないのは以下の権限確認部分のみなので、そこを消すか別OSの権限確認処理に差し替えれば動く……かも。
バージョン2022.8.12
にアップデートしました。
この更新でリネーム処理が高速化する……はず。
pip install -U git+https://github.com/Otsuhachi/OtsuFolderSerialRenamer.git#egg=otsufolserren
OtsuFolderSerialRenamer │ LICENSE │ Pipfile │ Pipfile.lock │ README.md │ setup.py │ └─otsufolserren │ cfg.py │ funcs.py │ orders.py │ __init__.py │ ├─monitoring │ classes.py │ __init__.py │ └─serial_renamer classes.py __init__.py
otsufolserren
直下にある__init__.py
にある以下のコードが権限確認部分。
import ctypes if ctypes.windll.shell32.IsUserAnAdmin() == 1: msg = f'このライブラリは重要なファイル構成を破壊する恐れがあるため、管理者権限で実行することはできません。' raise PermissionError(msg) del ctypes
余力があればGUI
で使いやすくしたりして、このブログで紹介していきたい。
以下動作確認用のコードの説明。
テキストの中身とファイル名がバラバラ1のテストフォルダを実行ディレクトリと同じフォルダに作成し、そのフォルダのファイルを削除、追加、改名など弄ってからr
, ro
, c
など試したい操作を標準入力することで確認ができる。
今回のテストでは.txt
ファイル以外を想定していないので、注意2。
e
で終了。
import random import shutil import time from pathlib import Path from typing import Iterable from otsufolserren import ORDER_CTIME, FolderSerialRenamer, get_fm from otsutil import setup_path from otsuvalidator import VInt ROOT = Path('otsufolserren_test_dir') CACHE = Path('otsufolserren_test_cache') def create_test(sample_number: int = 10) -> None: """テストフォルダを初期化します。 Args: sample_number (int, optional): テストフォルダ内に生成するファイル数です。 """ if ROOT.exists(): for p in ROOT.iterdir(): if p.is_file(): p.unlink() else: shutil.rmtree(p) sample_number = VInt(0).validate(sample_number) digit = len(str(sample_number)) for i, n in enumerate(random.sample(range(1, sample_number + 1), sample_number)): i += 1 if i > 1: time.sleep(0.3) n = f'{n:0{digit}d}' i = f'{i:0{digit}d}' with open(setup_path(ROOT / f'{n}.txt'), 'w', encoding='utf-8') as f: f.write(f'This file is No.{i}.') def remove_test() -> None: """テストフォルダを除去します。 """ if ROOT.exists(): shutil.rmtree(ROOT) if CACHE.exists(): shutil.rmtree(CACHE) def rfiles() -> Iterable[Path]: """テストフォルダ直下のファイルを返します。 Raises: FileNotFoundError: テストフォルダが存在しない場合に投げられます。 Returns: filter[Path]: テストフォルダ直下のファイルです。 """ if not ROOT.exists(): raise FileNotFoundError(ROOT) return filter(Path.is_file, ROOT.iterdir()) def show_files() -> None: """テストフォルダ内のフォルダを読み込み以下の形式で出力します。 <ファイル名>: <ファイルの内容> """ try: for file in rfiles(): try: with open(file, 'r', encoding='utf-8') as f: line = f.read() except: continue print(f'{file.name}: {line}') except: pass def ask_prompt(question: str, *answer: str) -> str: """質問に対して特定の回答以外なら再回答を促し、回答結果を返します。 Args: question (str): 質問です。 Raises: ValueError: answerが指定されていない場合に投げられます。 Returns: str: answerのいずれかです。 """ if not answer: msg = '1つ以上answerを指定してください。' raise ValueError(msg) answer_ = set(map(lambda x: x.lower(), answer)) while True: ans = input(question).lower() if ans in answer_: return ans def main(): create_test() fm = get_fm(ROOT, 'f', cache_dir=CACHE) help_str = 'c: 更新を確認します。\nr: リネームを実行します。\nro: only_when_changeをFalseにしてリネームを実行します。\nntmp: name_templateを変更します。\nfiles: ファイルとその中のテキストを表示します。\nh: このヘルプを再表示します。\ne: このスクリプトを終了します。' with FolderSerialRenamer(fm, 'td-', ORDER_CTIME) as fsr: print(help_str) while True: cmd = ask_prompt('実行する操作を指定してください。> ', 'c', 'r', 'ro', 'ntmp', 'files', 'h', 'e') if cmd == 'c': if fsr: l, diff = fsr.get_difference() print(f'{l}件の変更を検出しました。') if ask_prompt('表示しますか?(y/n)> ', 'y', 'n') == 'y': for d in diff: print(d) elif cmd in ('r', 'ro'): preview = fsr.get_preview() fsr.rename(only_when_change=cmd == 'r', preview=preview) preview = {x: y for x, y in preview.items() if str(x.resolve()) != str(y.resolve())} for b, a in preview.items(): print(b, '-->', a) elif cmd == 'ntmp': ntmp = input('新しいname_templateを入力してください。> ') fsr.name_template = ntmp elif cmd == 'files': show_files() elif cmd == 'h': print(help_str) elif cmd == 'e': break remove_test() if __name__ == "__main__": main()