pyinstallerで.pyファイルのexe化、エラー攻略(OSError, tkinter.TclError) Python

スポンサーリンク
Python/DeepLearning

こんにちは!みやしんです。

今回はPythonファイルをexe化して簡単にプログラムを実行する方法をご紹介します。

題材としましては、YouTube動画のダウンロードアプリをexe化していきます。

YouTubeダウンロードアプリに関しては詳しく知りたい方は、こちらの記事をご参照ください。

では、始めていきましょう!

みやしん
みやしん

いちいち.pyファイルを実行するのは面倒ですよね!

あとPython環境のない人に使って貰えないデメリットもあります。

exe化しておけばファイルをダブルクリックするだけ。

Windowsのパソコンさえあれば誰でも実行できるからとっても便利です。

スポンサーリンク

この記事で出来ること

exe化とは

exe化するとexeファイルを作成します。exeファイルとは、Windows環境でのファイルの拡張子の1つ。実行形式のファイルです。.pyファイルをexe化しておくことでダブルクリックのみで実行可能となります。Python環境も必要ありません。

Pyinstallerのインストール

Pythonファイルのexe化にはPyinstallerというライブラリを使います。

まずは、Pyinstallerをpipでインストールします。

pip install pyinstaller

Anacondaの場合は、condaコマンドでインストールします。

conda install pyinstaller

これで準備は完了です。

サンプルコード (YouTubeダウンロードアプリ)

exe化した時のエラー対策も織り込んでいます。

import youtube_dl
import tkinter as tk
from PIL import Image, ImageTk

import sys
import os

# アクセスしたいファイル名(同フォルダ内)を引数に渡すと絶対パスを返す
def find_data_file(filename):
   if getattr(sys, "frozen", False):
       # The application is frozen
       datadir = os.path.dirname(sys.executable)
   else:
       # The application is not frozen
       # Change this bit to match where you store your data files:
       datadir = os.path.dirname(__file__)
   return os.path.join(datadir, filename)


# ダウンロードが完了したことをお知らせするポップアップ
def popup_finish():
    # 画面の基本設定
    root_popup_finish = tk.Toplevel() # GUI画面を最前面に生成
    root_popup_finish.geometry("300x100") # 画面の大きさを設定。真ん中の×印は小文字のエックス
    root_popup_finish.title("完了") # 画面のタイトルを設定

    # 作成するexeファイルがあるフォルダpathを取得
    abs_path_current_file = find_data_file("icon_youtube_downloader.png")

    # アイコンを設定
    root_popup_finish.iconphoto(True, tk.PhotoImage(file=abs_path_current_file)) # Falseにするとアイコン画像が現在の特定のウィンドウにのみ適用となってしまう。アイコン画像はicon_youtube_downloader.png。

    # ラベルを設定
    l_popup_finish = tk.Label(
        root_popup_finish,
        font=("", 12), # フォントはデフォルト("")、フォント大きさは12
        text = "ダウンロードが完了しました"
    )
    l_popup_finish.place(x=1, y=1) # ラベルの表示座標

    root_popup_finish.mainloop() # 画面を表示


# ダウンロードが完了出来なかったことをお知らせするポップアップ
def popup_not_finish(sentence):
    # 画面の基本設定
    root_popup_not_finish = tk.Toplevel() # GUI画面を最前面に生成
    root_popup_not_finish.geometry("300x100") # 画面の大きさを設定。真ん中の×印は小文字のエックス
    root_popup_not_finish.title("エラー") # 画面のタイトルを設定

    abs_path_current_file = find_data_file("icon_youtube_downloader.png")

    # アイコンを設定
    root_popup_not_finish.iconphoto(True, tk.PhotoImage(file=abs_path_current_file)) # Falseにするとアイコン画像が現在の特定のウィンドウにのみ適用となってしまう。アイコン画像はicon_youtube_downloader.png。

    # ラベルを追加
    l_popup_not_finish = tk.Label(
        root_popup_not_finish,
        font=("", 12),
        text = str(sentence)
    )
    l_popup_not_finish.place(x=1, y=1)

    root_popup_not_finish.mainloop() # 画面を表示


# youtubeから動画をダウンロードする関数
def youtube_download(url_movie):
    try:
        ydl_opts = {}
        with youtube_dl.YoutubeDL(ydl_opts) as ydl:
            ydl.download([url_movie])
        popup_finish()
    except:
        popup_not_finish("ダウンロードできませんでした")      
    

# ツールを起動する関数
def start_tool():
    # 画面の基本設定
    root = tk.Tk() # GUI画面を生成
    root.geometry("700x500") # 画面の大きさを設定。真ん中の×印は小文字のエックス
    root.configure(bg='white') # 背景を白にする
    root.title("YouTube Douwnloader") # 画面のタイトルを設定

    abs_path_current_file = find_data_file("icon_youtube_downloader.png")

    # アイコンを設定
    root.iconphoto(True, tk.PhotoImage(file=abs_path_current_file)) # Falseにするとアイコン画像が現在の特定のウィンドウにのみ適用となってしまう。アイコン画像はicon_youtube_downloader.png。

    # タイトル画像表示
    canvas = tk.Canvas(root, bg="white", height=200, width=440, highlightthickness=0) # 画像を載せるためのキャンバスを画面に設定。highlightthickness=0で枠を消す
    canvas.place(x=130, y=60) # キャンバスの設置位置

    abs_path_current_file = find_data_file("title.png")

    img = Image.open(abs_path_current_file) # 画像読込
    img = img.resize((440, 200)) # 画像をキャンバスと同じ大きさにリサイズ
    img = ImageTk.PhotoImage(img) # 画像読込
    canvas.create_image(0, 0, image=img, anchor=tk.NW) # キャンバス上のx座標=0,y座標=0の位置に表示。
    # Tk.CENTER、Tk.W (左よせ)、Tk.E (右よせ)、Tk.N (上よせ)、Tk.S (下よせ)、 Tk.NW (左上)、Tk.SW (左下)、Tk.NE (右上)、Tk.SE (右下)

    # ラベルを設定
    l1 = tk.Label(
        root,
        font=("", 12), # 文字フォントは""で指定しない。フォントの大きさは12。
        bg="white", #背景を白にする(デフォルトはグレー)
        text="ダウンロードしたいYouTube動画のURLを入力してDownloadボタンを押してください" # ラベルとして表示するテキスト
    )
    l1.place(x=30, y=270) # ラベルの位置を指定

    l2 = tk.Label(
        root,
        font=("", 12),
        bg="white",
        text = "URL"
    )
    l2.place(x=30, y=300)

    # エントリーを設定(YouTube動画のURLを入れるところ)
    E = tk.Entry(
        root,
        font=("", 12),
        bg="lightgrey",
        width = 74
    )
    E.place(x=50, y=320 )

    # downloadボタンを設定
    B = tk.Button(
        root,
        text="Download", # ボタンに表示するテキスト
        fg="white", # テキストの色を指定
        bg="dimgrey", # ボタン背景の色を指定
        font=("", 20, "bold"), # フォントサイズを指定。フォント自体は""で指定しない。
        command=lambda:youtube_download(E.get()) # ボタンを押したときに実行される関数、#lambda型で書かないとなぜか上手く動かない。.get()でEntry内に入力された内容を取得
    ) 
    B.place(x=270, y=380) # ボタン位置調整

    root.mainloop() # 画面を表示する

if __name__ == '__main__':
    start_tool()

画像ファイル

使用している画像ファイルを添付しておきます。

title.png

icon_youtube_downloader.png

アイコン作成用画像

icon用の画像作成

アイコンの作成には下記のサイトがとっても便利です!

アイコン コンバータ

403 Forbidden

アイコンの作り方

①アイコン化したい画像ファイルを選択します。

②アイコン画像の解像度を選択します。今回は48×48で実施しています。

exe化実施

フォルダ構成

作成したアイコンファイルは「icon.ico」のファイル名にしています。拡張子は.icoです。

コマンド実行

以下のコマンドでpyinstallerを実行します。

pyinstaller --onefile --noconsole --icon=toolicon.ico youtube_downloader.py

オプションについては下記の表のようになります。

オプション内容
onefileファイルを1つにまとめます。基本的には指定します。
noconsole実行時にコンソールの表示を抑制します。tkinter等を使用したGUIの場合は指定します。
debug allデバッグ出力します。exe化に失敗したときなどの調査で使用します。
cleanキャッシュを削除します。納品時は指定するようにします。
iconアイコンファイルのパスを指定します。
nameexeファイル名を指定します。

実行するとフォルダやファイルが増えます。

distフォルダの中にexe化されたファイルが出力されます。

続いて、このdistフォルダにyoutube_downloader.exeの画面で使用している画像を入れておきます。

あとは、youtube_downloader.exeをダブルクリックするとツールが起動します!

しっかりツールを起動できるようになるまでに、2つ大きなエラーがありましたので、参考に攻略方法を載せておきます。

エラー攻略方法

OSError: Cannot load native module ‘Crypto.Cipher._raw_ecb’: Not found ‘_raw_ecb.cp38-win_amd64.pyd’, Not found ‘_raw_ecb.pyd’

このエラーは下記内容を実施して解決できました。

① Copy Crypto FolderCryptodomePythonのディレクトリで名前を変更します../site-packages

② ファイル./site-packages/Cryptodome/Util/_raw_api.pyを開き、インポートの名前を「Crypto」から「Cryptodome」に変更します。

①検索画面を出して(画面上のWindowsマークを押す)、_raw_api.pyを検索します。

②の部分をクリックして_raw_api.pyが入っているフォルダへ飛びます。(_raw_api.pyoとなっていますが、同じフォルダに_raw_api.pyも入っているので大丈夫です)

③フォルダ構成が「\site-packages\Crypto\Util」となっているので「\site-packages\Cryptodome\Util」に修正します。

④ファイル./site-packages/Cryptodome/Util/_raw_api.pyを開き、インポートの名前を「Crypto」から「Cryptodome」に変更します。

これでこのエラーは直りました!

tkinter.TclError: couldn’t open “〇〇〇”: no such file or directory

こちらはファイルパスのエラーです。

exe化したアプリケーションを実行すると、まずスクリプトの実体をTemporaryフォルダにコピーした後にスクリプトを実行する、という動きになります。Temporaryフォルダのパスは実行するたび変わるのでエラーになってしまうのです。

今回、スクリプトに下記のような記述をしてこのエラーを回避しています。

# アクセスしたいファイル名(同フォルダ内)を引数に渡すと絶対パスを返す
def find_data_file(filename):
   if getattr(sys, "frozen", False):
       # The application is frozen
       datadir = os.path.dirname(sys.executable)
   else:
       # The application is not frozen
       # Change this bit to match where you store your data files:
       datadir = os.path.dirname(__file__)
   return os.path.join(datadir, filename)

このコードでは、任意のファイルにアクセスしたい場合に、exeファイルからの相対パスを引数として、絶対パスを返してくれる関数です。
exeファイルが実行された場合はif文のコードが適応され、Pythonのまま実行した場合はelse以降が適応されます。

Python/DeepLearning
スポンサーリンク
記事が良かったらSNSでシェアを宜しくお願いします!
みやしんをフォローすると役立つ情報がいっぱいにゃ!
スポンサーリンク
みやしんのプログラミングスキル通信

コメント

タイトルとURLをコピーしました