背景
visualstudioで開発を進めていて、気づいたらShift-Jisでファイルを作成してしまうことがありました。
いちいちエディタでUTF-8に保存しなおすのが面倒だったので、勉強ついでに、エンコーダーを作ってみました。
いきなり作成したソース
お試しで作成したものなので、本来考慮すべきエラー処理や、クラス設計は一切してません!苦笑
あくまでも参考なので、そのまま使うことはお勧めしません。
import flet as ft
import os
import chardet
def detect_encoding(file_path):
"""ファイルのエンコーディングを判定する。"""
with open(file_path, 'rb') as f:
raw_data = f.read(1024) # 先頭 1024 バイトを読み込む
result = chardet.detect(raw_data)
return result['encoding']
def is_text_file(file_path):
"""ファイルがバイナリかどうかを判定する。
- NULL バイト (\x00) を含む場合はバイナリと判定する。
"""
try:
with open(file_path, 'rb') as f:
chunk = f.read(1024) # 先頭 1024 バイトをチェック
return b'\x00' not in chunk # NULL バイトがなければテキスト
except Exception:
return False # 例外が発生した場合はバイナリ扱い
def convert_encoding(file_path, target_encoding):
"""指定されたエンコーディングにファイルを変換する。"""
original_encoding = detect_encoding(file_path)
if not original_encoding:
return "判定不可", "N/A", "失敗"
try:
with open(file_path, 'r', encoding=original_encoding) as f:
content = f.read()
with open(file_path, 'w', encoding=target_encoding) as f:
f.write(content)
return original_encoding, target_encoding, "成功"
except Exception as e:
return original_encoding, str(e), "失敗"
def main(page: ft.Page):
"""Fletアプリのメイン処理。"""
page.title = "エンコード変換アプリ"
page.window_width = 1200
page.window_height = 800
page.window_resizable = True
page.window_maximizable = True
page.update()
tb_folder_path = ft.TextField(label="フォルダパス", width=500, read_only=True)
btn_select = ft.ElevatedButton("フォルダ選択", on_click=lambda e: file_picker.get_directory_path())
tb_ext_input = ft.TextField(
label="対象の拡張子 (カンマ区切り)",
value="txt,csv,log",
on_change=lambda e: list_files(tb_folder_path.value),
)
cb_select_all = ft.Checkbox(
label="すべて選択",
on_change=lambda e: toggle_all_selection(e.control.value)
)
dd_encoding = ft.Dropdown(
label="変換先エンコード",
options=[
ft.dropdown.Option("utf-8"),
ft.dropdown.Option("utf-16"),
ft.dropdown.Option("shift_jis"),
ft.dropdown.Option("iso-8859-1"),
ft.dropdown.Option("euc-jp"),
],
value="utf-8"
)
btn_convert = ft.ElevatedButton("変換実行", on_click=lambda e: process_files(), disabled=True)
grd_file_table = ft.DataTable(
columns=[
ft.DataColumn(ft.Text("ファイル名")),
ft.DataColumn(ft.Text("予測エンコーディング")),
],
rows=[]
)
grd_result_table = ft.DataTable(
columns=[
ft.DataColumn(ft.Text("ファイル名")),
ft.DataColumn(ft.Text("元エンコード")),
ft.DataColumn(ft.Text("新エンコード")),
ft.DataColumn(ft.Text("結果")),
],
rows=[]
)
file_picker = ft.FilePicker(on_result=lambda e: pick_folder_result(e))
selected_files = {}
def pick_folder_result(e):
"""フォルダ選択時の処理。"""
if e.path:
tb_folder_path.value = e.path
list_files(e.path)
btn_convert.disabled = False
page.update()
def list_files(folder):
"""指定フォルダ内のテキストファイルを一覧表示(バイナリ除外)。"""
if not folder or not os.path.exists(folder):
return
grd_file_table.rows.clear()
selected_files.clear()
if cb_select_all.value: # すべて選択がONなら全ファイルを対象
extensions = None
else:
extensions = [ext.strip() for ext in tb_ext_input.value.split(",")]
for file_name in os.listdir(folder):
file_path = os.path.join(folder, file_name)
if os.path.isfile(file_path) and is_text_file(file_path): # 🔹 バイナリを除外
if extensions is None or any(file_name.endswith(f".{ext}") for ext in extensions):
encoding = detect_encoding(file_path) or "不明"
grd_file_table.rows.append(
ft.DataRow(
cells=[
ft.DataCell(ft.Text(file_name)),
ft.DataCell(ft.Text(encoding)),
]
)
)
page.update()
def toggle_all_selection(select_all):
"""すべて選択・解除 & 拡張子入力欄の有効/無効を切り替え。"""
tb_ext_input.disabled = select_all # すべて選択なら拡張子入力を無効化
list_files(tb_folder_path.value) # ファイル一覧を更新
page.update()
def process_files():
"""選択されたファイルのみエンコード変換。"""
folder = tb_folder_path.value
target_encoding = dd_encoding.value
if not folder or not os.path.exists(folder):
page.snack_bar = ft.SnackBar(ft.Text("フォルダパスが正しくありません"))
page.snack_bar.open = True
page.update()
return
grd_result_table.rows.clear()
for file_name in os.listdir(folder):
file_path = os.path.join(folder, file_name)
if os.path.isfile(file_path) and is_text_file(file_path): # バイナリファイル除外
original_enc, new_enc, result = convert_encoding(file_path, target_encoding)
grd_result_table.rows.append(
ft.DataRow(
cells=[
ft.DataCell(ft.Text(file_name)),
ft.DataCell(ft.Text(original_enc)),
ft.DataCell(ft.Text(new_enc)),
ft.DataCell(ft.Text(result)),
]
)
)
page.update()
page.overlay.append(file_picker)
page.add(
ft.Row([tb_folder_path, btn_select]),
tb_ext_input,
cb_select_all,
ft.Column(controls=[grd_file_table],scroll=ft.ScrollMode.AUTO,expand=True),
dd_encoding,
btn_convert,
ft.Column(controls=[grd_result_table],scroll=ft.ScrollMode.AUTO,expand=True)
)
ft.app(target=main, view=ft.FLET_APP) # Fletアプリを起動
後記
使ったコントロール
今回使用したコントロールは、TextField
、Checkbox
、Column
、Dropdown
、ElevatedButton
、DataTable
です。経験上、大体このあたりのコントロールが使えるようになれば、困らない気がしましたので、勉強してみました。
DataTable単体ではスクロールできないのか・・
意外と苦労したのは、DatTableをスクロールできるようにするところでした。
FletのDataTableをそのまま、使用すると、いい感じに表示してくれていたので、
ビューとしても優秀なのかと思っていたら・・
項目が増えたときに、画面から見切れてしまいました。
どうやら、DataTable単体では、スクロールバーを設定する。という機能はないみたい。
対策として、Columnに、DataTableを入れ子にすることで、解決できています。よかった。
表示画面と使い方
- フォルダ選択ダイアログでフォルダを選択
- 配下のテキストファイルのエンコードを表示
- 実行ボタン押下で、選択したエンコードに変換。
- 以上!