きっかけ
何て書いてあるんだ...?
それは専門外な内容の英語プレゼンを聴講したある日のこと.
普段であればレジェメのPDFが配布され,テキストをコピーしてDeepLに投げることで内容を把握することができるのですが,この日はTeamsの映像でしか見ることができなかったので,専門外な内容だったことも相まって内容を理解するのに苦労しました.
「OCRしてDeepLに投げることができればいいのに...」
ということでOCR機能付きDeepLデスクトップアプリケーションを作りました.
Summary of technical topics
- OS: Windows10
- GUI: Eel(Python3.7)
- OCR: Tesseract
- Focus the window: Chrome extension (
chrome.windows.update()
)
先行事例
作成後に調べたらより多機能なものが色々ありました.
-
Screen Translator
DeepL以外の翻訳エンジンも利用でき,非常に多機能 -
PCOT
日本人の方が作成されています
作ったもの
Windows10用OCR機能付きDeepLデスクトップアプリケーション
できること
- 基本的な挙動は本家を参考にした
-
Ctrl+C
×2で選択したテキストをDeepLで翻訳 - 任意のキーボードショートカット設定
- 自動起動.bat作成機能付き
-
- OCR
- OCRソフトであるTesseractによる解析結果をDeepLに投げる
ペルソナ
- テキスト化されていない英語プレゼン(動画)の聴講者
- 英語ゲームプレイヤー(これはPCOTの方が使いやすいと思う)
技術的な話
GUI(Eel)
PythonでGUIアプリケーションを作成できるライブラリは様々存在する.
- Tkinter
- wxPython
- PySimpleGUI
- etc.
これらはそれぞれのライブラリ専用の記法で各種ウィジェットを配置していく形になっている.
ウィンドウの透明化など複雑な実装ができる点は魅力だが,ウィジェットの位置を調整するのが(CSSと比べ)大変だと実装して感じたので,HTML/CSSを利用してGUIを作成できないかと探していたところEelにたどり着いた.
EelはHTML/CSSを使ってGUIを作成できるだけでなく,Python⇔JS間で連携ができるため,Python側でキーボード入力監視やOCRといった作業を行った結果をJSに投げてウィンドウに表示するといったことが可能になる.
詳細は [Python] EelをつかってHTML/CSS/JavaScriptでGUIを構築 などの解説記事を参考にされたい.
フォルダ構造
以下紹介する各ファイル類は次のようなフォルダ構造からなる.
root
└── DeepLopenerOCR.pyw
└── assets
└── eel.js
└── main.html
└── main.js
└── main.css
import eel
eel.init(assets)
eel.start("main.html")
とすることで,assets
フォルダ内のmain.html
をウィンドウに表示できるようになる.
ちなみに*.py
でなく*.pyw
とすると,Pythonのコンソールなしで実行できるようになる.
GUIアプリケーションなのでこれは非表示にしたい.
Ctrl+C
監視
send_clipboard
は0.5秒以内にCtrl+C
が2連続で押された場合にクリップボードのテキストを翻訳する関数.また,クリップボードのテキストが前に翻訳したときと同じ場合は翻訳しない設定もしている.
import time
import keyboard
import pyperclip
def send_clipboard(target_lang, api_key):
global now, beforekeycc, beforetxt
print("key detect")
if(not keyboard.is_pressed("ctrl+C") and not beforekeycc):
pass
elif(time.time()-now < 0.5 and pyperclip.paste() != "" and pyperclip.paste() != beforekeycc:
# ここで翻訳する
beforetxt = pyperclip.paste()
now = time.time()
beforekeycc = keyboard.is_pressed("ctrl+C")
if __name__ == '__main__':
# 諸々省略
keyboard.add_hotkey('ctrl+C', send_clipboard,
args=[target_lang, api_key])
-
keyboard.add_hotkey('ctrl+C', send_clipboard,args=[target_lang, api_key])
によりctrl+C
を入力した際にsend_clipboard
関数が実行されるようになる- プログラム実行中は常に監視される
- ほかのホットキーを追加したい場合も同様に指定できる
-
keyboard.is_pressed("ctrl+C")
で入力がctrl+C
であるかTrue/False
-
pyperclip.paste()
でクリップボードのテキストを取得
スクリーンキャプチャ監視 & OCR
スクリーンキャプチャした際にOCRを実行する.
先述のScreen TranslatorはスクリーンキャプチャするGUIを作成しているが,自分はそこまでの労力を使いたくなかったので,クリップボードが画像に変化したことをスクリーンキャプチャされたとみなす方針にした.
したがって,Win+Shift+S
以外にもChromeで画像をコピー
を押した場合にも発火するようになっている.
ちなみに...
なお,クリップボードの内容が変化した際にイベントを発火させる方法が分からなかったため,1秒ごとにクリップボードの内容を取得して
「1秒前から変化している ∧ 画像データになっている」
時にOCRするような実装とした.
from PIL import ImageGrab, Image
import getpass
import pyocr
def ocr(im, target, apikey):
pyocr.tesseract.TESSERACT_CMD = r'C:\\Program Files\\Tesseract-OCR\\tesseract.exe'
engines = pyocr.get_available_tools()
if len(engines) == 0:
print("Tesseract is not found")
else:
engine = engines[0]
txt = engine.image_to_string(im, lang="eng")
if(len(txt.replace("\n", "").replace(" ", "")) > 0):
eel.show("main.html")
eel.sleep(1) # 読み込まれるまで待機(雑)
eel.js_translate(txt, target, apikey)
def capture():
global target_lang, api_key
image = ImageGrab.grabclipboard()
while True:
im = ImageGrab.grabclipboard()
if isinstance(im, Image.Image) and im != image:
ocr(im, target_lang, api_key)
else:
pass
image = ImageGrab.grabclipboard()
eel.sleep(1)
if __name__ == '__main__':
# 諸々省略
spawn = eel.spawn(capture)
eel.expose(js_translate);
function js_translate(clipboard, target_lang, api_key) {
// 諸々省略
api_translate(clipboard, target_lang, api_key);
}
<html>
<head>
<meta charset="UTF-8">
<title>DeepLopenerOCR</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!--諸々省略-->
<script type="text/javascript" src="eel.js"></script>
<script type="text/javascript" src="main.js">
</script>
</body>
</html>
capture()
-
hoge = eel.spawn(capture)
とすることで簡単にマルチスレッド化できる.停止したい場合はhoge.kill()
で可能.便利. -
ImageGrab.grabclipboard()
でクリップボードのオブジェクトを読み込むことができる -
isinstance()
でオブジェクトのデータ型の判定できる(=画像データかどうか) (参考)
ocr()
- tesseractを予めインストールしておく
-
pyocr.tesseract.TESSERACT_CMD = r'C:\\Program Files\\Tesseract-OCR\\tesseract.exe
でインストールしたtesseract.exe
のパスを明示的に定義 -
txt = engine.image_to_string(im, lang="eng")
でOCR解析を行う-
lang
を指定することで解析言語を変更可能(日本語も対応可能)
-
-
eel.show("main.html")
でmain.html
を新しいウィンドウで開く eel.sleep(1)
-
eel.js_translate(txt, target, apikey)
-
js_translate()
はmain.js
内で定義している関数 - しかし,ウィンドウを開いた直後は実行できないので
eel.sleep(1)
で1秒待っている(雑)
-
Focus the window
翻訳実行時はウィンドウが最前面に出てきてほしい.
以前はwin32gui.SetForegroundWindow
により実現できていたようだが,手元の環境では期待した挙動にならなかった(参考).
そこで,Pythonで制御することをあきらめてChrome拡張機能に着目した.
EelはデフォルトではChromeをapp-modeで起動するので拡張機能を利用できる.
キーとなる部分は以下のみ(参考).
chrome.windows.update(
sender.tab.windowId,
{ focused: true },
function (tab) {}
);
exe化
- Pyinstallerで可能
- 詳細はEelのGitHubを参照
python -m eel .\DeepLopenerOCR.pyw assets --icon assets/favicon.ico
`--oneflie`と`--noconsole`の共存について
Future Works
- Tesseract以外のOCR(Google Vision等)を選択できるように実装
- OCR言語変更機能
- 任意の範囲の自動スクリーンキャプチャ機能
- PCOTは実装済み(たぶん)
- ゲームのテキストウィンドウを設定しておけば洋ゲーが捗りそう
- PCOTは実装済み(たぶん)
まとめ
今回はじめてEelを触りましたが,HTML/CSSでウィジェットを配置できるのは楽で良かったです.
また,Python⇔JS間で連携できる点はもっと有効活用できそうに感じています.
英語プレゼンに悩んでいる方はDeepLopenerOCR,試してみてください.