DeepL APIを使って英語論文をサクッと翻訳するツールを作った。【Python】

# はじめに
PDFの英語論文を翻訳する試みは自分含めいろんな方が行っており、サービスとして提供されているものもありますが、結局使うのが面倒になって
**コピー　→　DeepLのホームページで直接翻訳**
に戻っていませんか？そうです私のことです。

何が面倒なんでしょうね？
私は原論文から都度目を離さなければいけない点なんじゃないかと思っています。

:::note info
**それなら元の文の上にオーバーレイで表示できればサクサク読めるのでは．．．？**
:::

作りましょう。

# 動作の様子
使用PDF　→　[PRL 104, 037002](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.104.037002) 
![メディア1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501281/2cfa51b0-680b-0930-84dd-e470c924684c.gif)


# 実装
プログラムの中身なんて興味のない。
機能が欲しい。ただそれだけ。 ~~（フクースナ・イ・トーチカ並感）~~　な方も多いと思うので先に実装をば。

```python 
from threading import Thread
import win32api
from PIL import ImageGrab
import pyocr
import ctypes
import time
import tkinter as tk
from requests import post


class ClipboardOCR:
    def __init__(self):
        self.tool = pyocr.get_available_tools()[0]
        self.builder = pyocr.builders.TextBuilder(tesseract_layout=6)
        self.text = ""

    def run(self, lang="jpn"):
        img = ImageGrab.grabclipboard()
        if img:
            self.text = self.tool.image_to_string(img, lang=lang, builder=self.builder)
        return self.text


def get_key_state(keycode):
    return win32api.GetAsyncKeyState(keycode) >> 15 < 0


def runClipboardOCR(lang="eng"):
    text = c.run(lang)
    return text


class Translator:
    def __init__(self, auth_key):
        self.data = {
            "source_lang": "EN",
            "target_lang": "JA",
            "split_sentences": "nonewlines",
            "auth_key": auth_key,
        }

    def translate(self, tk, text):
        task = Thread(
            target=self._translate,
            args=(
                tk,
                text,
            ),
            daemon=True,
        )
        task.start()

    def _translate(self, tk, text):
        tr_text = (
            post(
                "https://api-free.deepl.com/v2/translate",
                data=self.data | {"text": text},
            )
            .json()["translations"][0]
            .get("text")
        )
        print(tr_text)
        tk.label["text"] = tr_text
        x1, x2, y1, y2 = tk.coor
        tk.geometry(f"{x2-x1}x{max(tk.label.winfo_reqheight()+40, y2-y1)}+{x1}+{y1}")


class Application(tk.Tk):
    def __init__(self, x1, x2, y1, y2):
        super().__init__()
        self.coor = x1, x2, y1, y2
        self.title("Transparent window")
        self.attributes("-alpha", 0.95)
        self.attributes("-topmost", True)
        self.bind("<Configure>", self.sized)
        self.bind("<Shift-ButtonPress-1>", toggleOverrideRedirect)
        time.sleep(0.5)
        self.quit = tk.Button(self, text="x", command=self.destroy)
        self.quit.place(relx=1, rely=0, anchor="ne")
        self.text = runClipboardOCR()
        self.label = tk.Label(
            self,
            font=("游ゴシック", "12"),
            anchor="e",
            justify="left",
            text=self.text,
        )
        tr.translate(self, self.text)
        self.label.pack(expand=True)
        self.geometry(
            f"{x2-x1}x{max(self.label.winfo_reqheight()+40, y2-y1)}+{x1}+{y1}"
        )

    def sized(self, *args):
        self.label["wraplength"] = self.winfo_width() - 40


def toggleOverrideRedirect(ev):
    win = ev.widget.winfo_toplevel()
    win.overrideredirect(not win.overrideredirect())
    win.withdraw()
    win.deiconify()
    win.focus_force()
    return


def get_rectcoordinate():
    class _pointer(ctypes.Structure):
        _fields_ = [
            ("x", ctypes.c_long),
            ("y", ctypes.c_long),
        ]

    point = _pointer()
    vk_leftbutton = 0x01
    while 1:
        if ctypes.windll.user32.GetAsyncKeyState(vk_leftbutton) == 0x8000:
            ctypes.windll.user32.GetCursorPos(ctypes.byref(point))
            x1, y1 = point.x, point.y
            while ctypes.windll.user32.GetAsyncKeyState(vk_leftbutton) == 0x8000:
                pass
            break
    ctypes.windll.user32.GetCursorPos(ctypes.byref(point))
    x2, y2 = point.x, point.y
    return x1, x2, y1, y2


if __name__ == "__main__":
    AUTH_KEY =   # auth_keyを入力
    c = ClipboardOCR()
    tr = Translator(AUTH_KEY)

    while True:
        if get_key_state(91) and get_key_state(16) and get_key_state(83):
            app = Application(*get_rectcoordinate())
            app.overrideredirect(1)
            app.mainloop()

```
# 使い方
- 各種ライブラリ、Tesseract OCRをインストール。
- DeepLのアカウントを取得（無料垢多重取得防止の為にクレジットカード情報の入力が求められますが、有料会員登録しない限り請求は発生しません。（2022/10））
- ログイン後、「アカウント」→「アカウント」→「DeepL APIで使用する認証キー」に記載されている認証キーを、プログラム下部の`# auth_keyを入力`とある部分に入力。
- プログラム実行
- 「Windowsキー」＋「Shift」＋「S」で指定領域のスクリーンショットを撮る。

※複数ウィンドウの同時表示には対応していません。必要であればご自身で実装してください。

# 仕組み

- 「Windowsキー」＋「Shift」＋「S」の押下を検出
- 左クリックを押した位置と離した位置を記録
- クリップボードにコピーされたスクリーンショット画像に対してOCR
- DeepL APIに投げる
- 記録した領域にTkinterウィンドウ作成、原文を表示
- 返ってきた翻訳結果で上書き

# おわりに
この記事は修論の息抜きに書きました。辛いです。
あまり整理できていないので、ちょっと汚コードですがゆるして。

# 参考
[Tkinterのタイトルバー他を非表示にする部分](https://ja.stackoverflow.com/questions/82498/tkinter%E3%81%A7-overrideredirect%E3%82%92%E9%81%A9%E7%94%A8%E3%81%97%E3%81%A6-resizable%E3%82%92true%E3%81%AB%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E3%81%82%E3%82%8A%E3%81%BE%E3%81%99%E3%81%8B)
[左クリックの座標を検出する部分](https://oregengo.hatenablog.com/entry/2016/09/22/224027)