Python, pypdfでPDFの作成者やタイトルなどを取得・削除・変更

Modified: | Tags: Python, PDF

Pythonのサードパーティライブラリpypdf(旧PyPDF2)を使うと、PDFファイルのメタデータ(作成者、タイトルなど)の取得や削除、変更ができる。

ここでは以下の項目について説明する。

  • pypdfのインストール
  • PDFファイルのメタデータの項目
  • PDFファイルのメタデータを取得
  • PDFファイルのメタデータを削除
    • メタデータをすべて削除
    • メタデータを選択して削除
  • PDFファイルのメタデータを追加・変更
  • パスワードが設定されたPDFファイルの処理
  • XMPによるメタデータを取得(PDF 2.0)

サンプルで使用しているPDFファイルは以下のリンクから。暗号化されているファイルのパスワードはすべてpassword

すべてのPDFファイルに対して動作を保証するものではない。

pypdfのインストール

pypdfは外部ライブラリに依存しておらず、pippip3)でインストール可能。AES方式の復号を利用する場合は下のようにpypdf[crypto]とする。

$ pip install pypdf
$ pip install pypdf[crypto]

以下のサンプルコードで使用しているpypdfのバージョンは3.7.1

以前はPyPDF2という名前だったが、2023年にpypdfに改められた。

PDFファイルのメタデータの項目

PDFファイルの規格はISOで標準化されている。

ISO 32000-1 (PDF 1.7)は無料で公開されており、メタデータについては下記ファイルの「14.3 Metadata」(P548)で説明されている。

メタデータはMetadata StreamsDocument Information Dictionaryに格納される。Document Information Dictionaryのメタデータの項目は以下の通り。

  • Title: タイトル
  • Author: 作成者
  • Subject: サブジェクト(主題)
  • Keyword: キーワード
  • Creator: オリジナル文書の作成ツール
  • Producer: 変換ツール
  • CreateDate: 作成日時
  • ModDate: 更新日時
  • Trapped: トラッピングされているか

これらの項目は必須ではなく、また、その他の独自の項目を追加することも可能。

Author, Creator, Producerは似たような項目名だが、文書を作成した個人や団体の名前が入るのがAuthorで、Creator, ProducerにはPDFファイルの作成・変換に使用されたソフトの名前が入る。具体的な例は後述。

なお、2017年7月に公開されたISO 32000-2 (PDF 2.0)でメタデータに関する仕様が変更され、メタデータはExtensible Metadata PlatformXMP)に格納するようになった。

以下のサンプルコードでは、2019年1月時点で多くのファイルが対応しているPDF 1.7以前の仕様に対する処理をメインで説明する。PDF 2.0に対する処理は最後に少しだけ述べる。

PDFファイルのメタデータの取得

pypdfのPdfReaderクラスのmetadata属性で、PDFファイルのDocument Information Dictionaryの情報を抽出し取得できる。

PdfReaderはコンストラクタにPDFファイルのパスを指定して生成する。metadata属性で取得できるのはpypdf.DocumentInformationクラスのオブジェクト。

import pypdf

print(pypdf.__version__)
# 3.7.1

pdf = pypdf.PdfReader('data/src/pdf/sample1.pdf')

print(type(pdf.metadata))
# <class 'pypdf._reader.DocumentInformation'>

pypdf.DocumentInformationクラスは辞書(dict)のサブクラスなので、[キー名]で値を取得したりkeys()などのメソッドを使ったりできる。

ファイルによっては、そのままprint()で出力すると値がIndirectObject(...)となる場合があるが、キーを指定すると中身が確認できる。なお、このサンプルファイルはMacのKeynoteで作成、PDFに変換したもの。

print(isinstance(pdf.metadata, dict))
# True

print(pdf.metadata)
# {'/Title': IndirectObject(33, 0, 4360812432), '/Producer': IndirectObject(34, 0, 4360812432), '/Creator': IndirectObject(35, 0, 4360812432), '/CreationDate': IndirectObject(36, 0, 4360812432), '/ModDate': IndirectObject(36, 0, 4360812432)}

print(pdf.metadata['/Title'])
# sample1

for k in pdf.metadata.keys():
    print(k, ':', pdf.metadata[k])
# /Title : sample1
# /Producer : macOS バージョン10.14.2(ビルド18C54) Quartz PDFContext
# /Creator : Keynote
# /CreationDate : D:20190114072947Z00'00'
# /ModDate : D:20190114072947Z00'00'

キー名は'/Title'のようにスラッシュ/が付き、最初が大文字なので注意。

PDFファイルのメタデータを削除

メタデータをすべて削除

以下のような流れでPDFファイルのメタデータを削除して保存できる。

  1. 元のPDFファイルからPdfReaderオブジェクトを生成
  2. 空のPdfWriterオブジェクトを作成
  3. PdfReaderオブジェクトの中身をコピー
  4. PdfWriterオブジェクトをPDFファイルとして保存

PdfReaderPdfWriterを生成し、clone_reader_document_root()で元ファイルの内容をコピーしてから保存する。

src_pdf = pypdf.PdfReader('data/src/pdf/sample1.pdf')
dst_pdf = pypdf.PdfWriter()

dst_pdf.clone_reader_document_root(src_pdf)
dst_pdf.write('data/temp/sample1_no_meta.pdf')

clone_reader_document_root()ではメタデータはコピーされないので、保存したPDFファイルは元ファイルからメタデータの情報が削除されたものになる。

出力ファイルのメタデータを確認すると以下のようになる。Producer(変換ツール)の項目がpypdfとなり、それ以外のメタデータが削除されていることが確認できる。

print(pypdf.PdfReader('data/temp/sample1_no_meta.pdf').metadata)
# {'/Producer': 'pypdf'}

Producerの項目を変更したい場合はPdfWriteradd_metadata()メソッドを使う。空にしたい場合は以下の通り。Producerの項目そのものを削除することはたぶん出来ない。

dst_pdf.add_metadata({'/Producer': ''})
dst_pdf.write('data/temp/sample1_no_meta.pdf')
print(pypdf.PdfReader('data/temp/sample1_no_meta.pdf').metadata)
# {'/Producer': ''}

関数化した例

関数化すると以下のようになる。

def remove_all_metadata(src_path, dst_path, producer=''):
    src_pdf = pypdf.PdfReader(src_path)
    dst_pdf = pypdf.PdfWriter()
    dst_pdf.clone_reader_document_root(src_pdf)
    dst_pdf.add_metadata({'/Producer': producer})
    dst_pdf.write(dst_path)

remove_all_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_no_meta.pdf')
print(pypdf.PdfReader('data/temp/sample1_no_meta.pdf').metadata)
# {'/Producer': ''}

例では、関数のデフォルト引数を使ってProducerの項目にはデフォルトで空文字列''が入るようにしている。

メタデータを選択して削除

メタデータの項目を選択して削除したい場合は、元のPDFファイルのmetadata属性を取得して任意の項目を削除してからadd_metadata()メソッドで追加すればよい。

metadata属性の値がIndirectObject(...)である場合、そのままadd_metadata()メソッドの引数に指定するとエラーになるため、ここでは辞書内包表記で新たな辞書を生成する(もっといい方法があるかもしれない)。

src_pdf = pypdf.PdfReader('data/src/pdf/sample1.pdf')
dst_pdf = pypdf.PdfWriter()
dst_pdf.clone_reader_document_root(src_pdf)

d = {key: src_pdf.metadata[key] for key in src_pdf.metadata.keys()}
print(d)
# {'/Title': 'sample1', '/Producer': 'macOS バージョン10.14.2(ビルド18C54) Quartz PDFContext', '/Creator': 'Keynote', '/CreationDate': "D:20190114072947Z00'00'", '/ModDate': "D:20190114072947Z00'00'"}

辞書の要素はpop()delで削除できる。例としてCreator, Producerを削除する。

d.pop('/Creator')
d.pop('/Producer')
print(d)
# {'/Title': 'sample1', '/CreationDate': "D:20190114072947Z00'00'", '/ModDate': "D:20190114072947Z00'00'"}

これをPdfWriterオブジェクトのadd_metadata()メソッドで追加し、最後にwrite()メソッドでpdfファイルとして保存する。

dst_pdf.add_metadata(d)
dst_pdf.write('data/temp/sample1_remove_meta.pdf')

出力ファイルのメタデータは以下の通り。Producerの項目が削除された場合はpypdfが使われる。任意の値を設定したい場合はadd_metadata()メソッドに指定する辞書に要素を追加すればよい。

print(pypdf.PdfReader('data/temp/sample1_remove_meta.pdf').metadata)
# {'/Producer': 'pypdf', '/Title': 'sample1', '/CreationDate': "D:20190114072947Z00'00'", '/ModDate': "D:20190114072947Z00'00'"}

関数化した例

関数化すると以下のようになる。削除するメタデータの項目を指定する。

def remove_metadata(src_path, dst_path, *args, producer=''):
    src_pdf = pypdf.PdfReader(src_path)
    dst_pdf = pypdf.PdfWriter()
    dst_pdf.clone_reader_document_root(src_pdf)

    d = {key: src_pdf.metadata[key] for key in src_pdf.metadata.keys()
         if key not in args}
    d.setdefault('/Producer', producer)

    dst_pdf.add_metadata(d)
    dst_pdf.write(dst_path)

可変長引数と辞書のsetdefault()メソッドを利用している。

Producerの項目が残っている場合はその値が、削除した場合はデフォルトでは空文字列''が設定される。キーワード引数producerで任意の値を設定することも可能。

remove_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_no_meta.pdf',
                '/Creator', '/ModDate', '/CreationDate')
print(pypdf.PdfReader('data/temp/sample1_no_meta.pdf').metadata)
# {'/Producer': 'macOS バージョン10.14.2(ビルド18C54) Quartz PDFContext', '/Title': 'sample1'}

remove_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_no_meta.pdf',
                '/Creator', '/ModDate', '/CreationDate', '/Producer')
print(pypdf.PdfReader('data/temp/sample1_no_meta.pdf').metadata)
# {'/Producer': '', '/Title': 'sample1'}

remove_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_no_meta.pdf',
                '/Creator', '/ModDate', '/CreationDate', '/Producer',
                producer='XXX')
print(pypdf.PdfReader('data/temp/sample1_no_meta.pdf').metadata)
# {'/Producer': 'XXX', '/Title': 'sample1'}

削除するメタデータの項目ではなく残すメタデータの項目を指定する場合は以下のようになる。辞書内包表記の条件を変更するだけ。こっちのほうが実用的かもしれない。

def select_metadata(src_path, dst_path, *args, producer=''):
    src_pdf = pypdf.PdfReader(src_path)
    dst_pdf = pypdf.PdfWriter()
    dst_pdf.clone_reader_document_root(src_pdf)

    d = {key: src_pdf.metadata[key] for key in src_pdf.metadata.keys()
         if key in args}
    d.setdefault('/Producer', producer)

    dst_pdf.add_metadata(d)
    dst_pdf.write(dst_path)

使用例は以下の通り。

select_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_no_meta.pdf',
                '/Title', '/ModDate')
print(pypdf.PdfReader('data/temp/sample1_no_meta.pdf').metadata)
# {'/Producer': '', '/Title': 'sample1', '/ModDate': "D:20190114072947Z00'00'"}

select_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_no_meta.pdf',
                '/Title', '/Producer')
print(pypdf.PdfReader('data/temp/sample1_no_meta.pdf').metadata)
# {'/Producer': 'macOS バージョン10.14.2(ビルド18C54) Quartz PDFContext', '/Title': 'sample1'}

PDFファイルのメタデータを追加・変更

メタデータの項目を選択して追加したり変更したい場合は、元のPDFファイルのmetadata属性を取得して所望のキーの値を追加・変更してからadd_metadata()メソッドで追加すればよい。

基本的には削除の場合と同じ流れ。metadataで取得したオブジェクトを操作するだけ。

src_pdf = pypdf.PdfReader('data/src/pdf/sample1.pdf')
dst_pdf = pypdf.PdfWriter()

dst_pdf.clone_reader_document_root(src_pdf)

d = {key: src_pdf.metadata[key] for key in src_pdf.metadata.keys()}

d['/Title'] = 'new title'
d['/Producer'] = 'new producer'
d['/XXX'] = 'special data'

dst_pdf.add_metadata(d)
dst_pdf.write('data/temp/sample1_new_meta.pdf')

print(pypdf.PdfReader('data/temp/sample1_new_meta.pdf').metadata)
# {'/Producer': 'new producer', '/Title': 'new title', '/Creator': 'Keynote', '/CreationDate': "D:20190114072947Z00'00'", '/ModDate': "D:20190114072947Z00'00'", '/XXX': 'special data'}

関数化した例

関数化すると以下のようになる。追加・変更したいメタデータの情報を辞書で引数に指定する。

元のメタデータを保持する場合。辞書のupdate()メソッドを使う。

def update_metadata(src_path, dst_path, metadata):
    src_pdf = pypdf.PdfReader(src_path)
    dst_pdf = pypdf.PdfWriter()
    dst_pdf.clone_reader_document_root(src_pdf)

    d = {key: src_pdf.metadata[key] for key in src_pdf.metadata.keys()}
    d.update(metadata)

    dst_pdf.add_metadata(d)
    dst_pdf.write(dst_path)

update_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_new_meta.pdf',
                {'/Title': 'new title', '/Author': 'new author', '/Producer': ''})
print(pypdf.PdfReader('data/temp/sample1_new_meta.pdf').metadata)
# {'/Producer': '', '/Title': 'new title', '/Creator': 'Keynote', '/CreationDate': "D:20190114072947Z00'00'", '/ModDate': "D:20190114072947Z00'00'", '/Author': 'new author'}

元のメタデータを削除する場合。

def set_metadata(src_path, dst_path, metadata):
    src_pdf = pypdf.PdfReader(src_path)
    dst_pdf = pypdf.PdfWriter()
    dst_pdf.clone_reader_document_root(src_pdf)
    dst_pdf.add_metadata(metadata)
    dst_pdf.write(dst_path)

set_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_new_meta.pdf',
             {'/Title': 'new title', '/Author': 'new author', '/Producer': ''})
print(pypdf.PdfReader('data/temp/sample1_new_meta.pdf').metadata)
# {'/Producer': '', '/Title': 'new title', '/Author': 'new author'}

パスワードが設定されたPDFファイルの処理

パスワード付きの暗号化されたPDFファイルの場合、これまでのサンプルコードではエラーとなる。

PdfReaderオブジェクトを生成したあとでdecrypt()メソッドで復号する必要がある。

src_pdf = pypdf.PdfReader(src_path)
src_pdf.decrypt('password')

また、保存するPDFファイルにパスワードを掛ける場合はPdfWriterオブジェクトのwrite()で保存する前にencrypt()メソッドを使う。

dst_pdf.decrypt('password')

パスワードについての詳細は以下の記事を参照。

XMPによるメタデータを取得(PDF 2.0)

上述のように、2017年7月に公開されたISO 32000-2 (PDF 2.0)でメタデータに関する仕様が変更され、メタデータはExtensible Metadata PlatformXMP)に格納するようになった。

以下のレポジトリにあるPDF 2.0のサンプルファイルを使って、XMPで格納されたデータを取得する例を紹介する。

metadata属性はNone

pdf = pypdf.PdfReader('data/temp/Simple PDF 2.0 file.pdf')
print(pdf.metadata)
# None

XMPのデータはxmp_metadata属性で取得できる。xmp_metadata属性はXmpInformationクラス。詳細は以下のドキュメント参照。

xmp_metadata属性から各種の情報を取得できる。

print(type(pdf.xmp_metadata))
# <class 'pypdf.xmp.XmpInformation'>

print(pdf.xmp_metadata.dc_title)
# {'x-default': 'A simple PDF 2.0 example file'}

print(pdf.xmp_metadata.pdf_keywords)
# PDF 2.0 sample example

print(pdf.xmp_metadata.xmp_metadata_date)
# 2017-07-11 07:55:11

PdfWriterクラスのドキュメントを読む限りadd_metadata()に相当するようなメソッドはないので、新たなXMPを追加することは出来ない模様(たぶん)。

関連カテゴリー

関連記事