├── .gitignore ├── venv.yaml ├── requirements.txt ├── local.env ├── constant.py ├── PDFconverter.py ├── kindle_translator.py ├── translator.py ├── README.md ├── ocr.py └── capture.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /venv.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1plus1is3/kindle-translator/HEAD/venv.yaml -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyautogui 2 | img2pdf 3 | Pillow 4 | opencv-python 5 | pyocr 6 | fpdf2 7 | python-dotenv 8 | requests -------------------------------------------------------------------------------- /local.env: -------------------------------------------------------------------------------- 1 | OUTPUT_IMG_FOLDER="D:/KindlePDF/image" 2 | OUTPUT_TXT_FOLDER="D:/KindlePDF/txt" 3 | TESSERACT_PATH="D:/Tesseract-OCR" 4 | FONT_PATH="C:/Windows/Fonts/yumin.ttf" 5 | DEEPL_API_KEY="XXXXXX" 6 | -------------------------------------------------------------------------------- /constant.py: -------------------------------------------------------------------------------- 1 | from dotenv import dotenv_values 2 | 3 | config = dotenv_values("local.env") 4 | OUTPUT_IMG_FOLDER = config.get("OUTPUT_IMG_FOLDER") 5 | OUTPUT_TXT_FOLDER = config.get("OUTPUT_TXT_FOLDER") 6 | TESSERACT_PATH = config.get("TESSERACT_PATH") 7 | FONT_PATH = config.get("FONT_PATH") 8 | DEEPL_API_KEY = config.get("DEEPL_API_KEY") 9 | -------------------------------------------------------------------------------- /PDFconverter.py: -------------------------------------------------------------------------------- 1 | """ 2 | kindle-translator 3 | """ 4 | from fpdf import FPDF 5 | from constant import FONT_PATH 6 | 7 | 8 | class PDFConverter: 9 | """ 10 | テキストファイルをPDFに変換するクラス 11 | """ 12 | def text_to_pdf(self, file_name: str,) -> None: 13 | """ 14 | :関数名: 15 | - text_to_pdf 16 | :機能: 17 | - テキストファイルをPDFに変換する 18 | 19 | :引数: 20 | - 翻訳済みのテキストファイル名 21 | :戻り値: 22 | - None 23 | """ 24 | print('翻訳したファイルをPDFに変換しています...') 25 | 26 | # FPDF2のインスタンス化 27 | pdf = FPDF() 28 | 29 | pdf.add_page() 30 | 31 | pdf.add_font("yumin", fname=FONT_PATH, uni=True) 32 | pdf.set_font('yumin', size=12) 33 | 34 | # テキストファイルを開いてPDF形式で書き込み 35 | with open(f'JA_{file_name}.txt', 'r', encoding='utf-8') as f: 36 | for page in f: 37 | pdf.write(txt=page) 38 | 39 | # pdf形式で保存 40 | pdf.output(f'{file_name}.pdf') 41 | 42 | print('全ての工程が終了しました。フォルダに移動して、成果物を確認してください') 43 | -------------------------------------------------------------------------------- /kindle_translator.py: -------------------------------------------------------------------------------- 1 | """ 2 | kindle-translator 3 | """ 4 | from capture import Capture 5 | from ocr import OCR 6 | from translator import Translate 7 | from PDFconverter import PDFConverter 8 | 9 | 10 | class Main: 11 | """ 12 | kindle_translatorを実行するMainクラス 13 | """ 14 | 15 | capture = Capture() 16 | ocr = OCR() 17 | translator = Translate() 18 | convert = PDFConverter() 19 | 20 | # スクリーンショットを撮影する範囲の座標を取得 21 | x_1, y_1, x_2, y_2 = capture.window_manager() 22 | 23 | # ページごとにスクリーンショットを撮影(png形式で保存) 24 | page_number, file_name, img_file_path = capture.window_capture(x_1, y_1, x_2, y_2) 25 | 26 | # 取得した画像から文字を抽出してひとつのtxtファイルに保存 27 | file_name_txt, txt_file_path = ocr.extract_characters(page_number, file_name, img_file_path) 28 | 29 | # DeepLに投げられるようにファイルを100キロバイトごとに分割して保存 30 | file_list = ocr.divide_file(file_name, file_name_txt) 31 | 32 | # テキストファイルをDeepLに投げる 33 | translator.translate_with_deepl(file_list, file_name) 34 | 35 | # 翻訳されたテキストファイルをPDF形式に変換 36 | # 出力先はテキストファイルと同じフォルダ 37 | convert.text_to_pdf(file_name) 38 | -------------------------------------------------------------------------------- /translator.py: -------------------------------------------------------------------------------- 1 | """ 2 | kindle-translator 3 | """ 4 | import requests 5 | from constant import DEEPL_API_KEY 6 | 7 | class Translate: 8 | """ 9 | テキストファイルの翻訳を行うクラス 10 | """ 11 | def translate_with_deepl( 12 | self, 13 | file_list: list, 14 | file_name: str 15 | ) -> None: 16 | """ 17 | :関数名: 18 | - translate_with_deepl 19 | :機能: 20 | - テキストファイルをDeepLを用いて翻訳し、結果をひとつのテキストファイルに書き込む 21 | 22 | :引数: 23 | - 分割されたファイルの名前のリスト(list型) 24 | - 保存したいファイルの名前(str型) 25 | :戻り値: 26 | - なし 27 | """ 28 | print('テキストファイルの翻訳を行います') 29 | 30 | # file_listに記載されているファイルを順番に開いて翻訳にかける 31 | for partial_file_name in file_list: 32 | print('翻訳中...') 33 | # 翻訳したいファイルを開く 34 | with open(partial_file_name, "r", encoding="utf-8") as f: 35 | txt = f.read().replace('\n', ' ') 36 | 37 | # 英語から日本語に変換 38 | params = { 39 | "auth_key": DEEPL_API_KEY, 40 | "text": txt, 41 | "source_lang": 'EN', 42 | "target_lang": 'JA' 43 | } 44 | 45 | # DeepL APIにPOST 46 | # 無償版の場合は適宜URLを変更すること 47 | req = requests.post("https://api.deepl.com/v2/translate", data=params) 48 | 49 | # json形式で結果を取得 50 | result = req.json() 51 | 52 | # JA_foo.txtとしてtxt形式で結果を保存() 53 | with open(f'JA_{file_name}.txt', "a", encoding='utf-8') as text_file: 54 | text_file.write(result["translations"][0]["text"]) 55 | 56 | # 句点で改行する 57 | with open(f'JA_{file_name}.txt', "r", encoding="utf-8") as f: 58 | raw_data = f.read().replace("。", "。\n") 59 | with open(f'JA_{file_name}.txt', "w", encoding='utf-8') as f: 60 | f.write(raw_data) 61 | 62 | print('翻訳が完了しました') 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kindle-translator 2 | 3 | 非母語で書かれた電子書籍やPDFを母語で書かれたPDFに翻訳するツールです。 4 | 5 | # 特長 6 | 7 | 一冊50万字あるKindleの洋書を1分で日本語PDFに変換できます。 8 | 9 | キーボードの矢印キーでページ送りができるならKindleに限らずあらゆる電子書籍リーダおよびPDFビューワで使え、DeepLが対応している言語であれば英語以外の言語でも翻訳できます(仏→日とか)。 10 | 11 | # これがこうなる 12 | 13 | ![Kindle for PC - The Ethics of Cybersecurity_ 21 (The International Library of Ethics, Law and Technology) 2022_11_08 23_22_07](https://user-images.githubusercontent.com/99042183/201051157-86063261-ad8e-4ad8-977b-efc73a9359e5.png) 14 | 15 | ↓ 16 | 17 | ![Cybersecurity pdf - Adobe Acrobat Reader (64-bit) 2022_11_08 23_36_27](https://user-images.githubusercontent.com/99042183/201051238-43cbea77-1dae-4f98-9899-f37f24348940.png) 18 | 19 | (引用 **The Ethics of Cybersecurity (The International Library of Ethics, Law and Technology Book 21) by by Markus Christen, Bert Gordijn, et al. Feb 10, 2020) 20 | (CC: BYです) 21 | 22 | # 必要なライブラリ 23 | 24 | pyautogui 25 | img2pdf 26 | Pillow 27 | opencv-python 28 | pyocr 29 | fpdf2 30 | python-dotenv 31 | requests 32 | 33 | # 準備 34 | 35 | ## 1. pipまたはcondaが使えるPythonの実行環境 36 | 37 | 本ツールはpythonで書かれています。必要なライブラリをrequirements.txtおよびvenv.yamlに出力してあるので、pipを使う場合はrequirements.txtを、condaを使う場合はvenv.yamlをそれぞれ使用して仮想環境を整えてください。 38 | 39 | ### pipを使ったインストール 40 | 41 | ```bash 42 | pip install -r requirements.txt 43 | ``` 44 | 45 | ### condaを使ったインストール 46 | 47 | ```bash 48 | conda create -f venv.yml 49 | ``` 50 | 51 | ## 2. DeepL APIの登録 52 | 53 | 以下からAPI版DeepLに登録してください。(無料版であってもクレジットカードの登録が必要です) 54 | 55 | [https://www.deepl.com/ja/pro#developer](https://www.deepl.com/ja/pro#developer) 56 | 57 | 無料版で構いませんが、その場合はtranslator.pyの48行目のURLをapi.deepl.comからapi-free.deepl.comに変更しておく必要があります。 58 | 59 | 登録が済んだら、[local.env](./local.env)の`DEEPL_API_KEY`を入力してください。 60 | 61 | ## 3. Tesseractのインストール 62 | 63 | 以下から自分の環境に合ったインストーラをダウンロードしてください。 64 | 65 | [https://github.com/UB-Mannheim/tesseract/wiki](https://github.com/UB-Mannheim/tesseract/wiki) 66 | 67 | インストールが終わったら、[local.env](./local.env)の`TESSERACT_PATH`を自分の環境に適したパスへと変更してください。 68 | 69 | ## 4. フォルダパスの設定 70 | 71 | 画像保存用フォルダとテキストファイル(とできたPDFファイル)を保存するフォルダを準備し、[local.env](./local.env)の`OUTPUT_IMG_FOLDER`と`OUTPUT_TXT_FOLDER`を適宜変更してください。 72 | 73 | # 使い方 74 | 75 | ![Anaconda Powershell Prompt (Anaconda3) 2022_11_08 23_36_32](https://user-images.githubusercontent.com/99042183/201052912-550244b1-873b-4755-9c10-6936bee8f898.png) 76 | 77 | このリポジトリをcloneし、 78 | 79 | ``` 80 | python kindle_translator.py 81 | ``` 82 | 83 | を実行するだけでOKです。画像の通り、ほとんどの作業がコンソール上で進みます。 84 | 85 | その後は**電子書籍リーダをクリックしてアクティブにした上で**左上にカーソルをあてて10秒待ち、また右下にカーソルをあてて10秒待ちます。 86 | 87 | すると座標が取得でき、保存するファイル名・フォルダ名を聞かれるので入力しましょう。拡張子はいりません。 88 | 89 | あとは自動でフォルダが作られ、翻訳され、できたPDFがフォルダに格納されます。 90 | 91 | # 注意 92 | 93 | - デフォルトでは、ページ送り方向が【右】になっています。必要に応じて変更してください。(capture.py 104-105行目、118-119行目) 94 | - 現在のバージョンでは、スクリーンショットの撮影を行うため大量のpngファイルが生成されます。容量に余裕をもって実行してください。 95 | - 現在のバージョンでは、生成されたpngファイルやtxtファイルは自動で削除されません。必要に応じて手動で消去してください。 96 | 97 | # 制作者 98 | 99 | - 相良スヒト 100 | - Twitter: @1plus1is__ 101 | -------------------------------------------------------------------------------- /ocr.py: -------------------------------------------------------------------------------- 1 | """ 2 | kindle-translator 3 | """ 4 | import os 5 | import pyocr 6 | from os import path 7 | from constant import TESSERACT_PATH, OUTPUT_TXT_FOLDER 8 | from PIL import Image 9 | 10 | 11 | class OCR: 12 | """ 13 | 画像から文字起こしをするクラス 14 | """ 15 | def extract_characters( 16 | self, 17 | page_number: int, 18 | file_name: str, 19 | img_file_path: str 20 | ) -> str: 21 | """ 22 | :関数名: 23 | - extract_characters 24 | :機能: 25 | - 画像から文字を抽出する 26 | 27 | :引数: 28 | - page_number: 電子書籍全体のページ数(int型) 29 | - file_name: 保存したいファイルの名前兼フォルダの名前(str型) 30 | - img_file_path: 画像の保存先ファイルパス(str型) 31 | :戻り値: 32 | - 保存するテキストファイルの名前(str型) 33 | """ 34 | print('画像からテキストへの変換を行います') 35 | # 自分のOCRパスを設定する 36 | # 環境変数に設定しているのであれば以下2行は必要ない 37 | os.environ['PATH'] = os.environ['PATH'] + TESSERACT_PATH 38 | 39 | # tesseractの設定 40 | pyocr.tesseract.TESSERACT_CMD = path.join(TESSERACT_PATH, 'tesseract.exe') 41 | tools = pyocr.get_available_tools() 42 | tool = tools[0] 43 | 44 | file_name_txt = f'{file_name}.txt' 45 | 46 | # テキストファイルを出力するファイルパスを指定 47 | txt_file_path = path.join(OUTPUT_TXT_FOLDER, file_name) 48 | os.mkdir(txt_file_path) 49 | os.chdir(txt_file_path) 50 | print(f'{txt_file_path}にテキストファイルを保存します') 51 | 52 | # picture_0.pngのような名前の画像を一枚ずつ読み込みして文字を抽出 53 | text_list = [] 54 | for page in range(page_number): 55 | img = Image.open(f'{img_file_path}/picture_{page}.png') 56 | builder = pyocr.builders.TextBuilder(tesseract_layout=6) 57 | text = tool.image_to_string(img, lang="eng", builder=builder) 58 | text_list.append(text) 59 | 60 | # ひとつのテキストファイルに書き込み 61 | with open(file_name_txt, 'w', encoding='utf-8') as f: 62 | f.writelines(text_list) 63 | 64 | print('テキストファイルへの書き込みが終了しました') 65 | 66 | return file_name_txt, txt_file_path 67 | 68 | def divide_file( 69 | self, 70 | file_name: str, 71 | file_name_txt: str 72 | ) -> list: 73 | """ 74 | :関数名: 75 | - divide_file 76 | :機能: 77 | - テキストファイルをDeepL APIにPOSTできる適度な大きさに分割(およそ100kbごと) 78 | 79 | :引数: 80 | - file_name: 保存したいファイルの名前兼フォルダの名前(str型) 81 | - file_name_txt: 保存したいテキストファイルの名前(str型) 82 | :戻り値: 83 | - file_list: 分割した複数のテキストファイルの名前(list型) 84 | """ 85 | # read_data_size = 0 86 | num = 0 87 | file_list = [] 88 | 89 | print('テキストファイルを分割します') 90 | 91 | # 対象ファイルを開いて10万字ごと(およそ100kbごと)に分割して保存 92 | f = open(file_name_txt, "r", encoding="utf-8") 93 | file = f.read() 94 | file_length = len(file) 95 | split_length = 100000 96 | for data in range(0, file_length, split_length): 97 | page_data = (file[data:data+split_length]) 98 | save_file_name = f'{file_name}_{num}.txt' 99 | with open(save_file_name, 'w', encoding='utf-8') as save_file: 100 | save_file.write(page_data) 101 | # 読み込み位置の更新 102 | num += 1 103 | file_list.append(save_file_name) 104 | 105 | print('テキストファイルの分割が終了しました') 106 | 107 | return file_list 108 | -------------------------------------------------------------------------------- /capture.py: -------------------------------------------------------------------------------- 1 | """ 2 | kindle-translator 3 | """ 4 | import os 5 | import time 6 | import cv2 7 | import pyautogui 8 | from constant import OUTPUT_IMG_FOLDER 9 | from os import path 10 | 11 | 12 | class Capture: 13 | """ 14 | 特定の範囲のスクリーンショットを撮影するクラス 15 | """ 16 | 17 | def window_manager(self) -> int: 18 | """ 19 | :関数名: 20 | - window_manager 21 | :機能: 22 | - 撮影したい座標位置の取得 23 | 24 | :引数: 25 | - なし 26 | :戻り値: 27 | - tuple型( 28 | upper_left_x: 左上のx座標(int型) 29 | upper_left_y: 左上のy座標(int型) 30 | bottom_right_x: 右下のx座標(int型) 31 | bottom_right_y: 右下のy座標(int型) 32 | ) 33 | """ 34 | # 左上の座標を取得 35 | print("10秒以内に撮影したい範囲の左上にカーソルを合わせてください") 36 | time.sleep(10) 37 | upper_left_x, upper_left_y = pyautogui.position() 38 | print(f'左上の座標: {upper_left_x}, {upper_left_y}') 39 | 40 | # 右下の座標を取得 41 | print("10秒以内に撮影したい範囲の右下にカーソルを合わせてください") 42 | time.sleep(10) 43 | bottom_right_x, bottom_right_y = pyautogui.position() 44 | print(f'右上の座標: {bottom_right_x}, {bottom_right_y}') 45 | 46 | print('座標取得完了') 47 | 48 | return upper_left_x, upper_left_y, bottom_right_x, bottom_right_y 49 | 50 | def window_capture( 51 | self, 52 | x_1: int, 53 | y_1: int, 54 | x_2: int, 55 | y_2: int 56 | ) -> tuple: 57 | """ 58 | :関数名: 59 | - window_capture 60 | :機能: 61 | - 電子書籍のページごとのスクリーンショットを撮影する 62 | 63 | :引数: 64 | - x_1: 左上のx座標(int型) 65 | - y_1: 左上のy座標(int型) 66 | - x_2: 右下のx座標(int型) 67 | - y_2: 右下のy座標(int型) 68 | :戻り値: 69 | - tuple型( 70 | page: 最後のページが何ページ目かの数値(int型) 71 | file_name: 保存したいテキスト名兼ファイル名(str型) 72 | img_file_path: 画像の保存先パス(str型) 73 | ) 74 | """ 75 | # 保存するテキスト名(ファイル名にもなる)をコンソールで入力 76 | print('保存するテキストファイル名を入力してください:') 77 | file_name = input() 78 | 79 | # 撮影できる最大のページ数 80 | max_page = 3000 81 | 82 | # スクリーンショットの間隔 83 | span = 0.1 84 | 85 | # 電子書籍リーダを起動するために10秒間待機 86 | time.sleep(10) 87 | 88 | # 画像の出力先となるフォルダパスを設定してフォルダを生成 89 | img_file_path = path.join(OUTPUT_IMG_FOLDER, file_name) 90 | os.mkdir(img_file_path) 91 | 92 | # 出力先フォルダに画像を出力していく 93 | os.chdir(img_file_path) 94 | print(f'{img_file_path}に画像を保存していきます') 95 | 96 | # 最初のページの撮影 97 | png_name = 'picture_0.png' 98 | screen_shot = pyautogui.screenshot(region=( 99 | x_1, 100 | y_1, 101 | x_2 - x_1, 102 | y_2 - y_1 103 | )) 104 | screen_shot.save(png_name) 105 | 106 | # ページ送りの方向(デフォルトは右) 107 | pyautogui.keyDown('right') 108 | pyautogui.keyUp('right') 109 | time.sleep(span) 110 | 111 | # 残りのページのスクリーンショット撮影 112 | for page in range(1, max_page): 113 | png_name = f'picture_{page}.png' 114 | screen_shot = pyautogui.screenshot(region=( 115 | x_1, 116 | y_1, 117 | x_2 - x_1, 118 | y_2 - y_1 119 | )) 120 | screen_shot.save(png_name) 121 | pyautogui.keyDown('right') 122 | pyautogui.keyUp('right') 123 | img_prev_name = f'picture_{page-1}.png' 124 | 125 | # 前のページとの差分を比較 126 | # 最大ページ数に到達したら撮影を終了する 127 | # もしくは差分が0であれば(最後のページに到達したら)撮影を終了する 128 | img_prev = cv2.imread(img_prev_name) 129 | img_current = cv2.imread(png_name) 130 | time.sleep(span) 131 | mask = cv2.absdiff(img_prev, img_current) 132 | mask_gray = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY) 133 | if page == max_page or not cv2.countNonZero(mask_gray): 134 | break 135 | 136 | print('画像の保存が終了しました') 137 | 138 | return page, file_name, img_file_path 139 | --------------------------------------------------------------------------------