PCの画面に表示されている文字列を文字列としてコピーしたいが文字列としてコピーできないようになっている。 ということがたまにあるかと思います。
いい例が挙げられませんけど、例えばウィンドウのタイトルバーに書かれている文字列はコピーできません。
そんなときは目で見てタイプするわけですけど数が多かったり、文字列が長かったりすると面倒なわけで…。
そこでそれをGeminiでつまりAIで行うツールをつくろうという話です。
やることは要はスクリーンショットに対するOCR、文字認識ですね。
ツールを起動してマウスドラッグで画面内の文字認識したい範囲を指定すると文字認識がなされて結果がクリップボードに書き込まれます。 あとはメモ帳などにペーストするなりなんなり。そんなツールをつくります。
では早速つくっていきましょう。
作成の流れは以下の記事と同じです。
Python + Rye + Gemini API でAIチャットを作る - てけとーぶろぐ。
Ryeのインストール
セットアップを簡単にするためPythonのパッケージマネージャーにはRyeを使います。
Ryeをインストールしていない場合は以下の「Installation Instructions」に従ってインストールします。
プロジェクトの作成
プロジェクトを作成します。
$ rye init screen-ocr
$ cd screen-ocr
$ rye sync
パッケージのインストール
Gemini API 用の Python SDK が含まれる google-generativeai パッケージなど必要なパッケージをインストールします。
$ rye add google-generativeai $ rye add pillow $ rye add pyautogui $ rye add pyglet $ rye add pyperclip $ rye sync
Pythonファイルの作成
screen-ocr/main.py として以下の内容でPythonファイルを作成します。
import os import google.generativeai as genai import pyautogui import pyglet import pyperclip from PIL import Image from pyglet import shapes class SimpleScreenshotSelector(pyglet.window.Window): def __init__(self, original_image: Image): # 以下のコードだとマウスカーソルが左上に持っていけない問題が発生した # width, height = original_image.size # super().__init__(width, height, fullscreen=True) screen = pyglet.display.get_display().get_default_screen() width, height = screen.width, screen.height super().__init__( width, height, "Screenshot Selector", fullscreen=False, style=pyglet.window.Window.WINDOW_STYLE_BORDERLESS, ) # 何もしないとウィンドウがUbuntuのDockや画面上部のTop Barに # 重ならないような位置に表示されるので左上に移動する self.set_location(0, 0) self.set_mouse_cursor(self.get_system_mouse_cursor(self.CURSOR_CROSSHAIR)) # Pillow → Pyglet変換 image_data = ( original_image.transpose(Image.FLIP_TOP_BOTTOM).convert("RGB").tobytes() ) self.texture = pyglet.image.ImageData(width, height, "RGB", image_data) self.original_image = original_image self.start_pos = None self.current_pos = None self.is_dragging = False self.batch = pyglet.graphics.Batch() self.selection_rect = None def on_draw(self): self.clear() self.texture.blit(0, 0) if self.is_dragging and self.start_pos and self.current_pos: x1, y1 = self.start_pos x2, y2 = self.current_pos rect_x = min(x1, x2) rect_y = min(y1, y2) rect_w = abs(x2 - x1) rect_h = abs(y2 - y1) self.selection_rect = shapes.Rectangle( rect_x, rect_y, rect_w, rect_h, color=(255, 0, 0), batch=self.batch ) self.selection_rect.opacity = 100 self.batch.draw() def on_mouse_press(self, x, y, button, modifiers): if button == pyglet.window.mouse.LEFT: self.start_pos = (x, y) self.current_pos = (x, y) self.is_dragging = True def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): if self.is_dragging: self.current_pos = (x, y) def on_mouse_release(self, x, y, button, modifiers): if button == pyglet.window.mouse.LEFT: self.is_dragging = False image = self.get_selection() if image: text = perform_ocr(image) print("OCR Result:") print(text) pyperclip.copy(text) self.close() def on_key_press(self, symbol, modifiers): if symbol == pyglet.window.key.ESCAPE: self.close() def get_selection(self): if not self.start_pos or not self.current_pos: return None # self.start_pos, self.current_pos は # 左下を原点とする座標系での位置が入っている # x, y それぞれの最小と最大を得てboxにする x1, y1 = self.start_pos x2, y2 = self.current_pos box = (min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2)) if box[2] - box[0] < 2 or box[3] - box[1] < 2: print("Selection too small.") return None # 左上を原点とする座標系に直す corrected_y2 = self.original_image.height - box[1] - 1 corrected_y1 = self.original_image.height - box[3] - 1 box = (box[0], corrected_y1, box[2], corrected_y2) # crop()で切り抜く画像に右下の点が含まれるように調整 box = (box[0], box[1], box[2] + 1, box[3] + 1) return self.original_image.crop(box) def perform_ocr(image): # Geminiへの指示文 instruction = ( "このスクリーンショットに写っている文字列を全て読み取って返してください。" "写っている文字列のみを返してください。それ以外の説明などの文言は不要です。" ) # Geminiに画像と指示を渡す response = model.generate_content([instruction, image]) return response.text if __name__ == "__main__": GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") if GOOGLE_API_KEY is None: print("The environment variable GOOGLE_API_KEY is not set.") exit() # model = genai.GenerativeModel("gemini-2.5-flash") model = genai.GenerativeModel("gemini-2.5-pro") try: img = pyautogui.screenshot() window = SimpleScreenshotSelector(img) pyglet.app.run() except Exception as e: print(f"Error: {e}")
APIキーの取得
以下のサイトの「APIキーを取得する」ボタンをクリックしてAPIの使用に必要なAPIキーを取得します。
取得したら以下のようにして「GOOGLE_API_KEY」という環境変数に取得したAPIキーをセットします。 ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"の部分は取得したAPIキー) Pythonプログラムからこの環境変数を参照します。
$ export GOOGLE_API_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
プログラムの実行
$ rye run python main.py
.env ファイルの作成
上記のようにAPIキーを環境変数にセットしているとターミナルを立ち上げるたびに環境変数にセットしなければなりません。 それは面倒なのでプログラム実行時にファイルからAPIキーを読み込むようにしてみましょう。 screen-ocr/.env を以下の内容で作成します。("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"の部分は取得したAPIキー)
GOOGLE_API_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
pyproject.toml ファイルの編集
.envファイルから環境変数に値をセットしてプログラムの実行をするような スクリプトを screen-ocr/pyproject.toml に定義します。 pyproject.tomlに以下を追記します。
[tool.rye.scripts]
run-main = { cmd = "python main.py", env-file = ".env" }
スクリプトを使ってのプログラムの実行
そしてプログラムの実行を以下のコマンドで行うようにします。
$ rye run run-main
実は自分は以前にも同じようなツールを作っています。 そのときは以下のAPIを使っていました。
現在は変わっているかもしれませんが当時は使っていてけっこう誤認識がある印象でした。
一方で今回のものは認識精度が高すぎて画像から読んでいないのではないかと疑いたくなってしまうほどです。 えらいこってす。