[Python]自動で電子書籍をスクショしPDFで保存する

## はじめに
　電子書籍を1ページずつスクショしてPDFにまとめるのがめんどくさかったので自動化させてみました．
　ここでは自動化に用いたモジュールなどを説明したいと思います．実行コードはgithubにありますのでご自由にお使いください．

実行コード：https://github.com/ryosuke-noob/playground/blob/main/kindle/exec_all.py

## 環境
Apple M2
macOS Sonoma 14.6.1
Python 3.9.13

## 事前準備
以下のモジュールをインストールします．

fpdf2 : 画像をpdfに変換する目的で用いる. 
(https://pypi.org/project/PyPDF2/)
pyautogui : スクリーンショット，ページ移動をする目的で用いる
(https://pypi.org/project/PyAutoGUI/)

```
pip install fpdf2 pyautogui
```

## 電子書籍の各ページをスクショ
スクショの撮影やページの移動にはpyautoguiを用います.
pyautoguiは，pythonスクリプトからキーボードやマウスの操作を制御できるようにするモジュールです．
pyautoguiに関する詳しい説明は省きます．

（注意：2024年10月現在，マルチディスプレイには対応しておらず，メインディスプレイの操作しかできませんでした）

### スクショの範囲設定
・`pag.position()`でカーソルのディスプレイ上の位置を取得します．
・`decide_capture_region()`では，スクショしたい範囲の左上と右下の位置をそれぞれ取得することで，スクショの範囲を決定しています．

```py:screenshot.py
import pyautogui as pag

def decide_capture_region(
    wait_sec: int = 5,
) -> tuple[int, int, int, int]:
    """スクショする範囲を決める"""
    print("カーソルをスクショしたい範囲の左上角に合わせてください")
    for sec in range(wait_sec, 0, -1):
        print(sec)
        time.sleep(1)
    x1, y1 = pag.position()
    print(f"左上の座標を({x1},{y1})にセットしました")

    print("カーソルをスクショしたい範囲の右下角に合わせてください")
    for sec in range(wait_sec, 0, -1):
        print(sec)
        time.sleep(1)
    x2, y2 = pag.position()
    print(f"右下の座標を({x2},{y2})にセットしました")

    return x1, y1, x2-x1, y2-y1
```
### スクショ撮影
以下の関数では，指定した範囲をスクショし，画像にて保存します．
　・`pag.screenshot(region=region)`でregionを指定し，撮影できます．
　・`pag.keyDown(next_page_key)`，`pag.keyUp(next_page_key)`でページをめくります．keyDownを実行したあとにkeyUpを実行しないとkeyが押され続けた状態になります．
 ・電子書籍環境によってページを捲るkeyなどは変更してください

```py:screenshot.py
def capture_screen(
    start_page: int,
    end_page: int,
    image_dir: str,
    chapter_name: str,
    region: tuple[int, int, int, int],
):
    """スクショし画像として保存"""
    shot_span = 2
    next_page_key = 'right'

    print(region)

    # スクリーンの移動
    print("撮影したいウィンドウを選択してください")
    for sec in range(5, 0, -1):
        print(sec)
        time.sleep(1)

    # 実行
    print("撮影を始めます")
    for page in range(start_page, end_page+1):
        file_name = f"{chapter_name}_{page}.png" if chapter_name else f"{page}.png"
        s = pag.screenshot(region=region)
        s.save(f"{image_dir}/{file_name}")
        pag.keyDown(next_page_key)
        pag.keyUp(next_page_key)
        time.sleep(shot_span)
    print(f"画像を {image_dir} に保存しました")
```

## 画像をpdfに変換
画像のPDF化にはfpdfを用います.
fpdfは，pythonスクリプトからPDFを作成できるモジュールです．
今回やることは，pdfの各ページに画像を貼り付けていくイメージです．
fpdfに関する詳しい説明は省きます．


```py:image2pdf.py
def image_to_pdf(
    start_page: int,
    end_page: int,
    output_dir: str,
    chapter_name: str,
):
    """複数の画像を1つのPDFとして保存"""
    print("画像をPDFに変換します")
    pdf = FPDF()
    pdf.set_auto_page_break(False) # これをつけないと勝手に次のページが作られて空白ページができてしまう
    for page in range(start_page, end_page+1):
        image = f"{output_dir}/image/{chapter_name}_{page}.png" if chapter_name else f"{output_dir}/image/{page}.png"
        pdf.add_page()
        pdf.image(image, x=Align.C) # w=pdf.ephなどでフルサイズ指定できる
    pdf_path = f"{output_dir}/pdf/{chapter_name}_{start_page}_{end_page}.pdf" \
            if chapter_name else f"{output_dir}/pdf/{start_page}_{end_page}.pdf"
    pdf.output(pdf_path)
    print(f"PDFを {pdf_path} に保存しました")
```

## おわりに
今回初めて記事を書いたので，足りない部分も多いかと思います．
なにかアドバイスや質問などあればよろしくお願いします．