Pythonでファイルを整理する

# 作りたいもの
ドキュメントフォルダに大量のファイルが溜まってしまったため、次の機能を持ったプログラムを作成することにしました。
* 仕分け元フォルダ内（サブフォルダを含む）のファイルを更新日時に基づいて、別フォルダに整理する。
* 仕分け先には更新年月ごとのサブフォルダを作成し、ファイルはその中にコピーする。
* 重複するファイル（MD5ハッシュ値が同一のもの）は、ファイル名を変更して保存する。

# Pythonコード
### 1.モジュールのインポートとロケールの設定
```Python
import os
import shutil
from datetime import datetime
import locale
import hashlib
from tkinter import Tk, filedialog

# ロケールを設定
locale.setlocale(locale.LC_ALL, 'ja_JP.UTF-8')
```
* shutilは、タイムスタンプの情報も含めてファイルをコピーするために使用しています。
* localeは、処理全体のロケールを日本語に設定するために使用しています。
* hashlibは、MD5ハッシュ値でファイルを比較するために使用しています。
* tkinterは、仕分け元、仕分け先フォルダをGUIで指定するために使用しています。

:::note info
デフォルトでは英語に従って日付や時刻がフォーマットされるため、処理全体のロケールを日本語にしています。この部分は省略可能です。
:::

### 2.ルートウィンドウの作成と非表示
```Python
# ルートウィンドウを非表示にする
root = Tk()
root.withdraw()
```
Tkinterを使ってダイアログボックスを表示するために、ルートウィンドウを作成して非表示にしています。

### 3.ダイアログボックスで仕分け元・先のフォルダを選択
```Python
# 仕分け元のフォルダをダイアログで選択する
src_folder = filedialog.askdirectory(title="仕分け元のフォルダを選択してください")

# 仕分け先のフォルダをダイアログで選択する
dst_folder = filedialog.askdirectory(title="仕分け先のフォルダを選択してください")
```
コードを実行すると、ダイアログが2回表示されます。1回目では仕分け元フォルダを、2回目は仕分け先フォルダを選択してください。

### 4.日付フォーマットの設定
```Python
# 仕分ける際のファイルの更新日時のフォーマット
date_format = "%Y-%m-%d %H:%M:%S.%f"
```
ファイルの更新日時を処理するためのフォーマットを設定しています。

### 5.仕分け処理
```Python
# 仕分けるフォルダ内のファイルを取得
for root, dirs, files in os.walk(src_folder):
    for file in files:
        # ファイルのパス
        file_path = os.path.join(root, file)
        # MD5ハッシュ値を取得
        with open(file_path, 'rb') as f:
            file_data = f.read()
        file_hash = hashlib.md5(file_data).hexdigest()
        # 仕分け先のフォルダパスを生成
        mtime = os.path.getmtime(file_path)
        mtime_obj = datetime.fromtimestamp(mtime)
        year_month_str = mtime_obj.strftime("%Y年%m月")
        dst_path = os.path.join(dst_folder, year_month_str)
        # 仕分け先のフォルダが存在しなければ作成
        os.makedirs(dst_path, exist_ok=True)
        # 仕分け先に同じハッシュ値のファイルが存在する場合は、ファイル名を変更してコピーする
        dst_file_path = os.path.join(dst_path, file)
        i = 2
        while os.path.exists(dst_file_path):
            new_file_name = os.path.splitext(file)[0] + f" _重複{i}" + os.path.splitext(file)[1]
            dst_file_path = os.path.join(dst_path, new_file_name)
            i += 1
        # ファイルをコピーする
        shutil.copy2(file_path, dst_file_path)
```

1.os.walk() を使って、仕分け元のフォルダ内のすべてのファイルを取得します。
2.各ファイルについて以下の処理を行います。
* ファイルのパスを取得します。
* ファイルのMD5ハッシュ値を計算します。これは、後で重複ファイルを確認する際に使用します。
* ファイルの更新日時（mtime）を取得し、年と月に基づいて仕分け先のフォルダパスを生成します。
* 仕分け先のフォルダが存在しない場合は、新しく作成します。
* 仕分け先に同じハッシュ値のファイルが存在する場合、ファイル名を変更してコピーします。これは、重複するファイルが上書きされないようにするためです。
* 最後にファイルをコピーします。

この処理で、仕分け元のフォルダ内のファイルが、仕分け先のフォルダに更新日時に基づいて整理されます。具体的には、ファイルが更新された年と月に基づいて、仕分け先のフォルダ内にサブフォルダを作成し、ファイルを移動します。例えば、2022年9月に更新されたファイルは、仕分け先フォルダ内の「2022年09月」という名前のサブフォルダに移動されます。

また、重複したファイルの上書きを防ぎ、すべてのファイルを保持するため、ファイル名を変更して保存しています（重複ファイルが見つかった場合、ファイル名に、"_重複" という接尾辞と連番を追加しています）。

### 6.メモ
* このコードを書く時間を使って手動でファイルを整理すれば良かった。
* そもそも少しずつファイルを整理しておけば良かった。
* Pythonのコードを書くのが1年以上ぶりとなったため、一部のモジュールを使った処理についてChatGPT-4に助けてもらいました。便利な世の中になったものです。

以下、連結したコードです。
```Python filesorter.py
import os
import shutil
from datetime import datetime
import locale
import hashlib
from tkinter import Tk, filedialog

# ロケールを設定
locale.setlocale(locale.LC_ALL, 'ja_JP.UTF-8')

# ルートウィンドウを非表示にする
root = Tk()
root.withdraw()

# 仕分け元のフォルダをダイアログで選択する
src_folder = filedialog.askdirectory(title="仕分け元のフォルダを選択してください")

# 仕分け先のフォルダをダイアログで選択する
dst_folder = filedialog.askdirectory(title="仕分け先のフォルダを選択してください")

# 仕分ける際のファイルの更新日時のフォーマット
date_format = "%Y-%m-%d %H:%M:%S.%f"

# 仕分けるフォルダ内のファイルを取得
for root, dirs, files in os.walk(src_folder):
    for file in files:
        # ファイルのパス
        file_path = os.path.join(root, file)
        # MD5ハッシュ値を取得
        with open(file_path, 'rb') as f:
            file_data = f.read()
        file_hash = hashlib.md5(file_data).hexdigest()
        # 仕分け先のフォルダパスを生成
        mtime = os.path.getmtime(file_path)
        mtime_obj = datetime.fromtimestamp(mtime)
        year_month_str = mtime_obj.strftime("%Y年%m月")
        dst_path = os.path.join(dst_folder, year_month_str)
        # 仕分け先のフォルダが存在しなければ作成
        os.makedirs(dst_path, exist_ok=True)
        # 仕分け先に同じハッシュ値のファイルが存在する場合は、ファイル名を変更してコピーする
        dst_file_path = os.path.join(dst_path, file)
        i = 2
        while os.path.exists(dst_file_path):
            new_file_name = os.path.splitext(file)[0] + f" _重複{i}" + os.path.splitext(file)[1]
            dst_file_path = os.path.join(dst_path, new_file_name)
            i += 1
        # ファイルをコピーする
        shutil.copy2(file_path, dst_file_path)
```