前提
初めて質問させていただきます。
excelファイルのシート名を指定して、フォルダ内の該当シートのみexcel→PDF化、
PDF→Jpeg化の変換ツールを作成しています。
実現したいこと
指定したフォルダ内のエクセルファイルを読み込んで
指定したシート名を持つシートのみ、excel→PDF、PDF→Jpegに変換したい。
発生している問題・エラーメッセージ
エラーメッセージなし。
JupyterNoteやAnacondaでは正常に動作していましたが
Exe化するとPDF→Jpeg化が動かなくなった。
※Jpeg化するにあたり、Popplerを環境変数に設定して実行しています。
該当のソースコード
from pathlib import Path from pdf2image import convert_from_path import os import win32com.client import datetime import os import tkinter as tk from tkinter import * from tkinter import ttk from tkinter import messagebox from tkinter import filedialog import openpyxl as xl #Excel to PDF def excel_PDF(): path = path_excel.get() path_pdf = path_p.get() # コンボボックス内の値の取得 #def select_combo(event): #global sheetA #sheet = combobox.get() #print(sheet) excel = win32com.client.Dispatch("Excel.Application") excel.Visible=True files = [] now = datetime.datetime.now() for filename in os.listdir(path): if os.path.isfile(os.path.join(path, filename)): files.append(filename) for i in range(0,len(files)): try: file = excel.Workbooks.Open(path + '/' + files[i]) file.WorkSheets(sheet).Select() name, ext = os.path.splitext(files[i]) print(i) file.ActiveSheet.ExportAsFixedFormat(0, path_pdf + '/' + name + now.strftime('_%Y%m%d%H%M%S')) except Exception as e: print(e) finally: excel.Quit() #Excel to JPG def excel_JPG(): path_pdf = path_p.get() path_jpg = path_j.get() files_pdf = [] for filename_pdf in os.listdir(path_pdf): if os.path.isfile(os.path.join(path_pdf, filename_pdf)): files_pdf.append(filename_pdf) print(files_pdf) for k in range(0,len(files_pdf)): # PDFファイルのパス pdf_path = path_pdf + '/' + files_pdf[k] #この1文で変換されたjpegファイルが、imageホルダー内に作られます。 convert_from_path(pdf_path, output_folder=path_jpg,fmt='jpeg',output_file=Path(pdf_path).stem) # Excelフォルダ指定先参照ボタンの関数 def excel_dirdialog_clicked(): iDir = os.path.abspath(os.path.abspath("__file__")) iDirPath = filedialog.askdirectory(initialdir = iDir) path_excel.set(iDirPath) # pdf出力先指定の関数 def pdf_outdirdialog_clicked(): iDir_out = os.path.abspath(os.path.abspath("__file__")) iDirPath_out = filedialog.askdirectory(initialdir = iDir_out) path_p.set(iDirPath_out) # jpg出力先指定の関数 def jpg_outdirdialog_clicked(): iDir_out = os.path.abspath(os.path.abspath("__file__")) iDirPath_out = filedialog.askdirectory(initialdir = iDir_out) path_j.set(iDirPath_out) #SHEET名の取得関数 def main(): global statusbar # ファイルの参照 def brouse_func(): fTyp = [("", ".xlsx")] file_name = filedialog.askopenfilename(filetypes=fTyp) statusbar["text"] = file_name # コンボボックスのリスト取得 # ファイル読み込み wb = xl.load_workbook(filename=file_name) sheets = wb.sheetnames combobox.config(values=sheets) #これでコンボボックスのリストを更新できる # コンボボックス内の値の取得 def select_combo(event): global sheet sheet = combobox.get() #GUIの設定 root = tk.Tk() root.title("Excel変換ツール") root.geometry("600x300") # Frame1(Excel)の作成 frame1 = ttk.Frame(root, padding=10) frame1.grid(row=0, column=1, sticky=E) # 「Excelフォルダ参照」ラベルの作成 IDirLabel = ttk.Label(frame1, text="Excel読込先指定>>", padding=(5, 2)) IDirLabel.pack(side=LEFT) # 「Excelフォルダ参照」エントリーの作成 path_excel = StringVar() IDirEntry = ttk.Entry(frame1, textvariable=path_excel, width=60) IDirEntry.pack(side=LEFT) # 「Excelフォルダ参照」ボタンの作成 IDirButton = ttk.Button(frame1, text="参照", command=excel_dirdialog_clicked) IDirButton.pack(side=LEFT) # Frame2(PDF出力先)の作成 frame2 = ttk.Frame(root, padding=10) frame2.grid(row=2, column=1, sticky=E) # 「PDF出力先」ラベルの作成 IDirLabel_out = ttk.Label(frame2, text="PDF出力先指定>>", padding=(5, 2)) IDirLabel_out.pack(side=LEFT) # 「PDF出力先」エントリーの作成 path_p = StringVar() IDirEntry_out = ttk.Entry(frame2, textvariable=path_p, width=60) IDirEntry_out.pack(side=LEFT) # 「PDF出力先」ボタンの作成 IDirButton_out = ttk.Button(frame2, text="参照", command=pdf_outdirdialog_clicked) IDirButton_out.pack(side=LEFT) # Frame3(PDF出力先)の作成 frame3 = ttk.Frame(root, padding=10) frame3.grid(row=4, column=1, sticky=E) # 「jpg出力先」ラベルの作成 IDirLabel_out = ttk.Label(frame3, text="JPG出力先指定>>", padding=(5, 2)) IDirLabel_out.pack(side=LEFT) # 「JPG出力先」エントリーの作成 path_j = StringVar() IDirEntry_out = ttk.Entry(frame3, textvariable=path_j, width=60) IDirEntry_out.pack(side=LEFT) # 「JPG出力先」ボタンの作成 IDirButton_out_jpg = ttk.Button(frame3, text="参照", command=jpg_outdirdialog_clicked) IDirButton_out_jpg.pack(side=LEFT) # Sheet Frame作成 frame = ttk.Labelframe(root, padding=10) frame.grid(row=1, column=1, sticky=E) # ステータスバー statusbar = tk.Label(frame, text="参照するファイルを選択してください。 ", bd=1, relief=tk.SUNKEN, anchor=tk.W) statusbar.pack(side=LEFT) # 参照ボタン brouse_button = tk.Button(frame, text="参照", command=brouse_func) brouse_button.pack(padx=10, side=tk.LEFT) ##コンボボックス v = tk.StringVar() sheets = [] #一旦、空リスト用意 combobox = ttk.Combobox(frame, textvariable=v, values=sheets, height=3, #コンボボックスクリックした時の表示数 state="readonly") #手入力禁止 combobox.bind('<<ComboboxSelected>>', select_combo) #コンボボックスで選択した時、select_comboが発動 combobox.pack(padx=10, pady=5, side=tk.BOTTOM) # Frame(PDF変換 実行ボタン)の作成 frame4 = ttk.Frame(root, padding=8) frame4.grid(row=5,column=1,sticky=W) # PDF変換 実行ボタンの設置 button1 = ttk.Button(frame4, text="Excel to PDF変換", command=excel_PDF) button1.pack(fill = "x", padx=30, side = "left") # Frame(JPG変換 実行ボタン)の作成 frame5 = ttk.Frame(root, padding=8) frame5.grid(row=6,column=1,sticky=W) # JPG変換 実行ボタンの設置 button2 = ttk.Button(frame5, text="PDF to JPG変換", command=excel_JPG) button2.pack(fill = "x", padx=30, side = "left") tk.mainloop()
ソースコードのインデントが崩れているので、
修正をお願いします。マークダウン記法を用いてコードブロック内に挿入してください。
exeファイルを作るのに使ったツールは何でしょうか。
ツールにより仕様が異なるため、対処方法が変わることがあります。
また、プログラムのファイル・ディレクトリ構造の情報も必要です。
"file" というフォルダが実行時のフォルダにあることを想定していませんか?
このままではコードが読めないので、質問を編集し、</>(コードの挿入)ボタンを押し、出てくる’’’の枠の中にコードを貼り付けてください
ご回答ありがとうございます。コードの記載を修正いたしました。ご確認いただけますでしょうか。
また、exeファイルを作るのに利用したのは、”Pyinstaller”です。(Onefileのみで実行して、Exe実行時のエラー確認しましたがエラーは出現せず、Jpeg化のみ動かない状況)
”file”というフォルダは想定していないはずです。
(かなりの初心者のため、自分が実行したいコードを色々なところから寄せ集めています。初歩的なミスもあるかと思いますが、ご教示いただけますと幸いです。)
いくつか追加で確認点があります。
>Exe実行時のエラー確認しましたがエラーは出現せず
エラー自体は表示される設定でしょうか?GUIプログラムのexe化の場合、
標準エラー出力がオフになっている場合があります。
意図的にエラーの出るコードを試し、exe化してもエラーが正常に出力されてるのを確認してください。
(pyinstaller のconsoleを有効にするオプションが必要になるかもしれません)
>”file”というフォルダは想定していないはずです。
file ではなく __file__ だったようです。
(ソースコードのレイアウトが崩れて、__がマークダウン記法として解釈されてました)
exe化で問題になるよくあるケースは、ファイル等の相対パスが
デバッグ実行時とexeの実行時で異なるというものなのですが、
少し見た感じ、__file__ は exe化の際は実行ファイルではなく、元ソースのファイルの場所になるので
exe化を想定したコードでは、代替として sys.executable を用います。
但し、何らかのエラーは出ると思うので、他にも問題はありそうです。
よく見れば __file__ ではなく、"__file__" になってますね。
Python では、__file__ は特殊な変数で、
実行時に対象のソースのファイルのパスに置き換わるのですが、
"__file__" 文字列定数だと意味が違ってくるので、参考にした元コードを確認してもらったほうが良いかもしれません。
ディレクトリのパス自体は所得できているので、正常に動いて支障ないかもしれないけど、
恐らく誤ったコードです。
ご回答ありがとうございます。
以下のようなコードを作成して、Exe化したところ正常に動きましたので
原因はPathではないかと思います。
<コード>
from pdf2image import convert_from_path
images = convert_from_path("C:/Users/users/Desktop/PDF_JPG変換ツール/PDF/test2.pdf",poppler_path = "C:/Program Files (x86)/poppler/bin")
for i in range(len(images)):
images[i].save('img'+str(i)+'.jpg', 'JPEG')
"__file__"を修正して以下のようなコードにしてみました。
<コード>
# Excelフォルダ指定先参照ボタンの関数
def excel_dirdialog_clicked():
iDir = os.path.abspath(os.path.abspath(sys.executable))
iDirPath = filedialog.askdirectory(initialdir = iDir)
path_excel.set(iDirPath)
print(iDir)
# PDF出力指定の関数
def pdf_outdirdialog_clicked():
iDir_out = os.path.abspath(os.path.abspath(sys.executable))
iDirPath_out = filedialog.askdirectory(initialdir = iDir_out)
path_p.set(iDirPath_out)
print(iDir_out)
# jpg出力先指定の関数
def jpg_outdirdialog_clicked():
iDir_out_jpg = os.path.abspath(os.path.abspath(sys.executable))
iDirPath_out_jpg = filedialog.askdirectory(initialdir = iDir_out_jpg)
path_j.set(iDirPath_out_jpg)
print(iDir_out_jpg)
print結果は全て以下のように出力されており、JupyterNoteでは正常に動きました。
C:\Users\users\Anaconda3\python.exe
C:\Users\users\Anaconda3\python.exe
C:\Users\users\Anaconda3\python.exe
※Exe化した際は、「python.exe」のところが「●●.exe」(●●はエグゼ名)
ですがやはりExe化すると、Jpeg化のみ動かなくなってしまいました。
Run-timer Information の情報を参考に、「バンドルされていますか?」を実行してみたところ
JupyterNoteでは「running in a normal Python process」
Exe化では「running in a PyInstaller bundle」と出力されました。
何か関係がありますでしょうか。
Run-timer Information の情報に記載されている内容が難しく、あまり理解できておらず申し訳ありませんが
お知恵お借りできれば幸いです。何卒よろしくお願いいたします。
> Exe化したところ正常に動きましたので
> 原因はPathではないかと思います。
お互いの認識に齟齬がでないように言葉尻の確認ですが、
convert_from_path 自体は単体でexe化をしても動いたので、
原因は Path が濃厚ということですよね?
>os.path.abspath(os.path.abspath(sys.executable))
abspath を2回重ねてます
askdirectory への指定はファイルではなくディレクトリのpathなので
os.path.dirname(os.path.abspath(sys.executable)) とすべきです。
但し、exe化でない場合に正しく動いた説明にはならないので、
問題は他にあるはず。
convert_from_path へ渡す引数 をprint して
通常実行・exe化での実行結果を比較してみてください。
補足: os.path.abspath(os.path.abspath(sys.executable))
sys.executable をそのまま使うと、通常実行時に python.exe の場所になってしまうので
sys.frozen の有無をチェックして、通常実行時・EXEでの実行を区別する必要があります。
具体的なコードは、回答のリンク先を参照してください。
エラーが出てないということなので該当しないかもしれませんが、
関連で、以下も確認してみてください。
(エラー自体が出ない設定になっている可能性もあります)
Python3 Tkinter exe化後のPDFの画像変換ができない
https://teratail.com/questions/254226
私の環境 (win10) ではpyinstaller を使ってexeしてもjpgファイルに変換できました。
なので、コードの問題ではなく実行環境の問題かもしれません。
exe化した際の環境変数(os.environ)で外部プログラムの場所にPATHが通っているかを確認してみてください。
但し、その場合でも何らかのエラーは出るはずなので、
エラーの設定を先に確認してください。
(意図的にエラーを起こし、exe化した際に正常にエラーが出力されるかどうか)
ありがとうございます。
意図的にエラーを起こしたファイルをexe化したところ(Pyinstaller ●●.py --onefile)
エラーが表示されることを確認しました。
(--noconsoleオプションをつけるとエラー表示されないため、つけていません)
>exe化した際の環境変数(os.environ)で外部プログラムの場所にPATHが通っているかを確認してみてください。
確認すべきPathはPopplerのパスという理解であっていますでしょうか?
Pythonを再度インストールしなおしてみるなどしましたが、やはりexe化をするとJpeg化のみ動かなくなってしまいますが、画面がちらついているので何かしら走っているような気配はあるようです。。
> エラーが表示されることを確認しました。
これなら大丈夫です。私の環境でも --onefile オプションのみで確認済。
エラーもないとなると原因の特定が困難ですが、
他に懸念があるとすれば、変換対象の PDF ファイル等かな。
⇛ excel to PDF で変換した PDF ではなく、他の PDF ファイルを対象に試してみる。
> 確認すべきPathはPopplerのパスという理解であっていますでしょうか?
はい。具体的には pdfimages という名前の実行ファイルのある場所です。
(convert_from_path が呼び出す外部プログラム)
画面のちらつきに関しては確認してませんが、
convert_from_path 関数は、呼び出した外部プログラムの終了を待つため
処理時間が長かったりすると、その間 GUI は応答しなくなります。
今回の質問とは別問題ですが、スレッドを使って呼び出す事で回避できます。
(マルチスレッドになるので、プログラムの完了を GUI に通知したい場合は、
スレッド間のやり取りで少し複雑な手順が必要になってきます)
確認したい事がもう一点、通常実行時とexe化での実行時で
ファイルの置き場所もしくはファイル名に日本語が含まれてませんか?
日本語ファイル名を対象にすると、変換自体はできたものの文字化けを起こしたので。
色々とありがとうございます。
ファイル名に日本語が含まれていたため、「excel_converter.py」にてエグゼ化を試みてみましたが
やはりJpeg化のみ実行不可となりました。
#この1文で変換されたjpegファイルが、imageホルダー内に作られます。
convert_from_path(pdf_path, output_folder=path_jpg,fmt='jpeg',output_file=Path(pdf_path).stem)
上記のコードでJPEG化を実行しているのですが、コード内でPoppler/binのパスを指定していないことが原因かと思い、
convert_from_path(pdf_path, output_folder=path_jpg,fmt='jpeg',output_file=Path(pdf_path).stem,poppler_path= /poppler/bin)
のようにパス指定をしてみましたが、以下のようなエラーが出現しJupyterNote上でも実行不可となりました。
pywintypes.com_error: (-2147418111, '呼び出し先が呼び出しを拒否しました。', None, None)
poppler_path= /poppler/bin は想定していませんでしたが、
環境変数の PATH に通せばよいはずです。
追加で poppler_path での設定というのは、
単体でテストした場合に成功してるということなので、
その説明がつかなくなります。
>> convert_from_path へ渡す引数 をprint して
>> 通常実行・exe化での実行結果を比較してみてください。
ここの情報が未確認なのでお願いします。
デバッグで確認してほしいのは、通常実行時とexe化での実行時における
convert_from_path へ渡す引数 output_folder, output_file の値です。
>追加で poppler_path での設定というのは、
単体でテストした場合に成功してるということなので、
その説明がつかなくなります。
なるほど、たしかにそうですね。
>convert_from_path へ渡す引数 output_folder, output_file の値です。
確認がもれていました。
検証したところ、JupyterでもExe化でも以下のようなエラーが出現しました。
NameError: name 'output_folder' is not defined
NameError: name 'output_file' is not defined
Jupyterではprint(output_folder)やprint(putput_file)を入れてしまうと
エラーのため途中で止まってしまいJPEG化もできない状態でした。
Jupyterではprint(●●)を削除すると、所定のフォルダに格納され、想定通りのファイル名でJPEG化されているため、どこかで値をとることはできているのではないかと思います。
> 検証したところ、JupyterでもExe化でも以下のようなエラーが出現しました。
NameError: name 'output_folder' is not defined
NameError: name 'output_file' is not defined
変数名ではなく、convert_from_path に渡している
それぞれの引数の値です。
print(f"{pdf_path=}, output_folder={path_jpg}, output_file={Path(pdf_path).stem}")
という感じで、convert_from_path の直前あたりで確認してみてください。
Jupyter>>
pdf_path='C:/Users/UserName/Desktop/PDF_JPG変換ツール/PDF/test2_20221026193447.pdf', output_folder=C:/Users/UserName/Desktop/PDF_JPG変換ツール/JPG, output_file=test2_20221026193447
Exe>>
pdf_path='C:/Users/UserName/Desktop/PDF_JPG変換ツール/PDF/test2_20221026193447.pdf', output_folder=C:/Users/UserName/Desktop/PDF_JPG変換ツール/JPG, output_file=test2_20221026193447
何度もすみません、お示しのコードにて取得してJupyterとExeそれぞれで実施してみましたが
同じ値を取得してきているようです。
双方でpath が同じということは、
実行環境に問題がありそうですね。
単体テストで成功したときは、path 内に日本語は含まれていましたか?
(実行環境の違いにより、exeでの実行の場合のみ文字コードが適切に扱えない等)
他は、エラーもなく私の環境では再現していないので、
環境の導入手順をすり合わせていくしか無いのかな。
win32com 利用なので勝手に windows 想定でしたが
実行環境で何か特筆すべき点があれば教えてください。
(追記: 私がテストしたのは anaconda ではなく、
公式からインストールしたものでした)
Windows10を利用しています。
JupyterNote、Anaconda3で実行時は成功
Anaconda3でPyinstallerを利用してExe化したところJPEG化のみ実行不可でした。
公式からインストールしたものというのは、何を利用されていますでしょうか?(素人で申し訳ありません)
ちなみにコマンドプロンプトで実行した際にはエラーが出ていましたが、おそらくそれは環境構築が出来ていないため(モジュールがインストールされていないため)だと推測していました。
> 単体テストで成功したときは、path 内に日本語は含まれていましたか?
こちらの確認をお願いします。
公式からのインストールは、https://www.python.org/downloads/ からダウンロードしたものです。
バージョンは 3.8.8 で、venv で環境下に必要モジュール類をインストールしました。
poppler は4年くらい前にインストールしていて、導入経路は失念してます。バージョンは0.68.0
> ちなみにコマンドプロンプトで実行した際にはエラーが出ていましたが、おそらくそれは環境構築が出来ていないため(モジュールがインストールされていないため)だと推測していました。
これは、出来上がった exe ファイルを実行したときですか?
環境変数PATHの確認等に見落としがありそうです。念の為エラーメッセージと
exe ファイルの実行方法も教えてください。私はコマンドプロンプトからの実行で試しました。
試してもらいたいことがもう一点、
「PDF_JPG変換ツール」というフォルダ名ですが、
日本語を含まないフォルダ名で試してみてください。
実行場所のフォルダに日本語が含まれていると
エラーもなくファイルも生成されないことを確認しました。
PDFのファイル名自体は、(文字化けするものの)
日本語が含まれていても変換できることは確認しています。
通常実行時とexe化して実行する際の、
プログラムを実行する現在の作業ディレクトリ
確認方法
import os
print(os.getcwd())
対処方法ですが、恐らく 外部ライブラリ側の問題の為、そちらに手を加えるのは難しいので、
一旦別の場所に移動してから変換処理を実行し、
後で元に戻す等の回避手段を取らないといけないかもしれません。
回答1件