2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2023-12-11

はじめに

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

Cursorのインストール

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を用いたコードスニペットを示したためそのまま使用しました。

  • 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スクリプトを実行可能ファイルにビルドすることができます。

終わりに

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

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?