🌐

範囲選択で翻訳してくれるツールを作った

2024/10/26に公開

Snipping Toolのように範囲選択するだけで、範囲内の英語・日本語を相互翻訳してくれるツールを作りました。自分用に作ったのでWindowsのみで動作します。

リポジトリ

https://github.com/trasta298/honryu

モチベーション

仕事柄英語を読む機会が多いのですが、たまに難しい英文に出会うときや、英語を読む気分じゃないときがあります。(原文で読むべきなのですが……)
そういうときはChatGPTに翻訳をさせていたのですが、毎度行っていた

  • 日本語に訳して というプロンプトを打つ
  • Ctrl+V で画像貼り付けをする

の2つのキー入力の時間を省略したい、という気持ちが生まれました。

満たしたかった要件としては

  • 1,2時間でサクッと作りたい
  • タスクバーにピン留めして コマンド or クリック一発で起動するようにしたい

の2つで、少し調べてみたところ
https://qiita.com/harupy/items/d4f7978c4d8d1c2a3d01
https://dev.10yro.co.jp/entry/2021/11/25/134637

上の2記事が見つかり、どうやらPythonでSnipping Toolもどきを作れる上、Pythonをシェルスクリプトやvbsから呼び出すようにすればタスクバーにピン留めできることが判明したので採用しました。

技術的な話

画面キャプチャ

画面キャプチャの実装には、PyQt6 と mss ライブラリを使用しました。
まず、SelectWidgetクラスでマウスドラッグによる範囲選択を実装しています。今回問題になったのはマルチディスプレイ環境への対応です。

mssライブラリでは、すべての画面に対する座標と大きさを指定することで、特定の範囲のスクリーンショットimageを取得することができます。
https://github.com/trasta298/honryu/blob/main/main.py#L102-L110

また、PyQt6 ライブラリのマウスのクリックを検知するイベントにおいて、範囲選択をする際にマウスのクリックの開始点、ドラッグの終了点の座標を event.position() で取得しています。
https://github.com/trasta298/honryu/blob/main/main.py#L76-L87

しかし、event.position() で取れる座標の値はプライマリモニタの左上(青矢印)を (0,0) とした座標になっており、mss においてのすべての画面の配置込みの左上(赤矢印)を (0,0) とした座標とはそのままだと一致しません。
また、mssにおいては物理ピクセルの値を指定するようになっていますが、 event.position() はデバイスピクセルの値を返します。なので、4kモニターを使っている私のような環境ではピクセル数が一致しません。

今回は QPoint::mapToGlobal を使うとグローバル座標に変換することができるので、offsetを取得した座標に加えることで解決しました。
また、QWindow::devicePixelRatio でプライマリモニタのデバイスピクセル比を取得し、物理ピクセルに変換することで比率のズレも解消しました。

https://github.com/trasta298/honryu/blob/main/main.py#L40-L46

画像認識と翻訳

画像認識と翻訳には Gemini を使用しています。マルチモーダルなので、画像データから直接文字起こしと翻訳を出力することができます。Gemini 1.5 Flashはfree tierで使用できるトークン数も多いですし、そもそもがとても安いのでおすすめです。

今回はリアルタイムでの出力表示を行いたかったため、stream=Trueを指定しています。この時LLMからのレスポンスをjson形式に指定しているため、jsonとして構造が閉じていない文字列をパースする必要があります。とりあえず正規表現で抽出することで対応しています。

https://github.com/trasta298/honryu/blob/main/translate_image.py#L85-L115

uv

今回は 0.3.0 で導入された uvのパッケージ管理機能を利用しています。uvだけでPythonの仮想環境を含めたプロジェクト管理が完結し、またパッケージのインストールも早いためとても便利です。

https://astral.sh/blog/uv-unified-python-packaging

おわりに

現状の実装では課題があり、

  • 文字数が多い画像をキャプチャした場合、翻訳が出力されない
    • もしくは最後まで文字起こしされない
  • 文字起こしより先に翻訳文が出力されてほしいが、現状は固定で英語が先に出力される (jaより en のほうが辞書順で先になるため)

などの問題があります。解決できる課題もあると思うので、気が向いたときに直していきたいと思います。

Discussion