複数フォルダ内の画像をそれぞれフォルダごとに別々のpdfに一括で変換する

# はじめに
フォルダごとに分けた大量の画像があるのですが、簡単に取り扱えるようにフォルダごとのPDFファイルに変換しようと思いました。
最初は、Adobeを使ったり、cubePDFを使ったりしたのですが、どちらも実行速度が遅いという問題がありました。そのため、Pythonでプログラムを作ることにしました。
プログラムを調べたところ、以下のプログラムが見つかりました。

https://qiita.com/meznat/items/31d947ed4826350fd9fa

高速で、ロスレスでPDFに変換できる`img2pdf`というライブラリを使用してます。
基本的にはこれをベースに、少し使いにくかった部分を変更しました。


# できること

こんなフォルダがあったときに、

```
画像まとめ
├── 2015
│   ├── img1.jpg
│   ├── img2.jpg
│   ├── img3.jpg
├── 2016
│   ├── img10.jpg
│   ├── img11.jpg
│   ├── img12.jpg
├── 2017
│   ├── img21.jpg
│   ├── img22.jpg
│   ├── img23.jpg


```
`画像まとめ`という親フォルダをドラッグアンドドロップすることで、以下のように子フォルダ名を使用したPDFファイルに変換することができます。

```
画像まとめ
├── 2015
│   ├── img1.jpg
│   ├── img2.jpg
│   ├── img3.jpg
├── 2016
│   ├── img10.jpg
│   ├── img11.jpg
│   ├── img12.jpg
├── 2017
│   ├── img21.jpg
│   ├── img22.jpg
│   ├── img23.jpg
├── 2015.pdf
├── 2016.pdf
├── 2017.pdf
```





#ライブラリのインポート
```
pip install img2pdf
```

#コード

```make_pdf.py
import img2pdf
from pathlib import Path
import re


#数字がない文字列は前に持ってきている。
#逆にしたいときは代2引数にfloat('inf')を指定
#https://note.nkmk.me/python-sort-num-str/
def extract_num(s, ret=0):
    p = re.compile(r'(\d+)')    
    search = p.search(s)
    if search:
        return int(search.groups()[-1])
        print(search.groups())
    else:
        return ret


#単処理
def ImageToPdf(outputpath, imagepath):
    '''
    outputpath: pathlib.Path()
    imagepath: pathlib.Path()
    '''
    lists = list(imagepath.glob("**/*"))#単フォルダ内を検索
    #jpg,pngファイルだけpdfに結合
    #Pathlib.WindowsPath()をstring型に変換しないとエラー
    #jpegにも対応
    imgpath_list = [str(i) for i in lists if i.match("*.jpg") or i.match("*.png") or i.match("*.jpeg")]
    
    #https://note.nkmk.me/python-sort-num-str/
    #0埋めされていない番号順に対応
    imgpath_list.sort(key=lambda s: extract_num(s))
    
    #pdfを作成
    with open(outputpath,"wb") as f:
        f.write(img2pdf.convert(imgpath_list))
    print(outputpath.name + " :Done")

#複数フォルダをループ処理する
while True:
    #作業フォルダ
    base_path = input("PDFに変換したいファルダが入った親フォルダをd&Pしてください")
    base_path = base_path.strip(" \' ")
    #作業フォルダ内のディレクトリだけを抽出する
    glob = Path(base_path).glob("*")
    imagefolderlist = list([item for item in list(glob) if item.is_dir()])
    #outputpathに指定ディレクトリと同名を指定する
    outputpathlist = list([item.with_name(item.name + ".pdf") for item in imagefolderlist])
    #ループ処理を行う
    for outputpath,imagepath in zip(outputpathlist,imagefolderlist):
        try:
            ImageToPdf(outputpath,imagepath)
        except:
            import traceback
            traceback.print_exc()

```

#変更点

https://qiita.com/meznat/items/31d947ed4826350fd9fa

こちらのプログラムとの変更点は、
・パスをプログラム内に書かなければいけなかったところを、フォルダをドラックアンドドロップしてどんどん変換できるようにしました。
・`.jpeg`の拡張子に対応していなかったので対応させました。
・エラー内容を表示させるようにしました。
・0埋めされていないファイルも番号順に並べるようにしました。例えば、{1, 2, 10}を並べ替えると、{1,10,2}となってしまい、本来回避するためには、{01, 02, 10}と0で埋めないといけないです。なお、番号がないファイルは前に持ってこられます。


#ライブラリのアップデートによって改善されたこと

以前はアルファチャンネルを含むPNG画像は変換できずにエラーになっていました。
しかし2021/8/22のアップデートで、ソフトマスクを使用してアルファチャンネルのデータを保ったままPDFに変換できるようになりました。

https://github.com/josch/img2pdf/commit/af5ae5b9b6056e62c60dc629e23ee76550bb6c6f

https://www.antenna.co.jp/ptl/cookbook/vol4/i02-0043.html

#まとめ
フォルダで分けられた画像を、pdfに一気に変換するときに便利です。
ぜひ使ってください。