ぷそさんのプログラミング研究所

【Python】PDFを無制限に翻訳する方法|Googletrans

この記事では,Pythonを用いて英語の文書を翻訳する方法をご紹介します。

DeepLでPDFの論文の翻訳が出来るようになりましたが,無料版では制限があるのが現状です。

そこで,翻訳のAPIを用いて「無料」で「無制限」に翻訳する方法をご説明したいと思います!

目次

Pythonを使ったPDFの翻訳ツール

今回ご紹介するPython翻訳ツールの特徴は以下のようになっています。

Python翻訳ツールの特徴

  1. Pythonさえ使えれば無料で無制限に翻訳できる
  2. 図表や数式があってもレイアウトが崩れない
  3. 翻訳のルールを自分で決めることができる

図で表すと,以下のようなイメージで翻訳を行うことができます。

1つずつ特徴をご紹介したいと思います!

Pythonさえ使えれば無料で無制限に翻訳できる

翻訳にはgoogletransというAPIを使用するので,無料で無制限に翻訳することができます。

DeepLなどのPDF翻訳サービスでは,無料では1ヶ月で3文書程度,有料でも制限があることが多いです。

そのため,いっぱい翻訳したい!となった場合に,今回紹介するツールが活躍すると思います!

図表や数式があってもレイアウトが崩れない

DeepLでのPDF翻訳では,図や表,数式が含まれると,上手く翻訳されない現象が多く発生します。

例えば,図が次のページに飛んだりしてしまいます。

これは,図表や数式に含まれる文字も翻訳されるために起こる現象です。

そこで,今回ご紹介するツールでは,図表と数式を無視するような設定をすることで,レイアウトを保ったまま変換することができます。

翻訳のルールを自分で決めることができる

また,この翻訳ツールでは翻訳する部分を設定できるので,例えば「表の中身は翻訳しない」「参考文献は翻訳しない」といったことが自由自在に設定できます。

そのため,DeepLのサービスよりも柔軟に翻訳できると思います!

Python翻訳ソフトの使い方

準備するもの

実行が確認できているPythonのバージョンは「Python 3.8」です。

以下のモジュールが必要となります。

必要なモジュール

  • pandas :文章のデータを保存する用
  • pdfminer :PDFの読み取りなど
  • googletrans :翻訳する機械
  • pdfrw :PDFの読み込みなど
  • reportlab :PDFの編集など
  • PyPDF2 :PDFの結合

これらがない場合には,condapipでダウンロードしてください。

ソースコードについて

ソースコードは以下をコピペするだけで大丈夫です!

import pandas as pd
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LAParams, LTContainer, LTTextBox
from pdfminer.pdfinterp import PDFPageInterpreter, PDFResourceManager
from pdfminer.pdfpage import PDFPage

from googletrans import Translator
from pdfrw import PdfReader
from pdfrw.buildxobj import pagexobj
from pdfrw.toreportlab import makerl
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import portrait, A4
from reportlab.pdfbase import pdfmetrics
from reportlab.platypus import Table, TableStyle, Paragraph
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle
from PyPDF2 import PdfFileMerger
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
import time

# 自作関数の定義--------------------------------------------------------------------
# 1ページのテキストボックスを取得する関数
def find_textboxes(layout):
    if isinstance(layout, LTTextBox):
        return [layout]
    elif isinstance(layout, LTContainer):
        boxes = []
        for child in layout:
            boxes.extend(find_textboxes(child))
        return boxes
    else:
        return []


# PDFファイルに関する処理---------------------------------------------------------------------
# PDFファイル名の指定(入力)
file_input = input('Enter PDF name: ')
# 読み取るPDFの行間の設定(入力)(0.5で固定しても良い)
margin_input = input('Enter Margin: ')

# ファイルの取得
fp = open(file_input, 'rb')

# PDFの取得
pdfPages = PDFPage.get_pages(fp)

# 出力先のパスを設定
file_out = "和訳_" + file_input

# 翻訳機に関する処理-------------------------------------------------------------------------------------
# 翻訳機(Google trans)
translator = Translator()

# 翻訳の設定-------------------------------------------------------------------------------------
resourceManager = PDFResourceManager()
# 読み取りパラメータの設定
laParams = LAParams(line_overlap = 0.5,
                    word_margin  = 0.1,
                    char_margin  = 2,
                    line_margin  = float(margin_input),
                    detect_vertical = True)

#インタプリタオブジェクト作成
device = PDFPageAggregator(resourceManager, laparams=laParams)
interpreter = PDFPageInterpreter(resourceManager, device)

# 文章情報の取得-------------------------------------------------------------------------------------
# dfに文章とテキストボックスの位置の情報を保存する
df = pd.DataFrame()
for page_no, page in enumerate(pdfPages, start=1):
        #ページ処理
        interpreter.process_page(page)
        #LTPageオブジェクトを取得
        layout = device.get_result()
        #1ページ内のテキストのまとまりのリストを取得
        boxes = find_textboxes(layout)

        #テキストひとまとまりごとに処理
        for box in boxes:
            df_page = pd.DataFrame({"x_start":[box.x0],
                                    "x_end"  :box.x1,
                                    "y_start":box.y0,
                                    "y_end"  :box.y1,
                                    "text"   :box.get_text().strip(),
                                    "page"   :page_no}
                                    )

            df = pd.concat([df,df_page])

df = df.reset_index(drop=True)

# 翻訳ルールの設定-------------------------------------------------------------------------------------
# 80文字以上 and []から始まるのは除去,数式も削除(ここは各自カスタマイズ可能)

# min_words文字以下の文章は翻訳しない
min_words = 80 #各自設定

# 数式は=の有無で判定
math_list = []
for ii in range(len(df)):
    if "=" in df.iloc[ii,4] and "." not in df.iloc[ii,4]:
        math_list.append(ii)

df_index = []
for ii in range(len(df)):
    df.iloc[ii,4] = df.iloc[ii,4].replace('\n', '')
    if ii in math_list:
        continue
    if len(df.iloc[ii,4]) > min_words:
        if df.iloc[ii,4][0] != "[":
            df_index.append(ii)

    if "References" in df.iloc[ii,4] or "Reference" in df.iloc[ii,4]\
        or "REFERENCES" in df.iloc[ii,4] or "REFERENCE" in df.iloc[ii,4]:
        break

# 全部のセンテンス数を表示(なくてもよい)
print(len(df_index),"sentenses")


# 出力の設定-------------------------------------------------------------------------------------
# フォント等各種設定
font_name = "HeiseiKakuGo-W5"
pdfmetrics.registerFont(UnicodeCIDFont(font_name))
cc = canvas.Canvas(file_out, pagesize=portrait(A4))
past_page = -1

margen_ = 0
merger = PdfFileMerger()


# 翻訳する-------------------------------------------------------------------------------------
# 元のPDFを読み込み
pages = PdfReader(file_input, decompress=False).pages

# dfの情報を元に翻訳する
for dd in df_index:
    time.sleep(0.5)
    nowpage = df.iloc[dd,5]

    # 進行状況の表示用
    if nowpage != past_page:
        print(nowpage,"/",df.iloc[-1,5])
        if past_page != -1:
            cc.showPage()

        pp = pagexobj(pages[nowpage-1])
        cc.doForm(makerl(cc, pp))

    # ページ情報取得
    pagenum = df.iloc[dd,5]
    # 位置とサイズの指定
    left_low = [df.iloc[dd,0],df.iloc[dd,2] + margen_]
    colsize = [df.iloc[dd,1] - df.iloc[dd,0], df.iloc[dd,3]-df.iloc[dd,2] - margen_]

    # 翻訳した日本語の設定
    pstyle = ParagraphStyle(name='Normal', fontName=font_name, fontSize=9, leading=8)

    # TEXTを翻訳したものがTEXT_JNになるようにする(例外処理もする)
    TEXT = df.iloc[dd,4]
    try:
        TEXT_JN = translator.translate(TEXT, src='en', dest="ja").text
    except TypeError:
        TEXT_JN = TEXT

    try:
        JN_para = Paragraph(TEXT_JN,pstyle)
    except ValueError:
        continue


    # 元の文章の位置に翻訳した文章を貼り付ける
    table = Table([[JN_para]], colWidths=colsize[0], rowHeights=colsize[1])

    # 貼り付けの装飾設定(白枠にしている)
    table.setStyle(TableStyle([
            ('GRID', (0, 0), (-1, -1), 0.1, colors.white),
            ('VALIGN', (0, 0), (0, -1), 'TOP'),
            ('BACKGROUND', (0, 0), (0, -1), colors.white)
        ]))


    # 文字書き出し
    table.wrapOn(cc, left_low[0], left_low[1]) #x,y 左下原点
    try:
        table.drawOn(cc, left_low[0], left_low[1])
    except AttributeError:
        print("ERROR",nowpage)

    past_page = df.iloc[dd,5]

cc.save()

実行の方法

まずは,上のソースコードと同じフォルダに翻訳したいPDFを保存します。

今回は,ソースコードはgoogle_trans_auto.pyという名前で保存しています。

つまり,以下のようなファイル構造になれば大丈夫です。

この状態で,Pythonコードを実行します。

python google_trans_auto.py

すると,以下のような入力画面になります。ここで,PDFファイル名を入力します。

翻訳したいファイルが「test.pdf」とすると,以下のように入力します。

Enter PDF name: test.pdf

次に,行の余白を設定する画面になります。基本的に0.5と入力すれば大丈夫です。

Enter Margin: 0.5

こうすることで,翻訳が開始されます。

翻訳が完了したPDFは,「和訳_(翻訳したファイル名).pdf」として出力されます。

最後に

Pythonで作る翻訳ツールはいかがでしたでしょうか。

DeepLでのPDF翻訳も登場し便利な世の中になってきましたが,制限がある場合もあるので今回ご紹介したツールを使ってみてください!

また,翻訳の機械はDeepLのAPIにも変更できるので,APIが使える環境の人はそちらでも試してみてください!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

このブログでは,PythonやLaTeXの使い方などを紹介しています!
仕事でも趣味でもプログラミングをしています。
ブログは2022年8月にスタートしました。
【経歴】東京大学大学院修了→大手IT企業勤務

コメント

コメントする

目次