🐻‍❄️

しおりのないPDFに半自動でしおりを付けるPython製コマンドラインツール

に公開

背景

PDFを閲覧している場合,しおり[1]があると便利である。
現在,PDFを作成するさまざまなツールでしおりを付与することができるが,古いPDFや単に作成者が忘れた場合など,必ずしも付いているとは限らない。

しおりは作成者だけが付与できるわけではなく,別のユーザにも可能であるし,すでにしおりが存在している場合にはそれを編集することもできる(たとえば,Acrobatのしおり機能を使えばよい)。

しかし,対象のPDF文書の見出しをひとつひとつ手でコピペしたり手入力したりでやっていくのはきわめて煩雑である。
そこで、しおりのないPDFの本文情報からしおり情報を自動的に生成し、(もちろんそれだけで完璧なものは基本できないはずなので)しおり情報を編集可能なテキストでいったん出力し、編集後のそれを反映させたPDFを出力させるコマンドラインツールを作ってみた。

類似の機能をもつツールはすでにあり(後述),それぞれ興味深い。
ここでは、コマンドラインで

  • しおり情報があればそれを、なければPDFをPyMuPDF4LLMで読み込んでしおり情報をMarkdownで「自動生成」するコマンド
  • 上記ステップで生成されたしおり情報が適宜編集してPDFに反映させるコマンド

の2段階をもうけ、それをPythonパッケージの形で実現した。

下記リポジトリにアップロードしている。

https://github.com/misgnros/pdf-semiauto-bookmark

インストール

各環境で適切なやり方でpip installする。PyPIにあげたりとかはしていない。

pip install git+https://github.com/misgnros/pdf-semiauto-bookmark.git

使い方

以下のように対象ファイルがディレクトリにあるとする。

.
└── input.pdf

しおりの抽出

以下のコマンドでinput.pdfのしおりを抽出できる。

autobm read input.pdf

このステップの後、しおり情報を含むMarkdwonファイルinput.mdが生成される。

.
├─ input.md
└─ input.pdf
処理の詳細

指定されたファイルにすでにしおりがあるかどうかをPyMuPDFのget_toc()で取得して確認し、あればそれを下記形式のMarkdownに整形して出力する。なければPyMuPDF4LLMで全文テキストデータをメタデータ付きで取得し(ページ情報が必要なので)、下記形式のMarkdownに整形して出力する。

出力されるMarkdownの形式は以下のようにしてみた。

  • フロントマターには、しおりとして反映される見出しの集計情報
  • ページ数の記載後、そのページにある見出しを深さとともに記載
---
number of #: 8
number of ##: 14
---

<!-- page 2 -->
# 1 Introduction
<!-- page 4 -->
# 2 Chapter
## 2.1 Section
<!-- page 10 -->
## 2.2 Section

この形式に整形するうえで、一番高いランク(=Markdown記法だと#の数が少ない)の見出しが何であってもそのランクを#として、その他もランクの大小関係を保ちつつ#の数を減らすようにしている(後にPDFにしおりとして反映させるとき、エラーを起きにくくするため)。

つまり、最初読み込まれた結果が

## aaa
### bbb
## ccc
#### ddd

であった場合、これが

# aaa
## bbb
# ccc
### ddd

となるように調整している。

ブックマークの設定

生成されたMarkdownファイルは自由に編集することができる。

Markdownファイルが適切な形式であれば、以下のコマンドでしおりを設定することができる。

autobm write input.md

このステップの後、しおりが反映されたPDFファイルinput_with_bookmark.pdfが生成される。

.
├── input.md
├── input.pdf
└── input_with_bookmark.pdf
処理の詳細

編集後のMarkdownを所定の形式のリストに変換し、PyMuPDFのset_toc()でPDFに反映させる。

ここで、最初の見出しのランクが#でないとPyMuPDFのset_toc()するところでエラーが起きる。
先述の見出し調整では大小関係を保ったままだったので、最初の見出しランクが例えば##で、かつ以降に見出しランク#が存在する場合などに引っかかってしまう。
したがってこのような状況では、手動で適切に調整する必要がある。

コメント

なぜPyMuPDF4LLM?

LLM時代だから、というくらいの理由に結局はなる。PDFからのテキスト抽出には常に困難さが伴い、何がベストかは一概には言えない。後述の関連ツールにはPDFの構造から見出しを特定するものもある。

体感では、PyMuPDF4LLMの精度は決してベストとは言えない。ある程度きれいに作られているPDF文書ならばよいが、特に古いものはうまく文字抽出ができないのか、読み込んでも空っぽのMarkdownが出力されることも多い。

究極的には、PDFの構造を考慮し、文字抽出がうまくいかないようであれば自動的にOCRするようなものが実装されるといいのだろうか。

なぜMarkdown?

見出しテキストとその見出しがあるページ数のセットというデータなので、データ然とした形式にするのが良いのかもしれないが、直接ユーザが編集できることを前提としていたので、可読性を重視したいと考えた。しかしこれがベストだったかは不明。

TODO

  • コードをもう少しきっちりさせる
  • 読み込み部分の精度を上げるための代替手段(モデル)考案
  • 最初の見出しランクが#じゃないときの対処
  • 全文抽出してそれをLLMに投げ見出しを自動生成させるいい感じのフロー(本来はこうしたい)

既存の類似ツール

製品としては以下のようなものがある。
https://www.antenna.co.jp/pdf/reference/pdf-shiori.html

他にも、無料で使えるPDFリーダーFoxit PDF ReaderにAIによるしおり自動生成機能があり、2025年4月ごろに実装されていたらしい。Adobeのリーダーに同様の機能が(おそらく)ないのが意外。
https://www.foxit.com/pdf-reader/version-history.html

PDFの構造から自動的にしおり情報を付与するCLIツール。こちらで自由にしおりを編集できるステップはない。
https://github.com/hueyy/pdf_scout/

自動生成はしない、つまりあらかじめしおり情報が準備されている状態で使用するタイプのCLIツール。
https://github.com/yutayamamoto/pdf-yaml-bookmark/

脚注
  1. ブックマークやアウトラインなどとも呼ばれる。PDF1.7の仕様には,"outline items (sometimes called bookmarks)"とある。日本語では,https://helpx.adobe.com/jp/acrobat/kb/cq06151533.html などを見ると「しおり」が主流か。 ↩︎

Discussion