Python, PyPDF2でPDFの作成者やタイトルなどを取得・削除・変更
PythonのサードパーティライブラリPyPDF2を使うと、PDFファイルのメタデータ(作成者、タイトルなど)の取得や削除、変更ができる。
ここでは以下の項目について説明する。
- PyPDF2のインストール
- PDFファイルのメタデータの項目
- PDFファイルのメタデータを取得
- PDFファイルのメタデータを削除
- メタデータをすべて削除
- メタデータを選択して削除
- PDFファイルのメタデータを追加・変更
- パスワードが設定されたPDFファイルの処理
- XMPによるメタデータを取得(PDF 2.0)
サンプルで使用しているPDFファイルは以下のリンクから。暗号化されているファイルのパスワードはすべてpassword
。
すべてのPDFファイルに対して動作を保証するものではない。
PyPDF2のインストール
PyPDF2は外部ライブラリに依存していない。pip
(pip3
)やconda
でインストール可能。
$ pip install PyPDF2
以下のサンプルコードで使用しているPyPDF2のバージョンは1.26.0
。
クラスやメソッドなどの詳細は公式ドキュメントを参照。
IssueやPull Requestが溜まっており活発に開発されているという状況ではないが、シンプルなPDFファイルの処理であれば問題ない。
PDFファイルのメタデータの項目
PDFファイルの規格はISOで標準化されている。
ISO 32000-1 (PDF 1.7)は無料で公開されており、メタデータについては下記ファイルの「14.3 Metadata」(P548)で説明されている。
メタデータはMetadata Streams
かDocument 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 Platform
(XMP
)に格納するようになった。
以下のサンプルコードでは、2019年1月時点で多くのファイルが対応しているPDF 1.7以前の仕様に対する処理をメインで説明する。PDF 2.0に対する処理は最後に少しだけ述べる。
PDFファイルのメタデータの取得
PyPDF2のPdfFileReader
クラスのdocumentInfo
属性で、PDFファイルのDocument Information Dictionary
の情報を抽出し取得できる。
PdfFileReader
はコンストラクタにPDFファイルのパスを指定して生成する。documentInfo
属性で取得できるのはPyPDF2.pdf.DocumentInformation
クラスのオブジェクト。
import PyPDF2
pdf = PyPDF2.PdfFileReader('data/src/pdf/sample1.pdf')
print(type(pdf.documentInfo))
# <class 'PyPDF2.pdf.DocumentInformation'>
print(isinstance(pdf.documentInfo, dict))
# True
PyPDF2.pdf.DocumentInformation
クラスは辞書(dict
)のサブクラスなので、[キー名]
で値を取得したりkey()
などのメソッドを使ったりできる。
ファイルによっては、そのままprint()
で出力すると値がIndirectObject(...)
となる場合があるが、キーを指定すると中身が確認できる。なお、このサンプルファイルはMacのKeynoteで作成、PDFに変換したもの。
print(pdf.documentInfo)
# {'/Title': IndirectObject(33, 0), '/Producer': IndirectObject(34, 0), '/Creator': IndirectObject(35, 0), '/CreationDate': IndirectObject(36, 0), '/ModDate': IndirectObject(36, 0)}
print(pdf.documentInfo['/Title'])
# sample1
for k in pdf.documentInfo.keys():
print(k, ':', pdf.documentInfo[k])
# /Title : sample1
# /Producer : macOS バージョン10.14.2(ビルド18C54) Quartz PDFContext
# /Creator : Keynote
# /CreationDate : D:20190114072947Z00'00'
# /ModDate : D:20190114072947Z00'00'
キー名は'/Title'
のようにスラッシュ/
が付き、最初が大文字なので注意。
PDFファイルのメタデータを削除
メタデータをすべて削除
以下のような流れでPDFファイルのメタデータを削除して保存できる。
- 元のPDFファイルから
PdfFileReader
オブジェクトを生成 - 空の
PdfFileWriter
オブジェクトを作成 PdfFileReader
オブジェクトの中身をコピーPdfFileWriter
オブジェクトをPDFファイルとして保存
PdfFileReader
とPdfFileWriter
をそれぞれのコンストラクタから生成。
import PyPDF2
src_pdf = PyPDF2.PdfFileReader('data/src/pdf/sample1.pdf')
dst_pdf = PyPDF2.PdfFileWriter()
cloneReaderDocumentRoot()
でドキュメントの内容をコピー。
dst_pdf.cloneReaderDocumentRoot(src_pdf)
cloneReaderDocumentRoot()
ではメタデータはコピーされないので、このPdfFileWriter
をwrite()
メソッドでPDFファイルとして保存すればメタデータの情報は削除される。
PdfFileWriter
のwrite()
の引数はパス文字列ではなくファイルオブジェクトである必要があるのでopen()
を使う。wb
で書き込み用のバイナリファイルとしてオープンする。
with open('data/temp/sample1_no_meta.pdf', 'wb') as f:
dst_pdf.write(f)
出力ファイルのメタデータを確認すると以下のようになる。Producer
(変換ツール)の項目がPyPDF2
となり、それ以外のメタデータが削除されていることが確認できる。
print(PyPDF2.PdfFileReader('data/temp/sample1_no_meta.pdf').documentInfo)
# {'/Producer': 'PyPDF2'}
Producer
の項目を変更したい場合はPdfFileWriter
のaddMetadata()
メソッドを使う。空にしたい場合は以下の通り。Producer
の項目そのものを削除することはたぶん出来ない。
dst_pdf.addMetadata({'/Producer': ''})
with open('data/temp/sample1_no_meta.pdf', 'wb') as f:
dst_pdf.write(f)
print(PyPDF2.PdfFileReader('data/temp/sample1_no_meta.pdf').documentInfo)
# {'/Producer': ''}
関数化すると以下のようになる。
def remove_all_metadata(src_path, dst_path, producer=''):
src_pdf = PyPDF2.PdfFileReader(src_path)
dst_pdf = PyPDF2.PdfFileWriter()
dst_pdf.cloneReaderDocumentRoot(src_pdf)
dst_pdf.addMetadata({'/Producer': producer})
with open(dst_path, 'wb') as f:
dst_pdf.write(f)
remove_all_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_no_meta.pdf')
print(PyPDF2.PdfFileReader('data/temp/sample1_no_meta.pdf').documentInfo)
# {'/Producer': ''}
例では、関数のデフォルト引数を使って、Producer
の項目にはデフォルトで空文字列''
が入るようにしている。
メタデータを選択して削除
メタデータの項目を選択して削除したい場合は、元のPDFファイルのdocumentInfo
属性を取得して任意の項目を削除してからaddMetadata()
メソッドで追加すればよい。
documentInfo
属性の値がIndirectObject(...)
である場合、そのままaddMetadata()
メソッドの引数に指定するとエラーになるため、ここでは辞書内包表記で新たな辞書を生成する(もっといい方法があるかもしれない)。
src_pdf = PyPDF2.PdfFileReader('data/src/pdf/sample1.pdf')
dst_pdf = PyPDF2.PdfFileWriter()
d = {key: src_pdf.documentInfo[key] for key in src_pdf.documentInfo.keys()}
print(d)
# {'/Title': 'sample1', '/Producer': 'macOS バージョン10.14.2(ビルド18C54) Quartz PDFContext', '/Creator': 'Keynote', '/CreationDate': "D:20190114072947Z00'00'", '/ModDate': "D:20190114072947Z00'00'"}
上述のようにPyPDF2.pdf.DocumentInformation
は辞書(dict
)のサブクラスなのでpop()
やdel
で要素を削除できる。例としてCreator
, Producer
を削除する。
d.pop('/Creator')
d.pop('/Producer')
print(d)
# {'/Title': 'sample1', '/CreationDate': "D:20190114072947Z00'00'", '/ModDate': "D:20190114072947Z00'00'"}
これをPdfFileWriter
オブジェクトのaddMetadata()
メソッドで追加し、最後にwrite()
メソッドを使用してpdfファイルとして保存する。
dst_pdf.addMetadata(d)
with open('data/temp/sample1_remove_meta.pdf', 'wb') as f:
dst_pdf.write(f)
出力ファイルのメタデータを確認すると以下のようになる。Producer
の項目が削除された場合はデフォルトのPyPDF2
が使われる。任意の値を設定したい場合はaddMetadata()
メソッドに指定する辞書に要素を追加すればOK。
print(PyPDF2.PdfFileReader('data/temp/sample1_remove_meta.pdf').documentInfo)
# {'/Producer': 'PyPDF2', '/Title': 'sample1', '/CreationDate': "D:20190114072947Z00'00'", '/ModDate': "D:20190114072947Z00'00'"}
関数化すると以下のようになる。
def remove_metadata(src_path, dst_path, *args, producer=''):
src_pdf = PyPDF2.PdfFileReader(src_path)
dst_pdf = PyPDF2.PdfFileWriter()
dst_pdf.cloneReaderDocumentRoot(src_pdf)
d = {key: src_pdf.documentInfo[key] for key in src_pdf.documentInfo.keys()
if key not in args}
d.setdefault('/Producer', producer)
dst_pdf.addMetadata(d)
with open(dst_path, 'wb') as f:
dst_pdf.write(f)
可変長引数と辞書のsetdefault()
メソッドを利用している。
Producer
の項目が残っている場合はその値が、削除した場合はデフォルトでは空文字列''
が設定される。キーワード引数producer
で任意の値を設定することも可能。
remove_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_no_meta.pdf',
'/Creator', '/ModDate', '/CreationDate')
print(PyPDF2.PdfFileReader('data/temp/sample1_no_meta.pdf').documentInfo)
# {'/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(PyPDF2.PdfFileReader('data/temp/sample1_no_meta.pdf').documentInfo)
# {'/Producer': '', '/Title': 'sample1'}
remove_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_no_meta.pdf',
'/Creator', '/ModDate', '/CreationDate', '/Producer', producer='XXX')
print(PyPDF2.PdfFileReader('data/temp/sample1_no_meta.pdf').documentInfo)
# {'/Producer': 'XXX', '/Title': 'sample1'}
削除するメタデータの項目ではなく残すメタデータの項目を指定する場合は以下のようになる。辞書内包表記の条件を変更するだけ。こっちのほうが実用的かもしれない。
def select_metadata(src_path, dst_path, *args, producer=''):
src_pdf = PyPDF2.PdfFileReader(src_path)
dst_pdf = PyPDF2.PdfFileWriter()
dst_pdf.cloneReaderDocumentRoot(src_pdf)
d = {key: src_pdf.documentInfo[key] for key in src_pdf.documentInfo.keys()
if key in args}
d.setdefault('/Producer', producer)
dst_pdf.addMetadata(d)
with open(dst_path, 'wb') as f:
dst_pdf.write(f)
使用例は以下の通り。
select_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_no_meta.pdf',
'/Title', '/ModDate')
print(PyPDF2.PdfFileReader('data/temp/sample1_no_meta.pdf').documentInfo)
# {'/Producer': '', '/Title': 'sample1', '/ModDate': "D:20190114072947Z00'00'"}
select_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_no_meta.pdf',
'/Title', '/Producer')
print(PyPDF2.PdfFileReader('data/temp/sample1_no_meta.pdf').documentInfo)
# {'/Producer': 'macOS バージョン10.14.2(ビルド18C54) Quartz PDFContext', '/Title': 'sample1'}
PDFファイルのメタデータを追加・変更
メタデータの項目を選択して追加したり変更したい場合は、元のPDFファイルのdocumentInfo
属性を取得して所望のキーの値を追加・変更してからaddMetadata()
メソッドで追加すればよい。
基本的には削除の場合と同じ流れ。documentInfo
で取得したオブジェクトを操作するだけ。
import PyPDF2
src_pdf = PyPDF2.PdfFileReader('data/src/pdf/sample1.pdf')
dst_pdf = PyPDF2.PdfFileWriter()
dst_pdf.cloneReaderDocumentRoot(src_pdf)
d = {key: src_pdf.documentInfo[key] for key in src_pdf.documentInfo.keys()}
d['/Title'] = 'new title'
d['/Author'] = 'new author'
d['/XXX'] = 'special data'
dst_pdf.addMetadata(d)
with open('data/temp/sample1_new_meta.pdf', 'wb') as f:
dst_pdf.write(f)
print(PyPDF2.PdfFileReader('data/temp/sample1_new_meta.pdf').documentInfo)
# {'/Producer': 'macOS バージョン10.14.2(ビルド18C54) Quartz PDFContext', '/Title': 'new title', '/Creator': 'Keynote', '/CreationDate': "D:20190114072947Z00'00'", '/ModDate': "D:20190114072947Z00'00'", '/Author': 'new author', '/XXX': 'special data'}
関数化すると以下のようになる。追加・変更したいメタデータの情報を辞書で引数に指定する。
元のメタデータを保持する場合。辞書のupdate()
メソッドを使う。
def update_metadata(src_path, dst_path, metadata):
src_pdf = PyPDF2.PdfFileReader(src_path)
dst_pdf = PyPDF2.PdfFileWriter()
dst_pdf.cloneReaderDocumentRoot(src_pdf)
d = {key: src_pdf.documentInfo[key] for key in src_pdf.documentInfo.keys()}
d.update(metadata)
dst_pdf.addMetadata(d)
with open(dst_path, 'wb') as f:
dst_pdf.write(f)
update_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_new_meta.pdf',
{'/Title': 'new title', '/Author': 'new author', '/Producer': ''})
print(PyPDF2.PdfFileReader('data/temp/sample1_new_meta.pdf').documentInfo)
# {'/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 = PyPDF2.PdfFileReader(src_path)
dst_pdf = PyPDF2.PdfFileWriter()
dst_pdf.cloneReaderDocumentRoot(src_pdf)
dst_pdf.addMetadata(metadata)
with open(dst_path, 'wb') as f:
dst_pdf.write(f)
set_metadata('data/src/pdf/sample1.pdf', 'data/temp/sample1_new_meta.pdf',
{'/Title': 'new title', '/Author': 'new author', '/Producer': ''})
print(PyPDF2.PdfFileReader('data/temp/sample1_new_meta.pdf').documentInfo)
# {'/Producer': '', '/Title': 'new title', '/Author': 'new author'}
パスワードが設定されたPDFファイルの処理
パスワード付きの暗号化されたPDFファイルの場合、これまでのサンプルコードではエラーとなる。
PdfFileReader
オブジェクトを生成したあとでdecrypt()
メソッドで復号する必要がある。
src_pdf = PyPDF2.PdfFileReader(src_path)
src_pdf.decrypt('password')
また、保存するPDFファイルにパスワードを掛ける場合はPdfFileWriter
オブジェクトのwrite()
で保存する前にencrypt()
メソッドを使う。
dst_pdf.decrypt('password')
パスワードについての詳細は以下の記事を参照。暗号化アルゴリズムはRC4のみに対応しAESに対応していないため、最近のソフトで暗号化されたファイルは解除できない場合が多いといった制約もある(バージョン1.26.0
時点)。
XMPによるメタデータを取得(PDF 2.0)
上述のように、2017年7月に公開されたISO 32000-2 (PDF 2.0)でメタデータに関する仕様が変更され、メタデータはExtensible Metadata Platform
(XMP
)に格納するようになった。
以下のレポジトリにあるPDF 2.0のサンプルファイルを使って、XMP
で格納されたデータを取得する例を紹介する。
- pdf-association/pdf20examples: PDF 2.0 example files
- https://github.com/pdf-association/pdf20examples/raw/master/Simple%20PDF%202.0%20file.pdf
documentInfo
属性はNone
。
import PyPDF2
pdf = PyPDF2.PdfFileReader('data/temp/Simple PDF 2.0 file.pdf')
print(pdf.documentInfo)
# None
XMP
のデータはxmpMetadata
属性で取得できる。xmpMetadata
属性はXmpInformation
クラス。詳細は以下のドキュメント参照。
xmpMetadata
属性から各種の情報を取得できる。
print(type(pdf.xmpMetadata))
# <class 'PyPDF2.xmp.XmpInformation'>
print(pdf.xmpMetadata.dc_title)
# {'x-default': 'A simple PDF 2.0 example file'}
print(pdf.xmpMetadata.pdf_keywords)
# PDF 2.0 sample example
print(pdf.xmpMetadata.xmp_metadataDate)
# 2017-07-11 07:55:11
PdfFileWriter
クラスのドキュメントを読んだ限りaddMetadata()
に相当するようなメソッドはないので、新たなXMP
を追加することは出来ない模様(たぶん)。