【PySimpleGUI】画像閲覧とPDF画像変換アプリを自作してみた

# PySimpleGUIを使って、デスクトップアプリケーションを自作

## 目的と背景

日々の業務でPDF資料を使うことが非常に多い。また、PDFの一部だけ切り取って、共有したいという機会も多く、PDFを開いて画面キャプチャしたりするのが手間。
Windows標準のエクスプローラーでも画像やPDFの中身を見ることは出来るが、他の情報（ディレクトリや、そのほかの実行ファイルなど）が多く扱いにくい。


## 完成品と動作

以下のような、GUIを作成。

![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3052682/c78cdb19-356e-864b-a833-ad78ef8b585d.png)


タブ1は、任意のディレクトリの画像形式（pnh,jpeg）のみが一覧で表示され、クリックすると中身の画像が閲覧できる。
※サイズは任意サイズに変更可能
※都合の良いPDFファイルがなく自作…


![画像4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3052682/8f3068f4-e423-a048-c9fe-c78e84864f28.png)


タブ2は、任意のディレクトリのPDF形式（.pdf）のみが一覧で表示され、クリックすると全ページを画像形式に変換して、専用のフォルダを作成する。
（今回は、Dドライブに本アプリで分割した画像を管理する専用のフォルダを作成。元のPDFファイルの名称でフォルダが作成され、1枚ずつ画像として保存される。）

![画像5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3052682/21668b7a-0fca-2efb-66ee-c856194f18e4.png)


基本的な動きとしては、
・タブ2でPDFファイルを画像に変換して保存
・保存した画像をタブ1のビューワーで見る

## 参考にしたURL

### PySimpleGUI公式ドキュメント
[The PySimpleGUI Cookbook](https://www.pysimplegui.org/en/latest/cookbook/)

画像ビューワー部分は、上記ドキュメントの『Recipe - convert_to_bytes Function + PIL Image Viewer』のままです。

![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3052682/e1b598fe-ec87-3c07-d817-9b8da8bb1192.png)


### PDFを画像に変換する方法

[Pythonでpdf2imageを使用しPDFファイルを画像に変換する](https://laboratory.kazuuu.net/convert-a-pdf-file-to-an-image-using-pdf2image-in-python/)

PDFファイルを画像変換する方法




## 必要なライブラリ、事前準備

[Windows版Poppler](https://github.com/oschwartz10612/poppler-windows/releases/)

## ソースコード


```python:ライブラリのインポート
import PySimpleGUI as sg
import os
import sys
from pdf2image import convert_from_path
# import PySimpleGUIQt as sg
import os.path
import PIL.Image
import io
import base64
```


```python:PDFを画像に変換した際に保存するためのフォルダを作成
def make_folder():
    # デフォルトはDドライブ
    base_folder_name = "D:/Popper_Media_Library" # ここは任意のフォルダにしてOK
    # Dドライブにアプリケーション用のフォルダを作成
    os.makedirs(base_folder_name, exist_ok=True)
```




```python:PDFを画像に変換する
# POPPLERライブラリの格納場所
POPPLER_PATH = r"poppler-0.68.0_x86\poppler-0.68.0\bin"

def main(file_path, full_file_path):
    output_folder_name = "D:/Popper_Media_Library" + "/" + file_path
    os.makedirs(output_folder_name, exist_ok=True)
    folder_name = os.path.dirname(file_path)
    pdf_file_name = os.path.basename(file_path)
    base_file_name = os.path.splitext(pdf_file_name)[0]
    print(base_file_name)
    # PDFをImageに変換
    pages = convert_from_path(full_file_path , poppler_path=POPPLER_PATH)

    # 1ページずつ画像ファイルとして保存する
    for i, page in enumerate(pages):
        image_f_name = f'{base_file_name}_{str(i).zfill(2)}.png'
        image_f_path = os.path.join(output_folder_name, image_f_name)
        page.save(image_f_path )
```



```python:画像ファイルを読み込んでアプリ上に表示
# PySimpleGUI公式ドキュメントのまま
def convert_to_bytes(file_or_bytes, resize=None):
    if isinstance(file_or_bytes, str):
        img = PIL.Image.open(file_or_bytes)
    else:
        try:
            img = PIL.Image.open(io.BytesIO(base64.b64decode(file_or_bytes)))
        except Exception as e:
            dataBytesIO = io.BytesIO(file_or_bytes)
            img = PIL.Image.open(dataBytesIO)

    cur_width, cur_height = img.size
    if resize:
        new_width, new_height = resize
        scale = min(new_height/cur_height, new_width/cur_width)
        img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS)
    bio = io.BytesIO()
    img.save(bio, format="PNG")
    del img
    return bio.getvalue()
```


```python:レイアウトを定義
# 画像ビューワータブの左側
tab1_left_col = [[sg.Text('Folder'), sg.In(size=(25,1), enable_events=True ,key='-FOLDER_Tab1-'), sg.FolderBrowse()],
            [sg.Listbox(values=[], enable_events=True, size=(40,20),key='-FILE LIST_Tab1-')],
            [sg.Text('Resize to'), sg.In(default_text="700",key='-W-', size=(5,1)), sg.In(default_text="500",key='-H-', size=(5,1))]]

# 画像ビューワータブの右側
tab1_images_col = [[sg.Text('You choose from the list:')],
              [sg.Text(size=(40,1), key='-TOUT-')],
              [sg.Image(key='-IMAGE-')]]

# PDF変換タブの左側
tab2_pdf_col = [[sg.Text('Folder'), sg.In(size=(25,1), enable_events=True ,key='-FOLDER_Tab2-'), sg.FolderBrowse()],
               [sg.Listbox(values=[], enable_events=True, size=(40,20),key='-FILE LIST_Tab2-')]]
# PDF変換タブの右側
tab2_console = [[sg.Text('コンソール',size=(25,1))],
                [sg.Multiline(default_text="hello\nworld\n!!", size=(40,20),key='-Console-')]]

# ----- 全体レイアウト -----
# タブ1のレイアウト
tab1_layout = [[sg.Column(tab1_left_col, element_justification='c'), sg.VSeperator(),sg.Column(tab1_images_col, element_justification='c')]]

# タブ2のレイアウト
tab2_layout = [[sg.Column(tab2_pdf_col, element_justification='c'), sg.VSeperator(),sg.Column(tab2_console, element_justification='c')]
]

# 全体のレイアウト
layout = [
    [sg.TabGroup([[sg.Tab('画像ビュワー', tab1_layout), sg.Tab('PDF2PNG', tab2_layout)]])],
    [sg.Quit()]
]
# --------------------------------- ウインドウの定義 ---------------------------------
window = sg.Window('Multiple Format Image Viewer', layout,resizable=True)
```


```python:イベントの定義
while True:
    event, values = window.read()
    if event in (sg.WIN_CLOSED, 'Exit'):
        break
    if event == sg.WIN_CLOSED or event == 'Exit':
        break
    if event == '-FOLDER_Tab1-': 
        folder = values['-FOLDER_Tab1-']
        try:
            file_list = os.listdir(folder)
        except:
            file_list = []
        fnames = [f for f in file_list if os.path.isfile(
            os.path.join(folder, f)) and f.lower().endswith((".png", ".jpg", "jpeg", ".tiff", ".bmp"))]
        window['-FILE LIST_Tab1-'].update(fnames)
    elif event == '-FILE LIST_Tab1-':
        try:
            filename = os.path.join(values['-FOLDER_Tab1-'], values['-FILE LIST_Tab1-'][0])
            window['-TOUT-'].update(filename)
            if values['-W-'] and values['-H-']:
                new_size = int(values['-W-']), int(values['-H-'])
            else:
                new_size = None
            window['-IMAGE-'].update(data=convert_to_bytes(filename, resize=new_size))
        except Exception as E:
            print(f'** Error {E} **')
            pass
    
    if event == '-FOLDER_Tab2-':
        folder = values['-FOLDER_Tab2-']
        try:
            file_list = os.listdir(folder)
        except:
            file_list = []
        fnames = [f for f in file_list if os.path.isfile(
            os.path.join(folder, f)) and f.lower().endswith((".pdf"))]
        window['-FILE LIST_Tab2-'].update(fnames)
    elif event == '-FILE LIST_Tab2-':
        try:
            file_path = os.path.join(values['-FOLDER_Tab2-'], values['-FILE LIST_Tab2-'][0])
            window['-TOUT-'].update(file_path)
            full_file_path = file_path 
            file_path = file_path.split(chr(92))[1]
            print("File_Path は",file_path)
            main(file_path, full_file_path)
            console_text = file_path + "\n画像コンバートが完了しました"
            window['-Console-'].update(console_text)
            print("アップデート")
        except Exception as E:
            print(f'** Error {E} **')
            pass       
        
# --------------------------------- クローズ ---------------------------------
window.close()
```