nyanpyou Note

主な目的は調べたり作ったりしたプログラミング備忘録(予定)

大量のPDFに対して一斉にパスワード付与と解除をしたい話

実に2か月ぶりの更新。

やりたいこと

大量のPDFファイル全てにパスワードを付与したい。また、それの解除もしたい。
そしてこの機能を組み込みたいプログラムがPythonで作成中の物なので、できればPythonから動かしたい。
Pythonと連携はしないが、一斉にパスワードを付与するだけならAcrobat Proのアクションウィザードを使えば可能なためこれも試してみたが、パスワードを一斉解除することは出来なかった。

news.mynavi.jp

Google大先生に聞いてみる

まずは「Python PDF パスワード」で検索するとヒットするライブラリのPyPDF2を試してみたが、PDFファイルによってエラーが出ることが発覚した。

#エラーの例
PdfReadWarning: Object 105 0 not defined. [pdf.py:1629]
Traceback (most recent call last):
 (中略)
  File "C:\...\AppData\Local\Programs\Python\Python37\lib\site-packages\PyPDF2\pdf.py", line 482, in write
    self._sweepIndirectReferences(externalReferenceMap, self._root)
  File "C:\...\AppData\Local\Programs\Python\Python37\lib\site-packages\PyPDF2\pdf.py", line 571, in _sweepIndirectReferences
    self._sweepIndirectReferences(externMap, realdata)
  File "C:\...\AppData\Local\Programs\Python\Python37\lib\site-packages\PyPDF2\pdf.py", line 547, in _sweepIndirectReferences
    value = self._sweepIndirectReferences(externMap, value)
  File "C:\...\AppData\Local\Programs\Python\Python37\lib\site-packages\PyPDF2\pdf.py", line 577, in _sweepIndirectReferences
    newobj = data.pdf.getObject(data)
  File "C:\...\AppData\Local\Programs\Python\Python37\lib\site-packages\PyPDF2\pdf.py", line 1631, in getObject
    raise utils.PdfReadError("Could not find object.")
PyPDF2.utils.PdfReadError: Could not find object.

検索してみるとGithubやstackoverflowにも同様の事例が報告されている。stackoverflowにPyPDF2内のpdf.pyの中身を書き換える解決法が載っていたため試すも、Acrobatで開けないPDFファイルが出力されてしまい結局駄目だった。

stackoverflow.com

上記のエラーとは別の話だが、改めて調べてみると、そもそもPyPDF2は日本語名フォント入りのファイルが開けなかったり少し日本人的には不便なライブラリであるらしい。

gammasoft.jp

QPDF

仕方がないので別の手段を探した結果、QPDFが使えそうだと判明したため、これを使うことにした。 QPDFが使えるというアイディアはこちらの記事から頂いた。

uttnaoki.hatenablog.com

QPDFはTeXディストリビューションであるW32TeXを実行すると勝手にインストールされているようなので、TeXユーザーは気付かないうちにインストールされているかもしれない。自分も既に入っていた。

早速試してみたがどうも日本語が入ったファイル名を指定すると、

#inputfileにあ.pdfを指定した時のエラー例
terminate called after throwing an instance of 'QPDFSystemError'
  what():  open あ.pdf: No such file or directory

と言われて処理が出来ない。
仕方がないので半角英数字に名前を書き換える処理を経由することで動くようにした。QPDFで日本語名ファイルを扱うもっと上手い方法を知っていたらぜひ教えて下さい。

#フォルダ内のPDFファイルに対して一斉にパスワードを付与したり解除したりする
import glob
import subprocess
import os

PASSWORD = "password"

def decrypt_password():
    PDFLIST = glob.glob("*.pdf")
    for i in PDFLIST:
        os.rename(i, "decrypt_under_process.pdf")
        subprocess.call("qpdf --password={} --decrypt {} {}".format(PASSWORD, "decrypt_under_process.pdf", "decrypt_under_process2.pdf"))
        os.rename("decrypt_under_process2.pdf", i)
        os.remove("decrypt_under_process.pdf")

def encrypt_password():
    PDFLIST = glob.glob("*.pdf")
    for i in PDFLIST:
        os.rename(i, "encrypt_under_process.pdf")
        subprocess.call("qpdf --encrypt {} '' 40 -- {} {} ".format(PASSWORD, "encrypt_under_process.pdf", "encrypt_under_process2.pdf"))
        os.rename("encrypt_under_process2.pdf", i)
        os.remove("encrypt_under_process.pdf")

#decrypt_password()
encrypt_password()

QPDFの各種コマンドについては以下を参考にした。

pdf-file.nnn2.com

読み取りだけでなく編集もパスワードで制限する等の場合は、encryptのオプション(encrypt_password()のsubprocess.call中--encrypt以降の部分)を適宜変更してください。

コマンドラインプログラムをPythonのsubprocessで実行しているだけなので、他の言語にもそのまま流用可能(なはず)。