CursorとPythonで英文PDFを日本語翻訳して表示するツールをつくる

## はじめに
こんにちは。モノヒトです。PDF形式の英語論文をひたすら読んで内容を理解して簡単にまとめる。。。。みたいな業務をやることがありまして、論文の序論だけでもぱぱっと日本語訳できたら楽だなあと思い、それができるアプリみたいなもんを勉強を兼ねて作ろうと思いました。Cursorというエディタを使えば超簡単にできたので感動しました。以下に備忘録も兼ねて開発日記的なものを記します。
なお、PCのOSはWindows11にて開発しております。

## Cursorのインストール

https://cursor.sh/

Cursorはここからダウンロードします。右上の「download」をクリックしてインストールするだけです。最近巷でコーディングに特化してとても優秀なエディタと噂のcursorですが、何がスゴイかご存じの方も多いかもしれませんが、ChatGPT4に作りたいアプリの概要をチャットすると、ぱぱっとコードを作ってくれてそのままRun and Debugを試すことができるのが便利すぎてビビりました。ChatGPT4への質問はフリープランだと回数が限られており、一定数質問数がカウントされるとクールダウンが必要なようです。私は無制限に使いたいので課金しました。費用は2023年12月現在で毎月20$です。

## 使用したモジュール　動作環境メモ　2023/12/11

- tkinter バージョン8.6
GUIをpythonのコーディングによって簡単に作成します。便利。

- PyPDF2 バージョン3.0.1
今後はPyPDF2は公式によるサポートがなくなり以降はpypdfによるサポートを行うという情報がありましたが、
CursorによるGPT4の返答ではPyPDF2を用いたコードスニペットを示したためそのまま使用しました。

https://qiita.com/ryutarom128/items/6e5d36efb136f9595f07

- googletrans 
- 最新版のバージョンでは翻訳が安定しませんでした。CursorのGPT4曰くgoogletrans==3.1.0a0というバージョンを試してみてください。このバージョンは一部のユーザーにとって安定して動作すると報告されています。以下のコマンドでインストールできます
- - 
-   pip install googletrans==3.1.0a0

とのことだったのでこれを用います。翻訳結果は安定しました。なお、googletransというモジュールはスクレイピングを用いて

- pycryptodome
暗号化されているPDFを翻訳するためのモジュール。バージョン3.19.0
- time

## コード一覧
コードはほぼすべてcursor上での対話によって生成されたコードスニペットをコピペして作られています。
コードの意味は後々勉強することにして、とりあえず動いたコードを以下に保存目的も兼ねてペーストしておきます。
ご参考まで。

    import tkinter as tk
    from tkinter import filedialog
    import PyPDF2
    from googletrans import Translator
    import time
    import traceback
    
    def process_text(text):
        lines = text.split('\n')
        processed_lines = []
        for i in range(len(lines)):
            line = lines[i].strip()
            next_line = lines[i + 1].strip() if i + 1 < len(lines) else ''
    
            # Remove empty lines
            if not line:
                continue
    
            # Check if the line is a heading
            if line.startswith("I.") or line.startswith("II.") or line.startswith("III.") or line.startswith("IV.") or line.startswith("V."):
                processed_lines.append(line)
                continue
    
            # Check if the line ends with an alphabet character, a reference number, or a comma
            if processed_lines and (line[-1].isalpha() or line.endswith("]") or line.endswith("、")):
                processed_lines[-1] += ' ' + line
                continue
    
            # Check if the line is part of the previous line
            if processed_lines and (line.count('(') != line.count(')')):
                processed_lines[-1] += ' ' + line
            else:
                # If the previous line is a heading, append the line without a newline
                if processed_lines and (processed_lines[-1].isupper() or processed_lines[-1][0].isdigit()):
                    processed_lines[-1] += ' ' + line
                else:
                    processed_lines.append(line)
    
        return ' '.join(processed_lines)
    
    def open_file():
        filepath = filedialog.askopenfilename()
        if filepath:
            with open(filepath, 'rb') as file:
                reader = PyPDF2.PdfReader(file)
                page_number = int(page_number_entry.get())
                page = reader.pages[page_number]
                text = page.extract_text()
    
            # Translate the text to Japanese
            translator = Translator()
            for _ in range(5):  # Try up to 5 times
                try:
                    # Limit the text to 5000 characters
                    text = text[:5000]
                    translated = translator.translate(text, dest='ja')
                    output_text = process_text(translated.text)
                    translation_length = int(translation_length_entry.get())
                    output_text = output_text[:translation_length]
                    break
                except Exception as e:
                    print(f"Translation failed, retrying... ({e})")
                    traceback.print_exc()
                    time.sleep(1)  # Wait for 1 second before retrying
            else:
                output_text = "翻訳できませんでした。"
    
            # Display the output in the text widget
            text_widget.delete(1.0, tk.END)
            text_widget.insert(tk.END, output_text)
    
    root = tk.Tk()
    
    # Create entry widgets for page number, translation length and window size
    tk.Label(root, text="Page Number").pack()
    page_number_entry = tk.Entry(root)
    page_number_entry.insert(0, "0")  # Set initial value
    page_number_entry.pack()
    
    tk.Label(root, text="Translation Length").pack()
    translation_length_entry = tk.Entry(root)
    translation_length_entry.insert(0, "1200")  # Set initial value
    translation_length_entry.pack()
    
    tk.Label(root, text="Window Size").pack()
    window_size_entry = tk.Entry(root)
    window_size_entry.insert(0, "80x60")  # Set initial value
    window_size_entry.pack()
    
    # Create a button to apply the window size
    def apply_window_size():
        window_size = window_size_entry.get().split('x')
        text_widget.config(width=int(window_size[0]), height=int(window_size[1]))
    
    window_size_button = tk.Button(root, text="Apply Window Size", command=apply_window_size)
    window_size_button.pack()
    
    button = tk.Button(root, text="Open PDF", command=open_file)
    button.pack()
    
    # Create a text widget to display the output
    text_widget = tk.Text(root, wrap='word', width=100, height=40)  # 'word' wrap means that tkinter will break lines at words
    text_widget.pack()
    
    root.mainloop()

## 実行ファイル(exeファイル)の生成

pyinstallerをインストールするとexeファイルを生成することができるとのことなのでやってみました。上記のコードの名前がmain.pyだった場合、pyinstallerをpipでインストールしたのち、

    pyinstaller --onefile main.py

とターミナルでコマンド入力するとmain.specというファイルが生成されます。実際のコードの名前にしたがって引数のmain.pyを適宜適切に変更して用います。で、main.specを開くと

    hiddenimports=[],

という項目があるので、ここを

    hiddenimports=['PyPDF2','googletrans','pycryptodome'],

と書き換えて上書き保存します。その後、ターミナルにてmain.pyが存在するディレクトリまで移動して


    pyinstaller main.spec    

とコマンド入力すると、そのディレクトリに存在するはずのさきほど編集したmain.specファイルに従ってPythonスクリプトを実行可能ファイルにビルドすることができます。

## 終わりに
動作結果は例えば以下のように動きました。感動しました。

https://twitter.com/monohitoworks/status/1732792257743659421

以上です。今日も良い一日を！ モノヒト