2022/12/5更新:タグファイルの作成対応、WBDirInfo
クラスの作成、設定ファイル及び一部変数名のアンダーライン削減。
動画管理ソフトWhiteBrowserの簡単な操作を提供するGUIアプリケーションです。Python標準ライブラリーtkinter
モジュールの練習で作成しています。おかしなコードが含まれている可能性にご留意ください。
実行前に同一ディレクトリ(作業ディレクトリ)にwb.exeのディレクトリパスを記載したUTF-8の「wbdir.txt」が必要です。
コード
コンソールが不要なので拡張子は「.pyw」にします。
# WhiteBrowserTool.pyw import os import shutil import sqlite3 from tkinter import * from tkinter import messagebox from tkinter.ttk import * from pathlib import Path from contextlib import closing from typing import Iterator def vacuum_sqlite_file(path: Path) -> None: with closing(sqlite3.connect(path)) as connect: with closing(connect.cursor()) as cur: cur.execute("VACUUM") class WBDirInfo: __path: Path def __init__(self, path): self.__path = Path(path) @property def path(self) -> Path: return self.__path @property def thumdir_path(self) -> Path: return self.__path / "thum" @property def tempdir_path(self) -> Path: return self.__path / "temp" def iter_wbfile_path(self) -> Iterator[Path]: return (p for p in self.path.glob("*.wb") if p.is_file()) def iter_thumdir_path(self) -> Iterator[str]: return (p for p in self.thumdir_path.iterdir() if p.is_dir()) def iter_thumdir_name(self) -> Iterator[str]: return (p.name for p in self.iter_thumdir_path()) def iter_tagfile_path(self) -> tuple[Path]: return (p for p in self.tempdir_path.glob("*.tag") if p.is_file()) def iter_tagfile_name(self) -> tuple[str]: return (p.name for p in self.iter_tagfile_path()) def make_tagfile_path(self, name:str) -> Path: return self.tempdir_path / name class MyApp(Tk): __wbdir: WBDirInfo def __init__(self): super().__init__() #外部設定の読み込み try: self.__wbdir = WBDirInfo(Path("wbdir.txt").read_text()) except: messagebox.showerror( "エラー", "同フォルダにWhiteBrowser.exeのディレクトリを記述したwbdir.txtを作成してください。", parent=self) exit() if not self.__wbdir.path.is_dir(): messagebox.showerror( "エラー", "wbdir.txtで指定されたパスがディレクトリではありません。", parent=self) exit() # ウィンドウとウィジェットの準備 self.resizable(True, False) self.title("WhiteBrowserTool") frame1 = Frame(self, padding=16) open_wbdir_button = Button( frame1, text="WhiteBrowserディレクトリを開く...", command=lambda: os.startfile(self.wbdir.path)) delete_thumbnail_button = Button( frame1, text="サムネイルの削除▼", command=self.show_thumbnail_delete_menu) show_tagfile_button = Button( frame1, text="タグファイルを開く▼", command=self.show_tagfile_open_menu) make_tagfile_button = Button( frame1, text="タグファイルの作成▼", command=self.show_tagfile_make_menu) vacuum_db_button = Button( frame1, text="DBの空き領域解放▼", command=self.vacuum_db_menu) vacuum_all_db_button = Button( frame1, text="全DBの空き領域解放", command=self.vacuum_all_db_menu) sep1 = Separator(frame1) exit_button = Button( frame1, text="終了", command=lambda: self.destroy()) button_params = dict(fill="x", ipadx=5, expand=True, anchor="n") open_wbdir_button.pack(**button_params) delete_thumbnail_button.pack(**button_params) show_tagfile_button.pack(**button_params) make_tagfile_button.pack(**button_params) vacuum_db_button.pack(**button_params) vacuum_all_db_button.pack(**button_params) sep1.pack(fill="x", ipadx=5, expand=True, anchor="center") exit_button.pack(**button_params) frame1.pack(fill="both", expand=True) @property def wbdir(self) -> WBDirInfo: return self.__wbdir #「サムネイルの削除」 def show_thumbnail_delete_menu(self): menu = Menu(self, tearoff=False) for p in self.wbdir.iter_thumdir_path(): menu.add_command( label=p.name, command=lambda p=p: shutil.rmtree(p)) menu.post(self.winfo_pointerx(), self.winfo_pointery()) #「タグファイルを開く▼」 def show_tagfile_open_menu(self): menu = Menu(self, tearoff=False) for p in self.wbdir.iter_tagfile_path(): menu.add_command( label=p.name, command=lambda p=p: os.startfile(p)) menu.post(self.winfo_pointerx(), self.winfo_pointery()) #「タグファイルの作成▼」 def show_tagfile_make_menu(self): #タグファイルの存在しないサムネイルディレクトリ名の抽出 tempdir_names = set(self.wbdir.iter_thumdir_name()) #TODO:正規表現への置き換え tagfile_names = set((s.lower().rstrip(".tag") for s in self.wbdir.iter_tagfile_name())) menu = Menu(self, tearoff=False) for name in (tempdir_names - tagfile_names): path = self.wbdir.make_tagfile_path(name + ".tag") menu.add_command( label=name, command=lambda p=path: self.open_and_create_tagfile(p)) menu.post(self.winfo_pointerx(), self.winfo_pointery()) def open_and_create_tagfile(p: Path): p.write_text( "[評価]\n★★★★★\n★★★★\n★★★\n★★\n★\n\n", "shift-jis") os.startfile(p) #「DBの空き領域解放▼」 def vacuum_and_report_wb_file(self, path: Path) -> None: prev_size = os.path.getsize(path) vacuum_sqlite_file(path) messagebox.showinfo( "報告", f"{path.name}のサイズ増減:{os.path.getsize(path) - prev_size}バイト", parent=self) def vacuum_db_menu(self): menu = Menu(self, tearoff=False) for p in self.wbdir.iter_wbfile_path(): menu.add_command( label=p.name, command=lambda p=p: self.vacuum_and_report_wb_file(p)) menu.post(self.winfo_pointerx(), self.winfo_pointery()) #「全DBの空き領域解放」 def vacuum_all_db_menu(self): ret = messagebox.askquestion( "確認", "DBのバキュームには時間がかかる場合があります。実行しますか?", parent=self) if ret != "yes": return size_diff = 0 for p in self.wbdir.iter_wbfile_path(): prev_size = os.path.getsize(p) vacuum_sqlite_file(p) size_diff += prev_size - os.path.getsize(p) messagebox.showinfo( "報告", f"サイズ:-{size_diff}バイト") MyApp().mainloop()
メモ
for
を使ってcommand
付きのメニュー項目を動的に作成するような場合、値束縛に注意が必要です。詳しくは以下の方のページが分かりやすかったです。このコードではラムダ式で引数の既定値を指定する方法を採用しています。