開発book-knowledge-base

Kindle for PC(デスクトップ版)で開いた本を、ページ送りからスクリーンショット、OCR、Turso DB 投入まで一気通貫で流すパイプラインを組んだ。Cloud Reader で開けない本(固定レイアウトの学術書や、PC アプリにしか降りてこない本)に届かせるための回り道。

途中で C:\Users\numbe\.git を踏み抜く事故と、OpenAI API キーを env | grep で漏らす事故も同時に処理することになって、最後はだいぶ疲れた。

まず本文 DOM を直接取れないか足掻く

きっかけは「OCR の前に、Kindle アプリの中の本文テキストがそのまま取れたら無駄が省ける」という素朴な期待。アプリの裏側を Claude Code に掘らせた。

順番にこうなった。

  • ネイティブ Windows アプリだから DOM は無いと最初に整理された
  • UIA で覗くと ReadingArea の構造とフッターの Page X of Y までは取れる。ただし本文テキストは降りてこない
  • Kindle for PC の中身を漁ったら EBWebView/appBundle.jsbridge.js といったファイルが出てきた。一瞬「React/Webpack の Web アプリで、Chromium 系の埋め込みブラウザで動いてる」と確定したように見えた
  • そのあと Qt5Core.dll / LibWebCore.dll が見つかり「Qt + WebKit 系か」と反転。さらに bridge.js の中身で再反転
  • 最終的に 旧 Kindle(Legacy)は Qt + WebKit + KRF(Kindle Reader Framework)新 Kindle(MSIX)は React Native for Windows + XAML ネイティブ UI(Hermes bytecode のバンドル magic で確定) という結論に着地した

新 Kindle に至っては XAML ネイティブで描画していて、そもそも DOM が存在しない。WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS を設定して CDP に繋いだら旧 Kindle のライブラリ画面までは見えたが、リーダー領域は最後まで空のまま。

ここで OCR 路線に確定。Cloud 版で動かしている既存の「読み解く-Kindle」スキルと同じ出力形式(~/Downloads/kindle-captures/<ASIN>/page_NNNN.pngdone.json)に揃える方針にした。

事故その1:C:\Users\numbe\.git がいた

ここで横やり。VS Code の Git タブが「1万件変更」と言ってきた。

調べていくと、犯人はホームディレクトリ直下の .git。いつ作ったか覚えがないが C:/Users/numbe/.git がいて、Git_repo/ 配下の各プロジェクトの .git/objects/ の中身が、親リポジトリから見ると未追跡ファイルとして10K件積み上がっていた、という構図。

最初の対処は素直に削除する方針で進めたが、PowerShell に -rf が無くてエラー。最終的に mv C:/Users/numbe/.git C:/Users/numbe/.git.bak でリネームして無効化。万一中身が必要になっても戻せる形にして、後始末を終わらせた。

ユーザー(自分)がやったのは「VS Code が 1 万件と言ってるんだけど」と違和感を投げたことと、mv を一発打ったこと。原因の特定と回避手順は Claude Code に組み立てさせた。

事故その2:OpenAI API キーが env | grep で漏れた

OCR 路線が決まった直後、Qt WebKit Inspector を起動できないか調査していたとき、Claude が env | grep -iE "qt|..." を打った。qt が 2 文字なので、文字列の中にたまたま qt を含む環境変数の値まで全部マッチする。 OpenAI API キーが画面に流れた。

Claude 側から「APIキー漏洩しました」と即座に申告が出た。LINE の通知トークンも一緒に流れていた。

  • OpenAI 側はキーをリボーク済み。
  • LINE は通知トークンの単体漏れなので、できることが限定的(自分のチャネルへの通知が打てる程度)。実害評価だけ済ませて止めた。
  • 後始末として、レジストリから WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS も完全削除させた。

grep の 2 文字パターンは危ない、というのを身体で覚えた。次から grep -iE "qt[a-z_]*key|qt[a-z_]*url" のように、隣接する文字も含めて絞らせる癖をつけたい。

OCR 路線:矢印キー送信から「物理マウスクリック」へ

ここから本筋の実装に戻る。

最初の設計は素直なもの。「ユーザーがスタートを押す → 自動で先頭ページに戻る → 右矢印キーを送ってページ送り → 末尾までキャプチャ → done.json 書き出し」。CLI と GUI の両方で動くようにした。

詰まったのは新 Kindle 側のページ送り。

  • 新 Kindle MSIX は SendInput でのキー入力を完全にブロックしてくる。SetForegroundWindow FAILEDSendInput VK_RIGHT returned: 0
  • PostMessage WM_KEYDOWN / SendMessage も無視。DoDefaultAction() も効かない
  • マウスの方も mouse_event(旧 API)は無視。SendInput でマウスイベントを送る方式に切り替えてもダメに見えた
  • 唯一効いたのが、ReadingArea 右端 100px の page-chevron-container-right 領域への物理クリック

ここで切り分けに苦労した。「クリックは効いているのにページが進まない」と思い込んだ瞬間が何度かあったが、実際には UIA キャッシュが古い値を返していて、ページは進んでいた。スクショを撮って目視で Location を確認したら、自分の read_page_status が返す値より先に進んでいた、という瞬間がブレイクスルーになった。

最終的にこういう構図に落ち着いた。

  • スタートと書籍切り替えは自分(人間)がやる。新 Kindle はキーもクリックも synthetic input を弾いてくるので、Library 画面の本表紙クリックや Aa 設定(1列・Light・先頭)は手で済ませる
  • キャプチャ開始のボタンを押したら、あとは Python スクリプトが ReadingArea 右端をクリックしてページを送り続ける
  • フッターの Page X of Y / Location X of Y を UIA で読み、末尾に達するか同じ位置で 5 回連続止まったら停止
  • ~/Downloads/kindle-captures/<ASIN>/page_NNNN.png を吐いて、最後に done.json を書く

ページ送りの間隔は 1.5 秒で始めて、ユーザー判断で 1.0 秒まで縮めた。固定レイアウト本は 1.0 秒で問題なく走ったが、リフロー本は 1.0 秒だとレンダリングが追いつかず no_advance で即停止することがあって、停止判定を「3 回連続で位置変化なし」に緩めた。

ローマ数字ページの罠

何冊か撮っているうちに、税理士向けの実用書系(前付に i, ii, iii, iv... のローマ数字、本文に 1, 2, 3... のアラビア数字)で詰まった。

  • _PAGE_PATTERN_ENPage\s+\d+\s+of\s+\d+ でアラビア数字しか拾わない
  • 前付の i, ii を取れないまま「進んでないと判定して停止」していた

修正は単純で、ローマ数字(大文字/小文字両対応)も Page X of Y と同じ枠で拾えるようにしてから、停止判定はアラビア数字と location 形式の本文ページに限定することにした。ローマ数字のページは「撮るだけ、止めない」。

結果、修正後の走行で「前付 i〜viii + 本文 1〜299 = 307 枚、11.5 分、status: completed」が出た。

役割分担:自分が判断する係、Python と Claude が回す係

今回はっきり線が引けた。

  • 自分: Kindle で本を開く、Aa で 1 列・Light・先頭ページを揃える、GUI の START を押す、撮れた PNG を 3 枚ほど目視で確認する、B009U4ZX1I 池上彰のお金の学校みたいに「PC アプリでも開けない本」を発見してフラグを足す指示を出す
  • Python スクリプト: ReadingArea 右端を物理クリックしてページを送り、UIA でフッターを読んで終端判定、PNG と done.json を吐く
  • Claude Code: 仕様変更(停止判定の緩和、ローマ数字対応、座標調整)、OCR を yomitoku で回し、md と figures を整理して Turso の kindle_highlights / book_chunks 系に投入、FTS の検索テストまで一気通貫

「人間が判断する係、AI が実行する係」の構図そのまま。ページ送りが効いてないように見える瞬間や、座標がズレた瞬間に違和感を拾うのは自分の役目で、修正の手は Claude が動かす。

サンプル本 16 冊と「Desktop App で開ける本」のフラグ整理

撮影パイプラインが安定したあと、Turso の kindle_library テーブルで「Cloud Reader で開けなかった本」を洗い直した。excluded_reason 列に過去の Cloud Reader 失敗理由が積んであって、これが Desktop App ルートの取り込み候補リストになる。

  • is_sample = 1 のサンプル本 16 冊が混ざっていたので、これは買い直さない限り対象外として除外
  • Kindle Unlimited で借りているだけの本(is_kindle_unlimited = 1)も、返却済み/返却予定があるので原則除外
  • Cloud Reader 不可・購入済み・未処理の本 16 冊 → 14 冊 → 7 冊と絞り込んだ
  • 池上彰のお金の学校(B009U4ZX1I)は PC アプリでも開けなかった。新しい excluded_reason タグ「Desktop App でも未サポート(モバイル専用)」を立てた

撮影計画書は book-knowledge-base/memo/2026-06-23/kindle-desktop-bulk-capture-plan.md に固定して、明日以降の自分への引き継ぎはこのファイルに集約する形にした。

今日撮れた本

実走行できたのはこのあたり。

  • システムの科学 第3版(ハーバート・A・サイモン): 415 ページ、140 枚、6 分 20 秒。最初の本格走行。1 クリックで Kindle 内部のページカウンタが 2〜3 進む現象に気づいて、1 スプレッド ≒ 1 内容で問題ないと結論づけた
  • 税理士のためのプログラミング: 221 ページ、220 枚、11 分。固定レイアウト本のリファレンス挙動として安定
  • 新・現代会計入門 第3版: リフロー本。Location 10689 のうち 377 枚を撮影。no_advance の停止判定を 5 回 → 3 回に緩めた修正と、座標を right - 30(chevron 領域内)に戻した修正がここで効いた
  • ソフトウェア開発失敗集(B0CW1KZ5N3): 425 ページ、206 枚、11.85 分。is_kindle_unlimited = 1 と判明したので扱いを継続検討に回した

OCR と DB 投入は最初の「システムの科学」だけ通して、Step C(mv → yomitoku OCR → DB 投入 → FTS 確認 → restructure-book サブエージェント)が動くことを確認した。残りはまとめて OCR バッチで流す予定。

一日の終わりに残ったもの

  • Kindle for PC(旧/新両方)の中身がどうなっているか、UIA/CDP/物理マウスのどれが効くかが頭に入った
  • ローマ数字ページや、Location 形式のフッター(リフロー本)も扱える形になった
  • ホームディレクトリ直下の .git という地雷を1個踏み抜いて回避手順を学んだ
  • 環境変数を雑に grep してはいけない、という痛い教訓を 1 件追加した
  • 翌日に積む候補のリストが計画書に整理された

memo/ に過去経緯の HTML、book-knowledge-base/memo/2026-06-23/ に撮影計画書を残して終わり。明日は撮影フェーズの残り 6 冊を片付けてから、まとめて yomitoku を走らせる。