ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【python/tkinter/pyinstaller】pyinstallerでのexe化時にGUIとexeファイルのicon画像を設定する

 以前の記事でpyinstallerを使ってexe化を行いました。

 今回はexe化する際にアイコン画像も含める方法についての備忘録です。


 では、始めます。


0:デフォルト状態の場合
 バージョンなど環境によって変わるかもしれませんが、特にアイコン画像を指定せずにpyinstallerを使ってexe化した場合は以下のようになっています。
f:id:rikoubou:20220121153043p:plain

 機能としては問題ないですが、デフォルトのままだと色々と困る場合が多いと思うので実際にアイコンを作成しつつその画像を設定していきます。


1:アイコン画像の準備
 まずは設定したいアイコン画像を作成します。適当なペイントソフトで正方形の適当な画像を作成します。

 今回は例として以下のような100×100pxのpng画像を作りました。必要であればダウンロードして使ってください。
f:id:rikoubou:20220121154344p:plain

 アイコン画像の拡張子は「.ico」なので変換する必要がありますが、以下の便利な「Faviconジェネレーター」様があるので利用させていただきました。

 使い方は書いてある通りですが、以下のような手順でアイコンを作成してダウンロードします。
f:id:rikoubou:20220121174430p:plain

 これでアイコン画像が準備できました。

 以降このアイコン画像は「icon.ico」というファイル名という前提で進めます。


2:GUIのアイコン画像を設定する
 GUIのアイコン設定はpython側で設定します。

 今回はアイコン画像を以下のようにexe化したいpythonファイルと同じ階層に配置したという前提で進めます。
f:id:rikoubou:20220121160344p:plain

 この場合のGUIのアイコン設定の方法は以下のように「root.iconbitmap」で指定します。

import tkinter as tk

root = tk.Tk()
root.iconbitmap(default='icon.ico')

 この時設定するアイコン画像のファイルパスですが「pythonを実行している階層からのパス」になっているため注意が必要です。
 なので絶対パスを指定するか、相対パスの場合は実行している階層を気にする必要があります。

 またさらに厄介なのがpyinstallerによってexe化した場合、この「pythonを実行している階層」が不確定になります。
 というのも、詳しくは参考ページ様を参照していただきたいのですが、pyinstallerでexe化したファイルを実行した際、一度その中身が「一時フォルダ」に展開されてそこでpythonが実行されるという形になっています。一時フォルダは毎回exeが起動される度に違う名前で作成されるため、固定のパスというわけではありません。

 つまりpythonが実行される階層やexe化されることを考えるとこのままの記述ではまずいので、以下のように「get_icon_path」関数からアイコン画像のパスを取得するようにします。

import sys, os

# アイコンファイルの絶対パスを取得する関数
def get_icon_path(relative_path):
    try:
        # 一時フォルダのパスを取得
        base_path = sys._MEIPASS
    except Exception:
        # 一時フォルダパスを取得できない場合は実行階層パスを取得
        base_path = os.path.abspath(os.path.dirname(sys.argv[0]))
    
    # アイコンファイルの絶対パスを作成
    return os.path.join(base_path, relative_path)

 これでexe化したものを実行した際は一時フォルダに展開された中からアイコン画像のパスを作成し、pythonファイルを直接実行する際は実行階層からアイコン画像のパスを作成するという形になります。
 この方法はexe化した中にアイコン画像を含める必要があるので、それはpyinstallerでの実行時にオプションとして設定します。

 ちなみにこの関数を追加して修正したソースコードは以下のようになります。

tkinter_sample.py

#-*- coding:utf-8 -*-
import sys, os
import tkinter as tk
import tkinter.ttk as ttk
from functools import partial


def main():
    # rootの作成
    root = tk.Tk()
    root.title("tkinter_sample")
    root.geometry("300x100")
    root.iconbitmap(default=get_icon_path('icon.ico'))

    # frameの作成
    frame = ttk.Frame(root, padding=10)
    frame.grid(row=2, column=0)

    # ラベルの作成
    label = ttk.Label(frame, text="ここに入力したテキストの内容が表示されます")
    label.grid(row=0, column=0)

    # Entry(1行のテキスト入力行)を作成してframeに追加
    entry = ttk.Entry(frame, width=40)
    entry.grid(row=1, column=0, pady=4)

    # Buttonを作成してframeに追加
    button = ttk.Button(frame, text="表示", command=partial(update_label, entry, label))
    button.grid(row=2, column=0, pady=4)

    # 中央揃え
    root.grid_columnconfigure(0, weight=1)
    root.grid_rowconfigure(0, weight=1)

    root.mainloop()


# ラベルを入力されたテキストの内容で更新
def update_label(entry, label):
    label['text'] = entry.get()


# アイコンファイルの絶対パスを取得する関数
def get_icon_path(relative_path):
    try:
        # 一時フォルダのパスを取得
        base_path = sys._MEIPASS
    except Exception:
        # 一時フォルダパスを取得できない場合は実行階層パスを取得
        base_path = os.path.abspath(os.path.dirname(sys.argv[0]))

    # アイコンファイルの絶対パスを作成
    return os.path.join(base_path, relative_path)


if __name__ == "__main__":
    main()


3:exeのアイコン画像の設定と画像ファイルをexeに含める
 exeのアイコン画像を設定するには「--icon」オプションを使います。以前の記事で実行したpyinstallerのコマンドに追記すると以下のようになります。

$ pyinstaller tkinter_sample.py --icon=icon.ico --name tkinter_sample.exe --onefile --noconsole

 上記のように「--icon」とファイルパスを指定するだけでexeファイル自体のアイコンが設定できます。

 また2で記述したGUIで設定するアイコン画像を含めるために、以下のようにさらに「--add-data」オプションを加えます。

$ pyinstaller tkinter_sample.py --add-data "icon.ico;./" --icon=icon.ico --name tkinter_sample.exe --onefile --noconsole

 この「--add-data」オプションを記述することでexeファイルに画像などを含めることができます。

 以下オプションの書き方の簡単なまとめです。

書き方 内容
-i [FILE], --icon [FILE] exeのアイコン画像を設定
--add-data [FILE文字列] exeに含めるバイナリ以外のファイルやフォルダを設定


4:実際に実行してみる
 では実際に2で記述した修正したソースコードの実行とexe化コマンドを実行して確認してみます。

 以下のようにpythonファイルと同じ階層にicon.icoを配置して実行してみます。
f:id:rikoubou:20220121171417p:plain

 実行すると以下のようにGUIにちゃんと画像が設定されています。
f:id:rikoubou:20220121171539p:plain

 では次に以下のコマンドでexe化してみます。

$ pyinstaller tkinter_sample.py --add-data "icon.ico;./" --icon=icon.ico --name tkinter_sample.exe --onefile --noconsole

 実行すると以下のようにexeアイコンが設定され、実行時のGUIにもアイコンが設定されています。
f:id:rikoubou:20220121173257p:plain
f:id:rikoubou:20220121173406p:plain

 実際に一時フォルダ内を確認してみると、ちゃんとicon画像が含まれています。
f:id:rikoubou:20220121173800p:plain


 以上がpyinstallerでのexe化時にGUIとexeファイルのicon画像を設定する方法になります。

 色々とややこしかったりしますが、一度記述してコマンドを確定してしまえばあとは特に変更することがないので最初少し大変なだけだと思います。

 また別の方法としてアイコン画像をバイナリ文字列の定数としてソースコード内に埋め込むことも考えられますが、画像を変更する度にバイナリ文字列も変更しないといけないのは面倒です。
 なので今回記事にした方法であればファイルを変更するだけで良いので、これが一番楽なのかなと思います。


・参考資料