PDF の破損をチェックする Python コード例【QPDF】

Python で破損した PDF ファイルをチェックするコード例を書きました。

Python からコマンドラインツールの QPDFキューピーディーエフ を呼び出して、PDF のエラーをチェックします。

Python の Requests で PDF ファイルを取得したときに、『再取得が必要か?必要ないか?』を判定するために書きました。

これで、『PDF がエラーで開けない』といったケースを減らすことができました。

実際のところ、PDF のダウンロードが失敗するというのは滅多になかったのですが、例外がありました。

PDF が存在しないときに、サーバーが『200 (OK)』で応答しつつ、404 ページの HTML を返した場合です。

PDF を取得したつもりが、中身は 404 ページの HTML だったということが、けっこうありました。

そういった、中身が PDF でないものを検出するために、QPDF が便利でした。

コマンドラインツールの qpdf.exe を用意する

これは『QPDFキューピーディーエフ』というプロジェクト名で公開されていました。

ギットハブから取得します。

QPDF ダウンロードページ (GitHub)
https://github.com/qpdf/qpdf/releases

使うのは、Windows 用の『qpdf-9.1.1-bin-msvc64.zip』です。

(バージョンはそのときの最新版でいいと思います)

この中に「qpdf.exe」が含まれています。

QPDF の使い方

QPDF の基本的な使い方と、破損チェックの方法です。

qpdf.exe の呼び出し方は、QPDF のマニュアルに載っていました。

When running qpdf, the basic invocation is as follows:

qpdf [ options ] infilename [ outfilename ]

Basic Invocation

PDF の破損チェックを行う方法ですが、引数ひきすう--check というオプションを指定したらできました。

QPDF のマニュアルの場所

Testing, Inspection, and Debugging Options
--check (戻り値の説明もありました)

PDF が正常かいなかの判定方法です。

  • qpdf.exe の戻り値(返り値、リターンコード)が 0 なら正常でした。
  • エラーがあった時は 2 が返りました。
  • 警告のみであれば 3 が返りました。

ところで、PDF にエラーや警告があった場合でも、『PDF リーダーでは問題なく表示できた』というケースがたくさんありました。

なので、自分は『PDF が破損しているかもしれない』くらいの認識で使用しています。

特に、エラーではないけど警告 (WARNING) は出るといった PDF は、たくさんありました。

おそらくですが、PDF の作成に使用したソフトに、何らかの問題があったのだと思います。

しかしながら、そこはファイルの再取得でどうにかなる問題でもないので、無視しています。

エラーと判定された PDF だけ、『PDF が破損しているかもしれない』と判断しています。

Python から qpdf.exe を呼び出す方法

Python の subprocess.run() を使用して qpdf.exe を呼び出しました。

Python マニュアルの場所です。

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None)

それと、qpdf.exe の標準出力と標準エラー出力を抑制する方法です。

特殊値の subprocess.DEVNULL を使用したらできました。

subprocess.DEVNULL

subprocess.run() の戻り値の説明です。

class subprocess.CompletedProcess

コード例

Python から qpdf.exe を呼び出して、PDF の破損(エラーや警告)をチェックするコードです。

Python の subprocess モジュールを使用して qpdf.exe を呼び出しています。

そしてこの qpdf.exe ですが、『標準出力 (stdout)』や『標準エラー出力 (stderr)』に、読み込み結果の詳細を表示します。

これらが必要なければ、subprocess.DEVNULL で消すことができました。

"""check_pdf_main.py"""

from pathlib import Path
from subprocess import run, DEVNULL

def main():
    """メイン関数"""

    # (1/5) PDF ファイルを決める
    pdf_file = Path(r'F:\*****\sample.pdf')

    # (2/5) 実行ファイル qpdf.exe のファイルパスを決める
    exe_file = Path(r'F:\*****\qpdf\bin\qpdf.exe')

    # (3/5) コマンドラインの引数リストを作る(タプルでもOK)
    # qpdf [ options ] infilename [ outfilename ]
    # outfilename は必要なし。
    cmd = (exe_file, '--check', pdf_file)

    # (4/5) PDF をチェックする
    # cp は CompletedProcess の略です。
    cp = run(
        cmd,
        # stdout=DEVNULL, # エラー(警告)が見つからなかった時の表示を消す
        # stderr=DEVNULL, # エラー(警告)を検出した時の表示を消す
        )

    # (5/5) もし qpdf.exe がエラーを返したときは知らせる
    if cp.returncode == 0:
        # PDF は正常だった
        print(f'ok - {cp.returncode} - {pdf_file.name}')
        pass
    elif cp.returncode == 2:
        # エラー error を検出した
        print(f'error - {cp.returncode} - {pdf_file.name}')
    elif cp.returncode == 3:
        # 警告 warning を検出した
        print(f'warning - {cp.returncode} - {pdf_file.name}')

    # (デバッグ情報)
    print(f'デバッグ (exe_file) {exe_file}')
    print(f'デバッグ (pdf_file) {pdf_file}')
    print(f'デバッグ (cp.returncode) {cp.returncode}')

    # (終了)
    print('end')
    return

if __name__ == "__main__":
    main()

実行結果

Python コードの実行結果です。

コマンドプロンプトの表示を紹介します。

PDF が正常だった時

PDF が正常だった時の表示例です。

『File is not encrypted(PDF が暗号化されていない)』と『File is not linearized(PDF がリニアライズされていない)』の表示は、単なる情報でした。

PDF の破損とは関係ないので、無視して大丈夫です。

また、『エラーは見つからなかったけど、qpdf の検出できないエラーが、まだ含まれているかもしれない。』というむねの表示がありますが、これも無視して大丈夫です。

たぶんですが、『qpdf.exe のチェックは完璧なものじゃないよ』という注意を、念のために表示しているんだと思います。

PDF Version: 1.5
File is not encrypted
File is not linearized
No syntax or stream encoding errors found; the file may still contain
errors that qpdf cannot detect
ok - 0 - sample.pdf
デバッグ (exe_file) F:\*****\qpdf\bin\qpdf.exe
デバッグ (pdf_file) F:\*****\sample.pdf
デバッグ (cp.returncode) 0
end

PDF にエラーがあった時

PDF にエラーがあった時の表示例です。

いろいろな警告 (WARNING) を検出したのちに、エラーを検出していました。

ほかにもいろいろなパターンがありました。

WARNING: F:\*****\sample.pdf: can't find PDF header
WARNING: F:\*****\sample.pdf: file is damaged
WARNING: F:\*****\sample.pdf: can't find startxref
WARNING: F:\*****\sample.pdf: Attempting to reconstruct cross-reference table
F:\*****\sample.pdf: unable to find trailer dictionary while recovering
damaged file
error - 2 - sample.pdf
デバッグ (exe_file) F:\*****\qpdf\bin\qpdf.exe
デバッグ (pdf_file) F:\*****\sample.pdf
デバッグ (cp.returncode) 2
end

PDF に警告があった時

PDF にエラーは検出されなかったが、警告はあった時の表示例です。

ほかにもいろいろなパターンがありました。

PDF Version: 1.5
File is not encrypted
File is not linearized
WARNING: F:\*****\sample.pdf (object 2 0, offset 27): unknown token while
reading object; treating as string
WARNING: F:\*****\sample.pdf (object 2 0, offset 28): expected endobj
WARNING: page object 1 0:  object is supposed to be a stream or an array
of streams but is neither
warning - 3 - sample.pdf
デバッグ (exe_file) F:\*****\qpdf\bin\qpdf.exe
デバッグ (pdf_file) F:\*****\sample.pdf
デバッグ (cp.returncode) 3
end
タイトルとURLをコピーしました