Posted at

PyInstaller+PyQtでアイコン対応

More than 1 year has passed since last update.


はじめに

Pythonを使ってちょっとしたGUIアプリを作るときに,よくPyQtで書いてそれをPyInstallerでexeにパッケージングしています.

その時にお気に入りのアプリはアイコンもつけておきたいので,その方法についてまとめます.

対応したいアイコン表示は以下の二通りです.


  • エクスプローラに表示されるexeファイルのアイコン

  • タイトルバーやタスクバーに表示されるアイコン


開発環境

今回確認した環境は以下の通りです.


  • Windows10 Pro

  • Python 3.6.4

  • PyInstaller 3.3.1

  • PyQt5 5.10.1


インストール

PyInstallerとPyQtはpipからインストールができます.

> pip install pyinstaller pyqt5


exeファイルのアイコン

PyInstallerでパッケージングするときにアイコンをつけることができます.

> pyinstaller foo.py --onefile --noconsole --icon=bar.ico

--iconオプションにicoファイルを指定します.

またアイコン表示とは直接関係ありませんが,--onefileオプションでパッケージングするときに1ファイルにまとめるのと,--noconsoleオプションで起動時にコンソール画面を表示しないように指定しています.

foo.png


タイトルバーやタスクバーに表示されるアイコン

こちらはPyQtのコードで対応します.QApplicationのsetWindowIcon()に画像ファイルを渡すとタスクバーにアイコンが表示されます.

from PyQt5.QtGui import QIcon

from PyQt5.QtWidgets import QApplication, QWidget

app = QApplication(sys.argv)
app.setWindowIcon(QIcon('bar.ico')) # pngファイルなどでもOK
w = QWidget()
w.show()
sys.exit(app.exec())

これでPyInstallerで生成したexeファイルと同じパスにbar.icoを置いておくと,タイトルバーやタスクバーにアイコンが表示されます.

ただしこのままでは,exeファイルのほかにicoファイルも同時に配布する必要があるため,もう一工夫します.


画像ファイルの組み込み

PyInstallerでパッケージングするときに,外部ファイルを組み込むことができます.組み込むにはspecファイルに記述します.specファイルは一から記述してもいいですが,上記のようにfoo.pyファイルを指定してPyInstallerを動かしているとfoo.specファイルが生成されています.これに以下の一行を追記すると簡単です.

a = Analysis(['foo.py'],

pathex=['C:\\work\\Python\\foo'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
+a.datas += [('bar.ico','.\\bar.ico', 'Data')]
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name='foo',
debug=False,
strip=False,
upx=True,
runtime_tmpdir=None,
console=False , icon='bar.ico')

このspecファイルを使用してPyInstallerを実行します.

> pyinstaller foo.spec

これでbar.icoファイルが組み込まれてパッケージングされます.


組み込んだファイルの読み込み

PyInstallerでパッケージングしたexeを実行するとTempフォルダに一時ファイルが展開されます.この中に上記で組み込んだ画像ファイルも展開されているので,これを読み込みます.

展開される先のパスはsys._MEIPASSから取得ができます.以下のようなコードを書いておけば,PyInstallerを使用したときと普通にPythonで実行したときとで両対応できます.

import os

import sys

def resource_path(relative):
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative)
return os.path.join(relative)

これを利用して以下のようなコードを書けば,パッケージングされたexeファイルだけでタイトルバーやタスクバーにアイコンを表示することができます.

import os

import sys

from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QWidget

def resource_path(relative):
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative)
return os.path.join(relative)

def main():
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(resource_path('bar.ico')))
w = QWidget()
w.show()
sys.exit(app.exec())

if __name__ == '__main__':
main()

foo.png