範囲選択で翻訳してくれるツールを作った
Snipping Toolのように範囲選択するだけで、範囲内の英語・日本語を相互翻訳してくれるツールを作りました。自分用に作ったのでWindowsのみで動作します。
リポジトリ
モチベーション
仕事柄英語を読む機会が多いのですが、たまに難しい英文に出会うときや、英語を読む気分じゃないときがあります。(原文で読むべきなのですが……)
そういうときはChatGPTに翻訳をさせていたのですが、毎度行っていた
- 日本語に訳して というプロンプトを打つ
- Ctrl+V で画像貼り付けをする
の2つのキー入力の時間を省略したい、という気持ちが生まれました。
満たしたかった要件としては
- 1,2時間でサクッと作りたい
- タスクバーにピン留めして コマンド or クリック一発で起動するようにしたい
の2つで、少し調べてみたところ
上の2記事が見つかり、どうやらPythonでSnipping Toolもどきを作れる上、Pythonをシェルスクリプトやvbsから呼び出すようにすればタスクバーにピン留めできることが判明したので採用しました。
技術的な話
画面キャプチャ
画面キャプチャの実装には、PyQt6 と mss ライブラリを使用しました。
まず、SelectWidget
クラスでマウスドラッグによる範囲選択を実装しています。今回問題になったのはマルチディスプレイ環境への対応です。
mssライブラリでは、すべての画面に対する座標と大きさを指定することで、特定の範囲のスクリーンショットimageを取得することができます。
また、PyQt6 ライブラリのマウスのクリックを検知するイベントにおいて、範囲選択をする際にマウスのクリックの開始点、ドラッグの終了点の座標を event.position()
で取得しています。
しかし、event.position()
で取れる座標の値はプライマリモニタの左上(青矢印)を (0,0)
とした座標になっており、mss においてのすべての画面の配置込みの左上(赤矢印)を (0,0)
とした座標とはそのままだと一致しません。
また、mssにおいては物理ピクセルの値を指定するようになっていますが、 event.position()
はデバイスピクセルの値を返します。なので、4kモニターを使っている私のような環境ではピクセル数が一致しません。
今回は QPoint::mapToGlobal を使うとグローバル座標に変換することができるので、offsetを取得した座標に加えることで解決しました。
また、QWindow::devicePixelRatio でプライマリモニタのデバイスピクセル比を取得し、物理ピクセルに変換することで比率のズレも解消しました。
画像認識と翻訳
画像認識と翻訳には Gemini を使用しています。マルチモーダルなので、画像データから直接文字起こしと翻訳を出力することができます。Gemini 1.5 Flashはfree tierで使用できるトークン数も多いですし、そもそもがとても安いのでおすすめです。
今回はリアルタイムでの出力表示を行いたかったため、stream=True
を指定しています。この時LLMからのレスポンスをjson形式に指定しているため、jsonとして構造が閉じていない文字列をパースする必要があります。とりあえず正規表現で抽出することで対応しています。
uv
今回は 0.3.0 で導入された uvのパッケージ管理機能を利用しています。uvだけでPythonの仮想環境を含めたプロジェクト管理が完結し、またパッケージのインストールも早いためとても便利です。
おわりに
現状の実装では課題があり、
- 文字数が多い画像をキャプチャした場合、翻訳が出力されない
- もしくは最後まで文字起こしされない
- 文字起こしより先に翻訳文が出力されてほしいが、現状は固定で英語が先に出力される (
ja
よりen
のほうが辞書順で先になるため)
などの問題があります。解決できる課題もあると思うので、気が向いたときに直していきたいと思います。
Discussion