PythonとPopplerで大量のPDFを用紙サイズごとに結合

Share on:

PythonとPopplerを使ってPDFを用紙サイズごとに結合するプログラムを作成しました。GUIは毎度おなじみのPyQt5です。

subprocessでPopplerの呼び出し

Pythonからsubprocessモジュールを使ってpoppler-0.68.0に含まれるpdfunite.exeとpdfinfo.exeを呼び出してPDFの処理をしています。subprocessで実行するメソッドは下記の通りです。

1def subprocess_popen(self, cmd):
2    startupinfo = subprocess.STARTUPINFO()
3    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
4    startupinfo.wShowWindow = subprocess.SW_HIDE
5    proc = subprocess.Popen(cmd, encoding='cp932', stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo)
6    stdout, stderr = proc.communicate(timeout=50)
7    return stdout, stderr

subprocessではstartupinfoを指定してウィンドウを表示しないようにしています。今回使ったPCはWindows 10なので標準出力を文字列で受け取るためにencoding=’cp932’で文字コードを指定しています。

PDFのpage sizeを取得

pdfinfo.exeにPDFファイルを渡すと下記のように出力されます。

 1PS *:\*****\poppler-0.68.0\bin> .\pdfinfo A4.pdf
 2Title:          Book1
 3Author:         ********
 4Producer:       Microsoft: Print To PDF
 5CreationDate:   02/29/20 00:08:52 東京 (標準時)
 6ModDate:        02/29/20 00:08:52 東京 (標準時)
 7Tagged:         no
 8UserProperties: no
 9Suspects:       no
10Form:           none
11JavaScript:     no
12Pages:          1
13Encrypted:      no
14Page size:      595.32 x 841.92 pts (A4)
15Page rot:       0
16File size:      110952 bytes
17Optimized:      no
18PDF version:    1.7

この出力結果からPage sizeの行を取得します。まずは下記のようにpdfinfoを実行します。

1def pdfinfo(self, pdf_path):
2    stdout, stderr = self.subprocess_popen( ['poppler-0.68.0/bin/pdfinfo.exe', pdf_path] )
3    return stdout, stderr

読み込んだPDFの情報をPyQt5のQTableViewに表示させるため、下記のように、pdfのパスを受け取ったらpdfの情報をpdfinfoで読み取り、その情報をアイテムに追加するという処理をしました。

 1def pdfs_to_items(self, pdf_paths):
 2    self.model.removeRows(0, self.model.rowCount())
 3    paths = [ Path(pdf_path) for pdf_path in pdf_paths ]
 4    self.model.insertRows(0, len(paths))
 5    for row, path in enumerate(paths):
 6        item = self.model.index( row, 0, QtCore.QModelIndex() ).internalPointer()
 7        stdout, stderr = self.pdfinfo(str(path))
 8        if not stderr == '':
 9            continue
10        lines = [ line.split(':') for line in stdout.splitlines() ]
11        d = { line[0].strip():line[1].strip() for line in lines }
12        s = d['Page size'].split()
13        d2 = { 'Name':str(path.name), 'Path':str(path), 'height':s[0], 'width':s[2] }
14        d.update(d2)
15        item.set_dict(d)

PDFをpage sizeごとに結合

結合にはpdfunite.exeを使いました。下記のように実行しています。

1def pdfunite(self, pdfs, filename):
2    cmd = ['poppler-0.68.0/bin/pdfunite.exe']
3    cmd.extend(pdfs)
4    cmd.append(filename)
5    stdout, stderr = self.subprocess_popen(cmd)
6    return stdout, stderr

PDFをpage sizeごとに保存する処理は下記の通りです。

 1def filesave(self):
 2    savefolder = 'output/'
 3
 4    items = self.model.root_item.children()
 5
 6    sizes = set([item.data('Page size') for item in items])
 7    pdfs = { size:[] for size in sizes }
 8    for item in items:
 9        pdfs[item.data('Page size')].append(item.data('Path'))
10    for n, key in enumerate(pdfs):
11        self.pdfunite( pdfs[key], savefolder + str(n) + '.pdf' )

モデルからアイテムを取得して、アイテムからPDFのパスやpage sizeなどを取得して、page sizeごとにpdfuniteを実行して結合していくという処理です。

図面を扱うときなどに大量のPDFを処理することがあります。その時に、用紙サイズごとに分けてほしいとか図面リストに用紙サイズを入力してほしいなどといった要望があったりします。また、たくさんのPDFを印刷するときなどにも一旦用紙サイズごとに結合してから印刷することで作業効率を上げることが出来ます。そのために今回のようなプログラムを作ってみました。

使用頻度はそんなに多くないかもしれませんが、あると地味に便利なプログラムかなと思います。

関連記事