├── .gitignore ├── README.md ├── config ├── GoogleJS.js ├── logo.ico └── settin.json ├── configs.py ├── deploy.bat ├── deploy.sh ├── images ├── chinese.jpg ├── debug.jpg ├── full_result.jpg ├── japanese.jpg └── vis_resul.jpg ├── main.py ├── requirements.txt └── src ├── api.py ├── choose_range.py ├── hot_key.py ├── init.py ├── play_voice.py ├── range.py ├── screen_rate.py ├── settin.py ├── switch.py ├── translate.py └── vis_result.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | 3 | 4 | *.rar 5 | *.zip 6 | *.tar.gz 7 | 8 | *.TTC 9 | .idea/ 10 | config/image.jpg 11 | config/voice.mp3 12 | config/*.txt 13 | 14 | configs2.py 15 | 16 | __pycache__/ 17 | *.py[cod] 18 | *$py.class 19 | 20 | # C extensions 21 | *.so 22 | 23 | # Distribution / packaging 24 | .Python 25 | build/ 26 | develop-eggs/ 27 | dist/ 28 | downloads/ 29 | eggs/ 30 | .eggs/ 31 | lib/ 32 | lib64/ 33 | parts/ 34 | sdist/ 35 | var/ 36 | wheels/ 37 | pip-wheel-metadata/ 38 | share/python-wheels/ 39 | *.egg-info/ 40 | .installed.cfg 41 | *.egg 42 | MANIFEST 43 | 44 | # PyInstaller 45 | # Usually these files are written by a python script from a template 46 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 47 | *.manifest 48 | *.spec 49 | 50 | # Installer logs 51 | pip-log.txt 52 | pip-delete-this-directory.txt 53 | 54 | # Unit test / coverage reports 55 | htmlcov/ 56 | .tox/ 57 | .nox/ 58 | .coverage 59 | .coverage.* 60 | .cache 61 | nosetests.xml 62 | coverage.xml 63 | *.cover 64 | *.py,cover 65 | .hypothesis/ 66 | .pytest_cache/ 67 | 68 | # Translations 69 | *.mo 70 | *.pot 71 | 72 | # Django stuff: 73 | *.log 74 | local_settings.py 75 | db.sqlite3 76 | db.sqlite3-journal 77 | 78 | # Flask stuff: 79 | instance/ 80 | .webassets-cache 81 | 82 | # Scrapy stuff: 83 | .scrapy 84 | 85 | # Sphinx documentation 86 | docs/_build/ 87 | 88 | # PyBuilder 89 | target/ 90 | 91 | # Jupyter Notebook 92 | .ipynb_checkpoints 93 | 94 | # IPython 95 | profile_default/ 96 | ipython_config.py 97 | 98 | # pyenv 99 | .python-version 100 | 101 | # pipenv 102 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 103 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 104 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 105 | # install all needed dependencies. 106 | #Pipfile.lock 107 | 108 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 109 | __pypackages__/ 110 | 111 | # Celery stuff 112 | celerybeat-schedule 113 | celerybeat.pid 114 | 115 | # SageMath parsed files 116 | *.sage.py 117 | 118 | # Environments 119 | .env 120 | .venv 121 | env/ 122 | venv/ 123 | ENV/ 124 | env.bak/ 125 | venv.bak/ 126 | 127 | # Spyder project settings 128 | .spyderproject 129 | .spyproject 130 | 131 | # Rope project settings 132 | .ropeproject 133 | 134 | # mkdocs documentation 135 | /site 136 | 137 | # mypy 138 | .mypy_cache/ 139 | .dmypy.json 140 | dmypy.json 141 | 142 | # Pyre type checker 143 | .pyre/ 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dango-OCR(Windows, Mac, Ubuntu) 2 | 3 | #### 软件介绍: 4 | DangoOCR:一个开源的文字识别工具,通过选择识别范围自动截取屏幕图片,或者手动加载本地图片实现文字的识别/提取。特点: 5 | + 界面简单,即下即用 6 | + 目前支持汉语, 日语, 英语, 韩语, 德语, 法语的文字识别, 会持续优化识别算法 7 | + 支持截图或者本地加载图片进行识别 8 | + 支持可视化识别结果,可以手动修正识别结果,并导出文件(.docx, .txt)及对应图片 9 | + 支持翻译为汉语 10 | + 如果开启“自动模式”, 选择一个区域后可以自动截屏并进行识别 11 | + 如果无法使用, 复制"config/error.txt"的内容进行反馈 12 | + 软件下载(存放目录路径中不能有空格): 13 | ##### ~~服务器已到期,不再提供下载Windows版~~[下载](https://images-1302624744.cos.ap-beijing.myqcloud.com/software/DangoOCR_windows_v1.rar) 14 | ##### ~~服务器已到期,不再提供下载Mac版(只测了macOS 10.13.3)~~[下载](https://images-1302624744.cos.ap-beijing.myqcloud.com/software/DangoOCR_mac_v1.rar) 15 | ##### ~~服务器已到期,不再提供下载Ubuntu版(只测了ubuntu16.04)~~[下载](https://images-1302624744.cos.ap-beijing.myqcloud.com/software/DangoOCR_ubuntu_v1.rar) 16 | 17 | #### OCR算法服务 18 | + 如果您对OCR算法不感兴趣, 可以直接使用上面已经做好的软件,解压后可以直接使用。如果您想进一步了解文字识别算法或者要自己搭建算法服务,可以参考[OCR算法服务](https://github.com/zhangming8/ocr_algo_server)进行部署并确保可以调通接口。在本工程中首先修改'config.py'中的ocr_request_url为搭好的服务地址,main.py是程序入口。 19 | + 相关说明1 [手把手教你用PaddleOCR与PyQT实现多语言文字识别的程序](https://mp.weixin.qq.com/s/MSSsTLrxPvxpl6ij7jIR-w) 20 | + 相关说明2 [使用飞桨一步步实现多语言OCR文字识别软件](https://blog.csdn.net/u010397980/article/details/111940566) 21 | 22 | ### TODO计划 23 | + 优化高分辨率屏幕文字较小 24 | + 导出docx文件时优化排版 25 | + 算法轻量化,实现离线文字识别 26 | 27 | ### 使用方法(以Windows系统为例): 28 | 29 | + 1 解压压缩包后,找到“DangoOCR.exe”文件双击即可运行("Ubuntu"系统下也是双击。macOS系统下需要右键,打开方式选择终端)。在“设置”界面设置"待识别的语言类型"。 30 | 31 | + 2 软件界面如下(汉语识别)。 32 |
33 | 34 |
35 | 36 | + 3 如果在'设置'勾选了'可视化识别结果',可以对结果进行修改并可以导出为txt/docx。 37 |
38 | 39 |
40 | 41 | + 4 软件界面如下(日语识别)。 42 |
43 | 44 |
45 | 46 | + 5 识别英语文档并手动修改。 47 |
48 | 49 |
50 | 51 | + 6 算法debug 52 |
53 | 54 |
55 | 56 | 57 | #### 参考: 58 | + OCR服务: https://github.com/zhangming8/ocr_algo_server 59 | + OCR算法参考百度PaddleOCR: https://github.com/PaddlePaddle/PaddleOCR 60 | + 界面代码参考 https://github.com/PantsuDango/Dango-Translator 61 | -------------------------------------------------------------------------------- /config/GoogleJS.js: -------------------------------------------------------------------------------- 1 | function TL(a) { 2 | var k = ""; 3 | var b = 406644; 4 | var b1 = 3293161072; 5 | var jd = "."; 6 | var $b = "+-a^+6"; 7 | var Zb = "+-3^+b+-f"; 8 | for (var e = [], f = 0, g = 0; g < a.length; g++) { 9 | var m = a.charCodeAt(g); 10 | 128 > m ? e[f++] = m : (2048 > m ? e[f++] = m >> 6 | 192 : (55296 == (m & 64512) && g + 1 < a.length && 56320 == (a.charCodeAt(g + 1) & 64512) ? (m = 65536 + ((m & 1023) << 10) + (a.charCodeAt(++g) & 1023), 11 | e[f++] = m >> 18 | 240, 12 | e[f++] = m >> 12 & 63 | 128) : e[f++] = m >> 12 | 224, 13 | e[f++] = m >> 6 & 63 | 128), 14 | e[f++] = m & 63 | 128) 15 | } 16 | a = b; 17 | for (f = 0; f < e.length; f++) a += e[f], 18 | a = RL(a, $b); 19 | a = RL(a, Zb); 20 | a ^= b1 || 0; 21 | 0 > a && (a = (a & 2147483647) + 2147483648); 22 | a %= 1E6; 23 | return a.toString() + jd + (a ^ b) 24 | }; 25 | function RL(a, b) { 26 | var t = "a"; 27 | var Yb = "+"; 28 | for (var c = 0; c < b.length - 2; c += 3) { 29 | var d = b.charAt(c + 2), 30 | d = d >= t ? d.charCodeAt(0) - 87 : Number(d), 31 | d = b.charAt(c + 1) == Yb ? a >>> d: a << d; 32 | a = b.charAt(c) == Yb ? a + d & 4294967295 : a ^ d 33 | } 34 | return a 35 | } -------------------------------------------------------------------------------- /config/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangming8/Dango-ocr/bc2e44ded4fb60b572f4209b02156558d0cd1b10/config/logo.ico -------------------------------------------------------------------------------- /config/settin.json: -------------------------------------------------------------------------------- 1 | { 2 | "showColorType": "False", 3 | "fontColor": { 4 | "caiyunPrivate": "#ff0000", 5 | "original": "#d93636", 6 | "translated": "#1E90FF" 7 | }, 8 | "horizontal": 0, 9 | "showOriginal": "True", 10 | "vis_result": "True", 11 | "need_translate": "False", 12 | "showHotKeyValue2": "F2", 13 | "fontSize": 15, 14 | "showHotKey2": "True", 15 | "fontType": "\u534e\u5eb7\u65b9\u5706\u4f53W7", 16 | "range": { 17 | "X2": 1073, 18 | "Y2": 353, 19 | "X1": 357, 20 | "Y1": 286 21 | }, 22 | "showHotKeyValue1": "F3", 23 | "showClipboard": "False", 24 | "language": "CH", 25 | "sign": 1, 26 | "showHotKey1": "True" 27 | } -------------------------------------------------------------------------------- /configs.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | from sys import platform, argv 4 | from uuid import UUID, getnode 5 | 6 | # mac 系统下打包后需要全路径不能使用相对路径 7 | folder_path = os.path.dirname(os.path.realpath(argv[0])).replace("\\", "/") 8 | if folder_path.split("/")[-1] == 'src': 9 | folder_path = folder_path + "/.." 10 | 11 | 12 | class Config(object): 13 | language_name = [["CH", "汉/英语(Chinese)", "汉语"], ["CH_h", "汉/英语高精度(Chinese)", "汉高"], ["JAP", "日语(Japanese)", "日语"], 14 | ["ENG", "英语(English)", "英语"], ["KOR", "韩语(Korean)", "韩语"], ["French", "法语(French)", "法语"], 15 | ["German", "德语(German)", "德语"]] 16 | voice_language = {"JAP": "ja", "ENG": "en", "KOR": "ko", "CH": "zh-CN", "CH_h": "zh-CN", "French": "fr", 17 | "German": "de"} 18 | # ocr server 19 | ocr_request_url = "http://0.0.0.0:8811/dango/algo/ocr/server" 20 | 21 | # 图片相似度阈值 22 | similarity_score = 0.95 23 | # 控制帧率(10FPs) 24 | delay_time = 1. / 10 25 | # 请求time out 26 | time_out = 30 27 | debug = False # True 28 | 29 | language_map = {idx: name for idx, name in enumerate(language_name)} 30 | language_map_reverse = {v[0]: k for k, v in language_map.items()} 31 | letter_chinese_dict = {i: k for i, j, k in language_name} 32 | mac = UUID(int=getnode()).hex[-12:] 33 | platform = platform 34 | -------------------------------------------------------------------------------- /deploy.bat: -------------------------------------------------------------------------------- 1 | 2 | set save_name=DangoOCR_windows_v1 3 | set main=main 4 | 5 | scp configs2.py configs.py 6 | echo.>config\error.txt 7 | 8 | del/s/q build\*.* 9 | rd/s/q build 10 | del/s/q dist\*.* 11 | rd/s/q dist 12 | del %main%.spec 13 | del %save_name%.rar 14 | 15 | pyinstaller -D -w -i config\logo.ico %main%.py 16 | scp -r config dist\%main% 17 | 18 | ren "dist\%main%\%main%.exe" DangoOCR.exe 19 | ren "dist\%main%" "%save_name%" 20 | scp *.TTC dist 21 | 22 | cd dist 23 | "C:\Program Files\WinRAR\Rar.exe" a "%save_name%.rar" "%save_name%" *.TTC 24 | cd .. 25 | 26 | move "dist\%save_name%.rar" .\ 27 | 28 | del/s/q build\*.* 29 | rd/s/q build 30 | del/s/q dist\*.* 31 | rd/s/q dist 32 | del %main%.spec 33 | 34 | echo "done, save to: %save_name%.rar" 35 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | save_name="DangoOCR_ubuntu_v1" 4 | main="main" 5 | 6 | cp configs2.py configs.py 7 | cp /dev/null config/error.txt 8 | cp /dev/null config/识别结果.txt 9 | 10 | rm -rf build dist "${main}.spec" "${save_name}.rar" 11 | 12 | pyinstaller -D -w -i config/logo.ico "${main}.py" 13 | cp -r config "dist/${main}/" 14 | 15 | mv "dist/${main}/${main}" "dist/${main}/DangoOCR" 16 | mv "dist/${main}" "dist/${save_name}" 17 | cp *.TTC dist/ 18 | 19 | cd dist 20 | rar a "${save_name}.rar" "${save_name}" *.TTC 21 | 22 | cd .. 23 | 24 | mv "dist/${save_name}.rar" ./ 25 | 26 | rm -rf build dist "${main}.spec" 27 | 28 | echo "done, save to: ${save_name}.rar" 29 | -------------------------------------------------------------------------------- /images/chinese.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangming8/Dango-ocr/bc2e44ded4fb60b572f4209b02156558d0cd1b10/images/chinese.jpg -------------------------------------------------------------------------------- /images/debug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangming8/Dango-ocr/bc2e44ded4fb60b572f4209b02156558d0cd1b10/images/debug.jpg -------------------------------------------------------------------------------- /images/full_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangming8/Dango-ocr/bc2e44ded4fb60b572f4209b02156558d0cd1b10/images/full_result.jpg -------------------------------------------------------------------------------- /images/japanese.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangming8/Dango-ocr/bc2e44ded4fb60b572f4209b02156558d0cd1b10/images/japanese.jpg -------------------------------------------------------------------------------- /images/vis_resul.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangming8/Dango-ocr/bc2e44ded4fb60b572f4209b02156558d0cd1b10/images/vis_resul.jpg -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | from json import load, dump 5 | from qtawesome import icon as qticon 6 | from traceback import format_exc 7 | from threading import Thread 8 | 9 | from PyQt5.QtWidgets import QApplication 10 | 11 | from src.init import MainInterface 12 | from src.range import WScreenShot 13 | from src.choose_range import Range 14 | from src.api import MessageBox, write_error 15 | from src.settin import SettinInterface 16 | from src.screen_rate import get_screen_rate 17 | from src.hot_key import pyhk 18 | from configs import Config, folder_path 19 | 20 | config = Config() 21 | 22 | 23 | class Translater(): 24 | # 打开配置文件 25 | def open_settin(self): 26 | 27 | with open(folder_path + '/config/settin.json') as file: 28 | self.data = load(file) 29 | 30 | # 保存配置文件 31 | def save_settin(self): 32 | 33 | with open(folder_path + '/config/settin.json', 'w') as file: 34 | dump(self.data, file, indent=2) 35 | 36 | # 设置快捷键 37 | def set_hotKey(self): 38 | 39 | try: 40 | self.hotKey = pyhk() 41 | self.id_translate = False # 翻译快捷键预设 42 | self.id_range = False # 范围快捷键预设 43 | 44 | # 是否启用翻译键快捷键 45 | if self.data["showHotKey1"] == "True": 46 | self.id_translate = self.hotKey.addHotkey([self.data["showHotKeyValue1"]], self.Init.start_login) 47 | # 是否截图键快捷键 48 | if self.data["showHotKey2"] == "True": 49 | self.id_range = self.hotKey.addHotkey([self.data["showHotKeyValue2"]], self.goto_range) 50 | 51 | except Exception: 52 | write_error(format_exc()) 53 | 54 | # 进入范围选取 55 | def goto_range(self): 56 | 57 | try: 58 | self.Range = WScreenShot(self.Init, self.chooseRange) # 范围界面 59 | # 判断当前翻译运行状态,若为开始则切换为停止 60 | if self.Init.mode == True: 61 | self.open_settin() 62 | self.data["sign"] = 1 # 重置运行状态标志符 63 | self.save_settin() 64 | # 改变翻译键的图标为停止图标 65 | self.Init.StartButton.setIcon(qticon('fa.play', color='white')) 66 | 67 | self.Range.show() # 打开范围界面 68 | self.Init.show() # 翻译界面会被顶掉,再次打开 69 | 70 | if pyhk is not None and not self.thread_hotKey.isAlive(): 71 | self.thread_hotKey.start() 72 | 73 | except Exception: 74 | write_error(format_exc()) 75 | 76 | # 退出程序 77 | def close(self): 78 | 79 | path = folder_path + "/config/识别结果.txt" 80 | MessageBox('贴心小提示~', '结果已自动保存至\n%s\n可自行定期清理' % path) 81 | self.Init.close() # 关闭翻译界面 82 | if pyhk is not None: 83 | self.hotKey.end() # 关闭监控快捷键事件 84 | 85 | # 进入设置页面 86 | def goto_settin(self): 87 | 88 | # 判断当前翻译运行状态,若为开始则切换为停止 89 | if self.Init.mode == True: 90 | self.open_settin() 91 | self.data["sign"] = 1 # 重置运行状态标志符 92 | self.save_settin() 93 | # 改变翻译键的图标为停止图标 94 | self.Init.StartButton.setIcon(qticon('fa.play', color='white')) 95 | 96 | # self.Init.close() # 关闭翻译界面 97 | self.Settin.tabWidget.setCurrentIndex(0) # 预设设置页面的初始为第一栏 98 | self.Settin.show() # 打开设置页面 99 | 100 | # 刷新主界面 101 | def updata_Init(self): 102 | 103 | self.Settin.save_settin() # 保存设置 104 | self.data = self.Settin.data 105 | self.Settin.close() 106 | self.Init.image = None 107 | self.Init.original = '' 108 | # self.open_settin() 109 | 110 | # 刷新翻译界面的背景透明度 111 | horizontal = (self.data["horizontal"]) / 100 112 | if horizontal == 0: 113 | horizontal = 0.01 114 | self.Init.translateText.setStyleSheet("border-width:0;\ 115 | border-style:outset;\ 116 | border-top:0px solid #e8f3f9;\ 117 | color:white;\ 118 | font-weight: bold;\ 119 | background-color:rgba(62, 62, 62, %s)" 120 | % (horizontal)) 121 | self.Init.languageText.setText(config.letter_chinese_dict[self.data["language"]]) 122 | 123 | if pyhk is not None: 124 | # 是否注销翻译键快捷键 125 | if self.id_translate: 126 | self.hotKey.removeHotkey(id=self.id_translate) 127 | # 是否注销范围键快捷键 128 | if self.id_range: 129 | self.hotKey.removeHotkey(id=self.id_range) 130 | # 是否启用翻译键快捷键 131 | if self.data["showHotKey1"] == "True": 132 | self.id_translate = self.hotKey.addHotkey([self.data["showHotKeyValue1"]], self.Init.start_login) 133 | # 是否截图键快捷键 134 | if self.data["showHotKey2"] == "True": 135 | self.id_range = self.hotKey.addHotkey([self.data["showHotKeyValue2"]], self.goto_range) 136 | 137 | if not self.thread_hotKey.isAlive(): 138 | self.thread_hotKey.start() 139 | 140 | def keyPressEvent(self, QKeyEvent): 141 | 142 | if QKeyEvent.key() == self.Settin.QtHotKeysMaps[self.data["showHotKeyValue2"]] and self.data[ 143 | "showHotKey2"] == "True": 144 | self.goto_range() 145 | 146 | if QKeyEvent.key() == self.Settin.QtHotKeysMaps[self.data["showHotKeyValue1"]] and self.data[ 147 | "showHotKey1"] == "True": 148 | self.Init.start_login() 149 | 150 | # if QKeyEvent.modifiers() == Qt.ControlModifier | Qt.ShiftModifier and QKeyEvent.key() == Qt.Key_A: # 三键组合 151 | # print('按下了Ctrl+Shift+A键') 152 | 153 | # 登录成功后 154 | def Login_success(self): 155 | try: 156 | self.Settin = SettinInterface(self.screen_scale_rate) # 设置界面 157 | self.Init = MainInterface(self.screen_scale_rate, self.user) # 翻译界面 158 | self.chooseRange = Range(self.data["range"]['X1'], self.data["range"]['Y1'], self.data["range"]['X2'], 159 | self.data["range"]['Y2']) 160 | 161 | if pyhk is not None: 162 | self.set_hotKey() # 设置快捷键 163 | # 监听快捷键事件加入子线程 164 | self.thread_hotKey = Thread(target=self.hotKey.start) 165 | self.thread_hotKey.setDaemon(True) 166 | self.thread_hotKey.start() 167 | else: 168 | # 在mac/ubuntu上用qt自己的热键, 但是不支持全局热键 169 | self.Init.keyPressEvent = self.keyPressEvent 170 | 171 | # 点击设置键后执行的函数 172 | self.Init.SettinButton.clicked.connect(self.goto_settin) 173 | # 点击语言提示按钮 174 | self.Init.languageText.clicked.connect(self.goto_settin) 175 | # 点击范围键后执行的函数 176 | self.Init.RangeButton.clicked.connect(self.goto_range) 177 | # 点击退出键后执行的函数 178 | self.Init.QuitButton.clicked.connect(self.close) 179 | # 点击设置页面的保存键后执行的函数 180 | self.Settin.SaveButton.clicked.connect(self.updata_Init) 181 | # 点击设置页面的退出键后执行的函数 182 | self.Settin.CancelButton.clicked.connect(self.Settin.close) 183 | 184 | except Exception: 185 | write_error(format_exc()) 186 | 187 | # 主循环 188 | def main(self): 189 | 190 | try: 191 | self.screen_scale_rate = get_screen_rate() 192 | 193 | self.open_settin() 194 | self.data["sign"] = 1 # 重置运行状态标志符 195 | self.save_settin() 196 | 197 | App = QApplication(sys.argv) 198 | 199 | self.user = "admin" 200 | self.Login_success() 201 | self.Init.show() 202 | 203 | App.exit(App.exec_()) 204 | 205 | except Exception: 206 | write_error(format_exc()) 207 | 208 | 209 | if __name__ == '__main__': 210 | Dango = Translater() 211 | Dango.main() 212 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | altgraph==0.17 2 | certifi==2020.4.5.1 3 | chardet==3.0.4 4 | decorator==4.4.2 5 | future==0.18.2 6 | idna==2.9 7 | imageio==2.8.0 8 | Js2Py==0.70 9 | networkx==2.4 10 | numpy==1.18.3 11 | opencv-python==4.2.0.34 12 | pefile==2019.4.18 13 | Pillow==7.1.2 14 | playsound==1.2.2 15 | PyExecJS==1.5.1 16 | #PyHook3==1.6.1 17 | PyInstaller==3.6 18 | pyjsparser==2.7.1 19 | pyperclip==1.8.0 20 | PyQt5==5.14.2 21 | PyQt5-sip==12.7.2 22 | #PyQtWebEngine==5.14.0 23 | pytz==2020.1 24 | PyWavelets==1.1.1 25 | #pywin32==227 26 | #pywin32-ctypes==0.2.0 27 | QtAwesome==0.7.1 28 | QtPy==1.9.0 29 | requests==2.23.0 30 | scikit-image==0.15.0 31 | scipy==1.4.1 32 | six==1.14.0 33 | tzlocal==2.0.0 34 | urllib3==1.25.9 35 | python-docx 36 | paddlepaddle-gpu==2.0.0b0 37 | -------------------------------------------------------------------------------- /src/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from requests import post 4 | from base64 import b64encode 5 | from json import load, dump 6 | from traceback import format_exc 7 | from cv2 import imencode 8 | from time import time, localtime, strftime 9 | 10 | from PyQt5.QtCore import Qt 11 | from PyQt5.QtWidgets import QMessageBox, QPushButton 12 | from PyQt5.QtGui import QIcon, QPixmap 13 | 14 | from configs import Config, folder_path 15 | 16 | config = Config() 17 | 18 | 19 | def write_error(info): 20 | print(info) 21 | with open(folder_path + "/config/error.txt", "a+", encoding='utf-8') as f: 22 | time_array = localtime(int(time())) 23 | str_date = strftime("%Y-%m-%d %H:%M:%S", time_array) 24 | f.write("----------- {} ----------\n".format(str_date)) 25 | f.write(str(info) + "\n\n\n") 26 | 27 | 28 | # 出错时停止翻译状态 29 | def error_stop(): 30 | with open(folder_path + '/config/settin.json') as file: 31 | data = load(file) 32 | if data["sign"] % 2 == 0: 33 | data["sign"] = 1 34 | with open(folder_path + '/config/settin.json', 'w') as file: 35 | dump(data, file, indent=2) 36 | 37 | 38 | # 错误提示窗口 39 | def MessageBox(title, text): 40 | error_stop() # 停止翻译状态 41 | 42 | messageBox = QMessageBox() 43 | messageBox.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowMaximizeButtonHint | Qt.MSWindowsFixedSizeDialogHint) 44 | # 窗口图标 45 | icon = QIcon() 46 | icon.addPixmap(QPixmap(folder_path + "/config/logo.ico"), QIcon.Normal, QIcon.On) 47 | messageBox.setWindowIcon(icon) 48 | # 设定窗口标题和内容 49 | messageBox.setWindowTitle(title) 50 | messageBox.setText(text) 51 | messageBox.addButton(QPushButton('好滴'), QMessageBox.YesRole) 52 | # 显示窗口 53 | messageBox.exec_() 54 | 55 | 56 | def image_to_base64(image_np): 57 | image = imencode('.jpg', image_np)[1] 58 | image_code = str(b64encode(image))[2:-1] 59 | return image_code 60 | 61 | 62 | def ocr(data, image): 63 | img = image_to_base64(image) 64 | data = {"image": img, "language_type": data["language"], "user_id": config.mac, "platform": config.platform, 65 | 'translate': 'no' if data['need_translate'] == "False" or data["language"] == 'CH' or data[ 66 | "language"] == 'CH_h' else 'yes'} 67 | 68 | try: 69 | response = post(config.ocr_request_url, data=data, timeout=config.time_out) 70 | except: 71 | write_error("ocr识别服务:" + format_exc()) 72 | sentence = '' 73 | error_stop() 74 | return None, sentence, [], '' 75 | 76 | if response: 77 | data = response.json()['data'] 78 | result = data['result'][0] # batch result, now we only use first one 79 | # print(data) 80 | sentence = [] 81 | 82 | for one_ann in result: 83 | text = one_ann["text"] 84 | conf = one_ann["confidence"] 85 | points = one_ann["text_region"] 86 | sentence.append(text) 87 | sentence = " ".join(sentence) 88 | if data['translated']: 89 | translate_result = data['translate_result'] 90 | else: 91 | translate_result = '' 92 | return True, sentence, result, translate_result 93 | 94 | else: 95 | sentence = 'OCR错误:response无响应' 96 | return None, sentence, [], '' 97 | -------------------------------------------------------------------------------- /src/choose_range.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from json import load, dump 4 | from traceback import format_exc 5 | 6 | from PyQt5.QtWidgets import QMainWindow, QLabel, QStatusBar, QPushButton, QApplication 7 | from PyQt5.QtGui import QFont, QCursor, QMouseEvent 8 | from PyQt5.QtCore import Qt, QPoint, QRect 9 | 10 | from configs import folder_path 11 | from src.api import write_error 12 | 13 | 14 | class Range(QMainWindow): 15 | 16 | def __init__(self, X1, Y1, X2, Y2): 17 | 18 | try: 19 | super(Range, self).__init__() 20 | self.setGeometry(X1, Y1, X2 - X1, Y2 - Y1) 21 | 22 | # 窗口无标题栏、窗口置顶、窗口透明 23 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.Tool) 24 | self.setAttribute(Qt.WA_TranslucentBackground) 25 | 26 | self.Label = QLabel(self) 27 | self.Label.setObjectName("dragLabel") 28 | self.Label.setGeometry(0, 0, X2 - X1, Y2 - Y1) 29 | self.Label.setStyleSheet("border-width:1;\ 30 | border:2px dashed #1E90FF;\ 31 | background-color:rgba(62, 62, 62, 0.01)") 32 | 33 | # 此Label用于当鼠标进入界面时给出颜色反应 34 | self.dragLabel = QLabel(self) 35 | self.dragLabel.setObjectName("dragLabel") 36 | self.dragLabel.setGeometry(0, 0, 4000, 2000) 37 | 38 | self.Font = QFont() 39 | self.Font.setFamily("华康方圆体W7") 40 | self.Font.setPointSize(12) 41 | 42 | # 隐藏按钮 43 | self.Button = QPushButton(self) 44 | self.Button.setGeometry(QRect(X2 - X1 - 40, 0, 40, 30)) 45 | self.Button.setStyleSheet("background-color:rgba(62, 62, 62, 0.3);" 46 | "color:#1E90FF") 47 | self.Button.setFont(self.Font) 48 | self.Button.setText("隐藏") 49 | self.Button.setCursor(QCursor(Qt.PointingHandCursor)) 50 | self.Button.clicked.connect(self.close) 51 | self.Button.hide() 52 | 53 | # 右下角用于拉伸界面的控件 54 | self.statusbar = QStatusBar(self) 55 | self.setStatusBar(self.statusbar) 56 | self._startPos = None 57 | except Exception: 58 | write_error(format_exc()) 59 | 60 | # 鼠标移动事件 61 | def mouseMoveEvent(self, e: QMouseEvent): 62 | try: 63 | if self._startPos is not None: 64 | self._endPos = e.pos() - self._startPos 65 | self.move(self.pos() + self._endPos) 66 | except Exception: 67 | write_error(format_exc()) 68 | 69 | # 鼠标按下事件 70 | def mousePressEvent(self, e: QMouseEvent): 71 | try: 72 | if e.button() == Qt.LeftButton: 73 | self._isTracking = True 74 | self._startPos = QPoint(e.x(), e.y()) 75 | except Exception: 76 | write_error(format_exc()) 77 | 78 | # 鼠标松开事件 79 | def mouseReleaseEvent(self, e: QMouseEvent): 80 | try: 81 | if e.button() == Qt.LeftButton: 82 | self._isTracking = False 83 | self._startPos = None 84 | self._endPos = None 85 | except Exception: 86 | write_error(format_exc()) 87 | 88 | # 鼠标进入控件事件 89 | def enterEvent(self, QEvent): 90 | try: 91 | rect = self.geometry() 92 | X1 = rect.left() 93 | Y1 = rect.top() 94 | X2 = rect.left() + rect.width() 95 | Y2 = rect.top() + rect.height() 96 | self.Button.setGeometry(QRect(X2 - X1 - 40, 0, 40, 30)) 97 | self.Button.show() 98 | self.dragLabel.setStyleSheet("background-color:rgba(62, 62, 62, 0.3)") 99 | except Exception: 100 | write_error(format_exc()) 101 | 102 | # 鼠标离开控件事件 103 | def leaveEvent(self, QEvent): 104 | try: 105 | self.dragLabel.setStyleSheet("background-color:none") 106 | self.Label.setGeometry(0, 0, self.width(), self.height()) 107 | self.Button.hide() 108 | 109 | rect = self.geometry() 110 | X1 = rect.left() 111 | Y1 = rect.top() 112 | X2 = rect.left() + rect.width() 113 | Y2 = rect.top() + rect.height() 114 | set_path = folder_path + '/config/settin.json' 115 | with open(set_path) as file: 116 | data = load(file) 117 | data["range"]["X1"] = X1 118 | data["range"]["Y1"] = Y1 119 | data["range"]["X2"] = X2 120 | data["range"]["Y2"] = Y2 121 | with open(set_path, 'w') as file: 122 | dump(data, file, indent=2) 123 | 124 | except Exception: 125 | write_error(format_exc()) 126 | 127 | 128 | if __name__ == '__main__': 129 | import sys 130 | 131 | QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) 132 | app = QApplication(sys.argv) 133 | win = Range(500, 500, 1000, 600) 134 | win.show() 135 | app.exec_() 136 | -------------------------------------------------------------------------------- /src/hot_key.py: -------------------------------------------------------------------------------- 1 | # _*_ coding:UTF-8 _*_ 2 | 3 | try: 4 | from pythoncom import PumpMessages 5 | from PyHook3 import HookManager 6 | from ctypes import windll 7 | from _thread import start_new_thread 8 | 9 | pyhook_flag = True 10 | except: 11 | from src.api import write_error 12 | from configs import Config 13 | write_error("[INFO] 系统 {} 无法设置全局快捷键, 使用Qt自己的热键".format(Config().platform)) 14 | pyhook_flag = False 15 | 16 | 17 | class pyhook: 18 | """Hotkey class extending PyHook3""" 19 | 20 | def __init__(self): 21 | # initiate internal hotkey list 22 | self.KeyDownID = [] 23 | self.KeyDown = [] 24 | 25 | # initiate user hotkey list 26 | self.UserHKF = [] 27 | self.UserHKFUp = [] 28 | self.HKFIDDict = {} 29 | 30 | # create Lookup for event keys and ids 31 | # for keyboards 32 | self.ID2Key, self.Key2ID = self.createKeyLookup() 33 | 34 | # for mouse, artifical lookup first 35 | # self.mouseDown_MID2eventMessage, self.mouseDown_eventMessage2MID, self.mouseUp_MID2eventMessage, self.mouseUp_eventMessage2MID = self.createMouseLookup() 36 | 37 | # create list for singleEvent, ie there is only a key down, no key up 38 | # self.singleEventMouseMessage, self.singleEventMID = self.createSingleEventMouse() 39 | 40 | # creat list for merged keys like Ctrl <= Lcontrol, Rcontrol 41 | 42 | self.KeyID2MEID = self.createMergeKeys() 43 | 44 | # create a hook manager 45 | self.hm = HookManager() 46 | 47 | # watch for all keyboard events 48 | self.hm.KeyDown = self.OnKeyDown 49 | self.hm.KeyUp = self.OnKeyUp 50 | 51 | # watch for all mouse events 52 | self.hm.MouseAllButtonsDown = self.OnKeyDown 53 | self.hm.MouseAllButtonsUp = self.OnKeyUp 54 | 55 | self.hm.MouseMove = self.OnSingleEventMouse 56 | self.hm.MouseWheel = self.OnSingleEventMouse 57 | 58 | # set the hook 59 | self.hm.HookKeyboard() 60 | # self.hm.HookMouse() 61 | 62 | # set Ending hotkey 63 | self.EndHotkey = ['Ctrl', 'Shift', 'Q'] 64 | self.setEndHotkey(self.EndHotkey) 65 | 66 | def start(self): 67 | """Start pyhk to check for hotkeys""" 68 | while True: 69 | PumpMessages() 70 | 71 | def end(self): 72 | """End pyhk to check for hotkeys""" 73 | windll.user32.PostQuitMessage(0) 74 | 75 | # -------------------------------------------------------- 76 | 77 | def isIDHotkey(self, hotkey): 78 | """Test if hotkey is coded in IDs""" 79 | for key in hotkey: 80 | if type(key) == str: 81 | return False 82 | return True 83 | 84 | def isHumanHotkey(self, hotkey): 85 | """Test if hotkey is coded human readable. Ex ALT F2""" 86 | try: 87 | [self.Key2ID[key] for key in hotkey] 88 | except: 89 | return False 90 | return True 91 | 92 | def hotkey2ID(self, hotkey): 93 | """Converts human readable hotkeys to IDs""" 94 | if self.isHumanHotkey(hotkey): 95 | return [self.Key2ID[key] for key in hotkey] 96 | else: 97 | raise Exception("Invalid Hotkey") 98 | 99 | def getHotkeyList(self, hotkey): 100 | """Create a IDlist of hotkeys if necessary to ensure functionality of merged hotkeys""" 101 | hotkeyVariationList = [] 102 | hotkeyList = [] 103 | 104 | # convert everyting into ID,MID,MEID 105 | if self.isIDHotkey(hotkey): 106 | IDHotkey = hotkey 107 | else: 108 | IDHotkey = self.hotkey2ID(hotkey) 109 | 110 | IDHotkeyTemp = IDHotkey[:] 111 | 112 | # check if there is a MEID and create accorind hotkeyVariationList 113 | for Key in self.KeyID2MEID: 114 | 115 | if self.KeyID2MEID[Key] in IDHotkeyTemp: 116 | # merged hotkey in hotkey 117 | # get MEID 118 | MEIDTemp = self.KeyID2MEID[Key] 119 | # get all KeyIDs 120 | KeyIDVariationTemp = [k for k in self.KeyID2MEID if self.KeyID2MEID[k] == MEIDTemp] 121 | 122 | # remove MEID from IDHotekey 123 | IDHotkeyTemp.remove(MEIDTemp) 124 | 125 | # store according MEID and KeyIDList 126 | hotkeyVariationList.append(KeyIDVariationTemp) 127 | 128 | if len(hotkeyVariationList) > 0: 129 | hotkeyVariationList.append(IDHotkeyTemp) 130 | # get all possible permutations 131 | hotkeyList = UniquePermutation(hotkeyVariationList) 132 | else: 133 | hotkeyList = [IDHotkey] 134 | 135 | return hotkeyList 136 | 137 | def addHotkey(self, hotkey, fhot, isThread=False, up=False): 138 | """Add hotkeys with according function""" 139 | hotkeyList = self.getHotkeyList(hotkey) 140 | 141 | newHKFID = self.getNewHKFID() 142 | self.HKFIDDict[newHKFID] = [] 143 | 144 | if up: 145 | 146 | if len(hotkey) < 2: 147 | if isThread: 148 | t = ExecFunThread(fhot) 149 | for IDHotKeyItem in hotkeyList: 150 | self.UserHKFUp.append([IDHotKeyItem, t.Start]) 151 | self.HKFIDDict[newHKFID].append([IDHotKeyItem, t.Start]) 152 | 153 | else: 154 | for IDHotKeyItem in hotkeyList: 155 | self.UserHKFUp.append([IDHotKeyItem, fhot]) 156 | self.HKFIDDict[newHKFID].append([IDHotKeyItem, fhot]) 157 | 158 | else: 159 | 160 | if isThread: 161 | t = ExecFunThread(fhot) 162 | for IDHotKeyItem in hotkeyList: 163 | self.UserHKF.append([IDHotKeyItem, t.Start]) 164 | self.HKFIDDict[newHKFID].append([IDHotKeyItem, t.Start]) 165 | 166 | else: 167 | for IDHotKeyItem in hotkeyList: 168 | self.UserHKF.append([IDHotKeyItem, fhot]) 169 | self.HKFIDDict[newHKFID].append([IDHotKeyItem, fhot]) 170 | 171 | return newHKFID 172 | 173 | def removeHotkey(self, hotkey=False, id=False): 174 | """Remove hotkeys and corresponding function""" 175 | HKFID = id 176 | try: 177 | if hotkey: 178 | hotkeyList = self.getHotkeyList(hotkey) 179 | try: 180 | UserHKFTemp = [[hotk, fun] for hotk, fun in self.UserHKF if not (hotk in hotkeyList)] 181 | self.UserHKF = UserHKFTemp[:] 182 | except: 183 | pass 184 | try: 185 | UserHKFTemp = [[hotk, fun] for hotk, fun in self.UserHKFUp if not (hotk in hotkeyList)] 186 | self.UserHKFUp = UserHKFTemp[:] 187 | except: 188 | pass 189 | elif HKFID: 190 | for item in self.HKFIDDict[HKFID]: 191 | try: 192 | self.UserHKF.remove(item) 193 | except: 194 | self.UserHKFUp.remove(item) 195 | self.HKFIDDict.pop(HKFID) 196 | else: 197 | self.UserHKF = [] 198 | self.UserHKFUp = [] 199 | except: 200 | pass 201 | 202 | def setEndHotkey(self, hotkey): 203 | """Add exit hotkeys""" 204 | self.removeHotkey(self.EndHotkey) 205 | self.EndHotkey = hotkey 206 | self.addHotkey(hotkey, self.end) 207 | 208 | # -------------------------------------------------------- 209 | # ID functions for HKFID 210 | def getNewHKFID(self): 211 | try: 212 | return max(self.HKFIDDict.keys()) + 1 213 | except: 214 | return 1 215 | 216 | # -------------------------------------------------------- 217 | 218 | def isHotkey(self, hotkey): 219 | """Check if hotkey is pressed down 220 | Hotkey is given as KeyID""" 221 | 222 | try: 223 | # make sure exact hotkey is pressed 224 | if not (len(hotkey) == len(self.KeyDownID)): 225 | return False 226 | for hotk in hotkey: 227 | if not (hotk in self.KeyDownID): 228 | return False 229 | except: 230 | return False 231 | 232 | return True 233 | 234 | def OnKeyDown(self, event): 235 | 236 | if not "mouse" in event.MessageName: 237 | # check for merged keys first 238 | eventID = event.KeyID 239 | else: 240 | eventID = self.mouseDown_eventMessage2MID[event.Message] 241 | 242 | # make sure key only gets presse once 243 | if not (eventID in self.KeyDownID): 244 | 245 | self.KeyDownID.append(eventID) 246 | 247 | # Add user hotkeys and functions 248 | for hk, fun in self.UserHKF: 249 | if self.isHotkey(hk): 250 | fun() 251 | 252 | return True 253 | 254 | def OnKeyUp(self, event): 255 | 256 | if not "mouse" in event.MessageName: 257 | eventID = event.KeyID 258 | else: 259 | eventID = self.mouseUp_eventMessage2MID[event.Message] 260 | 261 | # check for hotkey up keys 262 | for hk, fun in self.UserHKFUp: 263 | if hk[0] == eventID: 264 | fun() 265 | 266 | try: 267 | self.KeyDownID.remove(eventID) 268 | 269 | except: 270 | pass 271 | return True 272 | 273 | def OnSingleEventMouse(self, event): 274 | """Function to excetue single mouse events""" 275 | 276 | if event.Message in self.singleEventMouseMessage: 277 | # test for mouse wheel: 278 | if event.Message == 522: 279 | if event.Wheel == 1: 280 | eventID = 1004 281 | else: 282 | eventID = 1005 283 | # test mouse move 284 | elif event.Message == 512: 285 | eventID = 1000 286 | else: 287 | return False 288 | 289 | self.KeyDownID.append(eventID) 290 | 291 | # Add user hotkeys and functions 292 | for hk, fun in self.UserHKF: 293 | if self.isHotkey(hk): 294 | fun() 295 | 296 | self.KeyDownID.remove(eventID) 297 | 298 | return True 299 | 300 | # -------------------------------------------------------- 301 | 302 | def createKeyLookup(self): 303 | """Creates Key look up dictionaries, change names as you please""" 304 | ID2Key = {8: 'Back', 305 | 9: 'Tab', 306 | 13: 'Return', 307 | 20: 'Capital', 308 | 27: 'Escape', 309 | 32: 'Space', 310 | 33: 'Prior', 311 | 34: 'Next', 312 | 35: 'End', 313 | 36: 'Home', 314 | 37: 'Left', 315 | 38: 'Up', 316 | 39: 'Right', 317 | 40: 'Down', 318 | 44: 'Snapshot', 319 | 46: 'Delete', 320 | 48: '0', 321 | 49: '1', 322 | 50: '2', 323 | 51: '3', 324 | 52: '4', 325 | 53: '5', 326 | 54: '6', 327 | 55: '7', 328 | 56: '8', 329 | 57: '9', 330 | 65: 'A', 331 | 66: 'B', 332 | 67: 'C', 333 | 68: 'D', 334 | 69: 'E', 335 | 70: 'F', 336 | 71: 'G', 337 | 72: 'H', 338 | 73: 'I', 339 | 74: 'J', 340 | 75: 'K', 341 | 76: 'L', 342 | 77: 'M', 343 | 78: 'N', 344 | 79: 'O', 345 | 80: 'P', 346 | 81: 'Q', 347 | 82: 'R', 348 | 83: 'S', 349 | 84: 'T', 350 | 85: 'U', 351 | 86: 'V', 352 | 87: 'W', 353 | 88: 'X', 354 | 89: 'Y', 355 | 90: 'Z', 356 | 91: 'Lwin', 357 | 96: 'Numpad0', 358 | 97: 'Numpad1', 359 | 98: 'Numpad2', 360 | 99: 'Numpad3', 361 | 100: 'Numpad4', 362 | 101: 'Numpad5', 363 | 102: 'Numpad6', 364 | 103: 'Numpad7', 365 | 104: 'Numpad8', 366 | 105: 'Numpad9', 367 | 106: 'Multiply', 368 | 107: 'Add', 369 | 109: 'Subtract', 370 | 110: 'Decimal', 371 | 111: 'Divide', 372 | 112: 'F1', 373 | 113: 'F2', 374 | 114: 'F3', 375 | 115: 'F4', 376 | 116: 'F5', 377 | 117: 'F6', 378 | 118: 'F7', 379 | 119: 'F8', 380 | 120: 'F9', 381 | 121: 'F10', 382 | 122: 'F11', 383 | 123: 'F12', 384 | 144: 'Numlock', 385 | 160: 'Lshift', 386 | 161: 'Rshift', 387 | 162: 'Lcontrol', 388 | 163: 'Rcontrol', 389 | 164: 'Lmenu', 390 | 165: 'Rmenu', 391 | 186: 'Oem_1', 392 | 187: 'Oem_Plus', 393 | 188: 'Oem_Comma', 394 | 189: 'Oem_Minus', 395 | 190: 'Oem_Period', 396 | 191: 'Oem_2', 397 | 192: 'Oem_3', 398 | 219: 'Oem_4', 399 | 220: 'Oem_5', 400 | 221: 'Oem_6', 401 | 222: 'Oem_7', 402 | 1010: 'Ctrl', # merged hotkeys 403 | 1011: 'Alt', 404 | 1012: 'Shift'} 405 | 406 | Key2ID = dict(map(lambda x, y: (x, y), ID2Key.values(), ID2Key.keys())) 407 | 408 | return ID2Key, Key2ID 409 | 410 | def createMouseLookup(self): 411 | """Takes a event.Message from mouse and converts it to artificial KeyID""" 412 | mouseDown_MID2eventMessage = { 413 | 1001: 513, 414 | 1002: 516, 415 | 1003: 519} 416 | mouseDown_eventMessage2MID = dict( 417 | map(lambda x, y: (x, y), mouseDown_MID2eventMessage.values(), mouseDown_MID2eventMessage.keys())) 418 | 419 | mouseUp_MID2eventMessage = { 420 | 1001: 514, 421 | 1002: 517, 422 | 1003: 520} 423 | mouseUp_eventMessage2MID = dict( 424 | map(lambda x, y: (x, y), mouseUp_MID2eventMessage.values(), mouseUp_MID2eventMessage.keys())) 425 | 426 | return mouseDown_MID2eventMessage, mouseDown_eventMessage2MID, mouseUp_MID2eventMessage, mouseUp_eventMessage2MID 427 | 428 | def createSingleEventMouse(self): 429 | """Store events that get executed on single event like wheel up 430 | MID event.Message pyhk hotkey comments 431 | 1000 512 mouse move 432 | 1004 522 mouse wheel up event.Wheel = 1 433 | 1005 522 mouse wheel up event.Wheel = 1""" 434 | 435 | singleEventMouseMessage = [512, 522] 436 | singleEventMID = [1000, 1004, 1005] 437 | 438 | return singleEventMouseMessage, singleEventMID 439 | 440 | def createMergeKeys(self): 441 | """Merge two keys into one 442 | KeyID MEID MergeHumanHotkey 443 | 162 1010 Ctrl (Lcontrol) 444 | 163 1010 Ctrl (Rcontrol 445 | 164 1011 Alt (Lmenu) 446 | 165 1011 Alt (Rmenu) 447 | 160 1012 Shift (Lshift) 448 | 161 1012 Shift (Rshift)""" 449 | 450 | KeyID2MEID = {162: 1010, 451 | 163: 1010, 452 | 164: 1011, 453 | 165: 1011, 454 | 160: 1012, 455 | 161: 1012} 456 | return KeyID2MEID 457 | 458 | def getHotkeyListNoSingleNoModifiers(self): 459 | """return a list of all hotkeys without single events and modifiers""" 460 | TempID2Key = self.ID2Key.copy() 461 | 462 | # get rid of single events and modifiers 463 | getRid = [160, 161, 162, 163, 164, 165, 1000, 1004, 1005, 1010, 1011, 1012] 464 | 465 | # get rid of Lwin and oems 466 | moreRid = [91, 186, 187, 188, 189, 190, 191, 192, 219, 220, 221, 222] 467 | 468 | for item in moreRid: 469 | getRid.append(item) 470 | 471 | for gR in getRid: 472 | TempID2Key.pop(gR) 473 | 474 | LTempID2Key = TempID2Key.values() 475 | 476 | return LTempID2Key 477 | 478 | 479 | # permutation functions needed for merged hotkeys 480 | def UniquePermutation2(l1, l2): 481 | """"Return UP of two lists""" 482 | ltemp = [] 483 | for x1 in l1: 484 | for x2 in l2: 485 | ltemp.append([x1, x2]) 486 | 487 | return ltemp 488 | 489 | 490 | def UniquePermutation(li): 491 | """Return UP of a general list""" 492 | lcurrent = li[0] 493 | depth = 0 494 | for xl in li[1:]: 495 | lcurrenttemp = list() 496 | lcurrenttemp = UniquePermutation2(lcurrent, xl) 497 | 498 | if depth > 0: 499 | lcurrent = list() 500 | for item in lcurrenttemp: 501 | item0 = list(item[0]) 502 | item0.append(item[1]) 503 | lcurrent.append(item0) 504 | else: 505 | lcurrent = lcurrenttemp[:] 506 | depth += 1 507 | return lcurrent 508 | 509 | 510 | # class for thread 511 | class ExecFunThread: 512 | def __init__(self, fun): 513 | self.fun = fun 514 | 515 | def Start(self): 516 | self.running = True 517 | start_new_thread(self.Run, ()) 518 | 519 | def IsRunning(self): 520 | return self.running 521 | 522 | def Run(self): 523 | self.fun() 524 | self.running = False 525 | 526 | 527 | def test1(): 528 | print("按下了F2") 529 | 530 | 531 | def test2(): 532 | print("按下了F3") 533 | # hot.removeHotkey(id=id3) 534 | 535 | 536 | def test3(): 537 | print("'Ctrl', 'Shift', 's'") 538 | 539 | 540 | def test(): 541 | hot = pyhk() 542 | id1 = hot.addHotkey(["F2"], test1) 543 | id3 = hot.addHotkey(["F3"], test2) 544 | id3 = hot.addHotkey(['Ctrl', 'Shift', 'S'], test3) 545 | hot.start() 546 | 547 | 548 | if pyhook_flag: 549 | pyhk = pyhook 550 | else: 551 | pyhk = None 552 | 553 | if __name__ == '__main__': 554 | from threading import Thread 555 | import time 556 | 557 | if pyhk is not None: 558 | thread_hotKey = Thread(target=test) 559 | thread_hotKey.setDaemon(True) 560 | thread_hotKey.start() 561 | while True: 562 | print(time.time()) 563 | time.sleep(2) 564 | -------------------------------------------------------------------------------- /src/init.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from qtawesome import icon as qticon 4 | from json import load, dump 5 | from traceback import format_exc 6 | from pyperclip import copy 7 | from threading import Thread 8 | from cv2 import imread 9 | from os.path import dirname 10 | 11 | from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer 12 | from PyQt5.QtCore import QObject, pyqtSignal, QSize, QRect, QPoint, Qt, QUrl 13 | from PyQt5.QtGui import QIcon, QPixmap, QFont, QTextCharFormat, QPen, QColor, QCursor, QMouseEvent 14 | from PyQt5.QtWidgets import QSystemTrayIcon, QLabel, QTextBrowser, QPushButton, QStatusBar, QFileDialog, QApplication, \ 15 | QMainWindow 16 | 17 | from src.translate import TranslateThread 18 | from src.switch import SwitchBtn 19 | from src.screen_rate import get_screen_rate 20 | from src.play_voice import Voice 21 | from src.api import write_error 22 | from src.vis_result import VisResult 23 | from configs import Config, folder_path 24 | 25 | config = Config() 26 | 27 | 28 | class UseTranslateThread(QObject): 29 | use_translate_signal = pyqtSignal(str, dict, str) 30 | 31 | def __init__(self, fun, original, data, translate_type): 32 | 33 | self.fun = fun # 要执行的翻译函数 34 | self.original = original # 识别到的原文 35 | self.data = data # 配置信息 36 | self.translate_type = translate_type # 翻译源 37 | super(UseTranslateThread, self).__init__() 38 | 39 | def run(self): 40 | 41 | if self.translate_type == "caiyunPrivate": 42 | result = self.fun(self.original, self.data) 43 | elif self.translate_type == "original" or self.translate_type == "translated": 44 | result = self.original 45 | else: 46 | result = self.fun(self.original) 47 | 48 | self.use_translate_signal.emit(result, self.data, self.translate_type) 49 | 50 | 51 | class MainInterface(QMainWindow): 52 | 53 | def __init__(self, screen_scale_rate, user): 54 | 55 | super(MainInterface, self).__init__() 56 | 57 | self.rate = screen_scale_rate # 屏幕缩放比例 58 | self.lock_sign = 0 59 | self.user = user 60 | self.get_settin() 61 | self.init_ui() 62 | 63 | self._padding = 5 # 设置边界宽度为5 64 | # 设置鼠标跟踪判断扳机默认值 65 | # reference: https://blog.csdn.net/qq_38528972/article/details/78573591 66 | self._move_drag = False 67 | self._corner_drag = False 68 | self._bottom_drag = False 69 | self._right_drag = False 70 | 71 | self._right_rect = [] 72 | self._bottom_rect = [] 73 | self._corner_rect = [] 74 | self.image = None 75 | self.load_local_img = False 76 | self.load_local_img_path = folder_path 77 | self.save_result_path = [folder_path] # 写成list, 传入方式为引用, 便于修改 78 | 79 | def init_ui(self): 80 | 81 | # 窗口尺寸 82 | self.resize(800 * self.rate, 120 * self.rate) 83 | self.setMouseTracking(True) # 设置widget鼠标跟踪 84 | 85 | # 窗口无标题栏、窗口置顶、窗口透明 86 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) 87 | # Mac系统下隐藏标题栏同时窗口透明会造成窗口不能伸缩 88 | self.setAttribute(Qt.WA_TranslucentBackground) 89 | # self.setWindowOpacity(0.7) 90 | 91 | # 窗口图标 92 | self.icon = QIcon() 93 | self.icon.addPixmap(QPixmap(folder_path + "/config/logo.ico"), QIcon.Normal, QIcon.On) 94 | self.setWindowIcon(self.icon) 95 | 96 | # 系统托盘 97 | self.tray = QSystemTrayIcon(self) 98 | self.tray.setIcon(self.icon) 99 | self.tray.activated.connect(self.show) 100 | self.tray.show() 101 | 102 | # 鼠标样式 103 | # self.pixmap = QPixmap(folder_path + '/config/光标.png') 104 | # self.cursor = QCursor(self.pixmap, 0, 0) 105 | # self.setCursor(self.cursor) 106 | 107 | # 工具栏标签 108 | self.titleLabel = QLabel(self) 109 | self.titleLabel.setGeometry(0, 0, 800 * self.rate, 30 * self.rate) 110 | self.titleLabel.setStyleSheet("background-color:rgba(62, 62, 62, 0.01)") 111 | 112 | self.Font = QFont() 113 | self.Font.setFamily("华康方圆体W7") 114 | self.Font.setPointSize(15) 115 | 116 | # 翻译框 117 | self.translateText = QTextBrowser(self) 118 | self.translateText.setGeometry(0, 30 * self.rate, 1500 * self.rate, 90 * self.rate) 119 | self.translateText.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 120 | self.translateText.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 121 | self.translateText.setStyleSheet("border-width:0;\ 122 | border-style:outset;\ 123 | border-top:0px solid #e8f3f9;\ 124 | color:white;\ 125 | font-weight: bold;\ 126 | background-color:rgba(62, 62, 62, %s)" 127 | % (self.horizontal)) 128 | self.translateText.setFont(self.Font) 129 | 130 | # 翻译框加入描边文字 131 | self.format = QTextCharFormat() 132 | self.format.setTextOutline(QPen(QColor('#1E90FF'), 0.7, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) 133 | self.translateText.mergeCurrentCharFormat(self.format) 134 | self.translateText.append("欢迎~ 么么哒~") 135 | self.format.setTextOutline(QPen(QColor('#FF69B4'), 0.7, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) 136 | self.translateText.mergeCurrentCharFormat(self.format) 137 | self.translateText.append("点击设置修改待识别语言类型") 138 | self.format.setTextOutline(QPen(QColor('#674ea7'), 0.7, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) 139 | self.translateText.mergeCurrentCharFormat(self.format) 140 | self.translateText.append("点击截屏按钮选择识图区域") 141 | 142 | # 翻译框根据内容自适应大小 143 | self.document = self.translateText.document() 144 | self.document.contentsChanged.connect(self.textAreaChanged) 145 | 146 | # 此Label用于当鼠标进入界面时给出颜色反应 147 | self.dragLabel = QLabel(self) 148 | self.dragLabel.setObjectName("dragLabel") 149 | self.dragLabel.setGeometry(0, 0, 4000 * self.rate, 2000 * self.rate) 150 | 151 | # 截屏范围按钮 152 | self.RangeButton = QPushButton(qticon('fa.crop', color='white'), "", self) 153 | self.RangeButton.setIconSize(QSize(20, 20)) 154 | self.RangeButton.setGeometry(QRect(193 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 155 | self.RangeButton.setToolTip('截屏识别图片 ScreenShot Range
框选要识别的区域') 156 | self.RangeButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);") 157 | self.RangeButton.setCursor(QCursor(Qt.PointingHandCursor)) 158 | self.RangeButton.hide() 159 | 160 | # 运行按钮 161 | self.StartButton = QPushButton(qticon('fa.play', color='white'), "", self) 162 | self.StartButton.setIconSize(QSize(20, 20)) 163 | self.StartButton.setGeometry(QRect(233 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 164 | self.StartButton.setToolTip('识别 Recognize
点击开始(手动)
开始/停止(自动)') 165 | self.StartButton.setStyleSheet("background: transparent") 166 | self.StartButton.clicked.connect(self.start_login) 167 | self.StartButton.setCursor(QCursor(Qt.PointingHandCursor)) 168 | self.StartButton.hide() 169 | 170 | # 手动打开文件按钮 171 | self.OpenButton = QPushButton(qticon('fa.folder-open-o', color='white'), "", self) 172 | self.OpenButton.setIconSize(QSize(20, 20)) 173 | self.OpenButton.setGeometry(QRect(273 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 174 | self.OpenButton.setToolTip('打开识别图片 Open image
识别本地图片') 175 | self.OpenButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);") 176 | self.OpenButton.setCursor(QCursor(Qt.PointingHandCursor)) 177 | self.OpenButton.clicked.connect(self.open_image) 178 | self.OpenButton.hide() 179 | 180 | # 复制按钮 181 | self.CopyButton = QPushButton(qticon('fa.copy', color='white'), "", self) 182 | self.CopyButton.setIconSize(QSize(20, 20)) 183 | self.CopyButton.setGeometry(QRect(313 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 184 | self.CopyButton.setToolTip('复制 Copy
将当前识别到的文本
复制至剪贴板') 185 | self.CopyButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);") 186 | self.CopyButton.setCursor(QCursor(Qt.PointingHandCursor)) 187 | self.CopyButton.clicked.connect(lambda: copy(self.original)) 188 | self.CopyButton.hide() 189 | 190 | # 朗读原文按钮 191 | self.playVoiceButton = QPushButton(qticon('fa.music', color='white'), "", self) 192 | self.playVoiceButton.setIconSize(QSize(20, 20)) 193 | self.playVoiceButton.setGeometry(QRect(353 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 194 | self.playVoiceButton.setToolTip('朗读原文 Play Voice
朗读识别到的原文') 195 | self.playVoiceButton.setStyleSheet("background: transparent") 196 | self.playVoiceButton.clicked.connect(self.play_voice) 197 | self.playVoiceButton.setCursor(QCursor(Qt.PointingHandCursor)) 198 | self.playVoiceButton.hide() 199 | 200 | # 翻译模式按钮 201 | self.switchBtn = SwitchBtn(self) 202 | self.switchBtn.setGeometry(393 * self.rate, 5 * self.rate, 50 * self.rate, 20 * self.rate) 203 | self.switchBtn.setToolTip('模式 Mode
手动识别/自动识别') 204 | self.switchBtn.checkedChanged.connect(self.getState) 205 | self.switchBtn.setCursor(QCursor(Qt.PointingHandCursor)) 206 | self.switchBtn.hide() 207 | 208 | # 识别原文字类型提示框 209 | languageFont = QFont() 210 | languageFont.setFamily("华康方圆体W7") 211 | languageFont.setPointSize(10) 212 | self.languageText = QPushButton(self) 213 | self.languageText.setIconSize(QSize(20, 20)) 214 | self.languageText.setGeometry(QRect(463 * self.rate, 5 * self.rate, 45 * self.rate, 20 * self.rate)) 215 | self.languageText.setToolTip('待识别的原文类型
Original Language Type') 216 | self.languageText.setStyleSheet("border-width:0;\ 217 | border-style:outset;\ 218 | border-top:0px solid #e8f3f9;\ 219 | color:white;\ 220 | background-color:rgba(143, 143, 143, 0)") 221 | self.languageText.setCursor(QCursor(Qt.PointingHandCursor)) 222 | self.languageText.setText(config.letter_chinese_dict[self.data["language"]]) 223 | self.languageText.setFont(languageFont) 224 | self.languageText.hide() 225 | 226 | # 设置按钮 227 | self.SettinButton = QPushButton(qticon('fa.cog', color='white'), "", self) 228 | self.SettinButton.setIconSize(QSize(20, 20)) 229 | self.SettinButton.setGeometry(QRect(518 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 230 | self.SettinButton.setToolTip('设置 Settin') 231 | self.SettinButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);") 232 | self.SettinButton.setCursor(QCursor(Qt.PointingHandCursor)) 233 | self.SettinButton.hide() 234 | 235 | # 锁按钮 236 | self.LockButton = QPushButton(qticon('fa.lock', color='white'), "", self) 237 | self.LockButton.setIconSize(QSize(20, 20)) 238 | self.LockButton.setGeometry(QRect(562 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 239 | self.LockButton.setToolTip('锁定翻译界面 Lock') 240 | self.LockButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);") 241 | self.LockButton.setCursor(QCursor(Qt.PointingHandCursor)) 242 | self.LockButton.clicked.connect(self.lock) 243 | self.LockButton.hide() 244 | 245 | # 最小化按钮 246 | self.MinimizeButton = QPushButton(qticon('fa.minus', color='white'), "", self) 247 | self.MinimizeButton.setIconSize(QSize(20, 20)) 248 | self.MinimizeButton.setGeometry(QRect(602 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 249 | self.MinimizeButton.setToolTip('最小化 Minimize') 250 | self.MinimizeButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);") 251 | self.MinimizeButton.setCursor(QCursor(Qt.PointingHandCursor)) 252 | self.MinimizeButton.clicked.connect(self.showMinimized) 253 | self.MinimizeButton.hide() 254 | 255 | # 退出按钮 256 | self.QuitButton = QPushButton(qticon('fa.times', color='white'), "", self) 257 | self.QuitButton.setIconSize(QSize(20, 20)) 258 | self.QuitButton.setGeometry(QRect(642 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 259 | self.QuitButton.setToolTip('退出程序 Quit') 260 | self.QuitButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);") 261 | self.QuitButton.setCursor(QCursor(Qt.PointingHandCursor)) 262 | self.QuitButton.hide() 263 | 264 | # 右下角用于拉伸界面的控件 mac系统应该注释掉 265 | self.statusbar = QStatusBar(self) 266 | self.statusbar.setStyleSheet("background-color:rgba(62, 62, 62, 0);") 267 | self.setStatusBar(self.statusbar) 268 | 269 | # 锁定界面 270 | def lock(self): 271 | 272 | try: 273 | if self.lock_sign == 0: 274 | self.LockButton.setIcon(qticon('fa.unlock', color='white')) 275 | self.dragLabel.hide() 276 | self.lock_sign = 1 277 | 278 | if self.horizontal == 0.01: 279 | self.horizontal = 0 280 | else: 281 | self.LockButton.setIcon(qticon('fa.lock', color='white')) 282 | self.LockButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);") 283 | self.dragLabel.show() 284 | self.lock_sign = 0 285 | 286 | if self.horizontal == 0: 287 | self.horizontal = 0.01 288 | 289 | self.translateText.setStyleSheet("border-width:0;\ 290 | border-style:outset;\ 291 | border-top:0px solid #e8f3f9;\ 292 | color:white;\ 293 | font-weight: bold;\ 294 | background-color:rgba(62, 62, 62, %s)" 295 | % (self.horizontal)) 296 | except Exception: 297 | write_error(format_exc()) 298 | 299 | # 当翻译内容改变时界面自适应窗口大小 300 | def textAreaChanged(self): 301 | 302 | newHeight = self.document.size().height() 303 | width = self.width() 304 | self.resize(width, newHeight + 30 * self.rate) 305 | self.translateText.setGeometry(0, 30 * self.rate, width, newHeight) 306 | 307 | # 判断翻译模式键状态 308 | def getState(self, checked): 309 | 310 | if checked: 311 | self.mode = True 312 | else: 313 | self.mode = False 314 | 315 | with open(folder_path + '/config/settin.json') as file: 316 | data = load(file) 317 | data["sign"] = 1 318 | with open(folder_path + '/config/settin.json', 'w') as file: 319 | dump(data, file, indent=2) 320 | self.StartButton.setIcon(qticon('fa.play', color='white')) 321 | 322 | # 鼠标移动事件 323 | def mouseMoveEvent(self, e: QMouseEvent): 324 | 325 | if self.lock_sign == 1: 326 | return 327 | 328 | try: 329 | self._endPos = e.pos() - self._startPos 330 | self.move(self.pos() + self._endPos) 331 | except Exception: 332 | write_error(format_exc()) 333 | 334 | # 鼠标移动事件 mac 335 | # def mouseMoveEvent(self, QMouseEvent): 336 | # if self.lock_sign == 1: 337 | # return 338 | # 339 | # # 判断鼠标位置切换鼠标手势 340 | # if QMouseEvent.pos() in self._corner_rect: 341 | # self.setCursor(Qt.SizeFDiagCursor) 342 | # elif QMouseEvent.pos() in self._bottom_rect: 343 | # self.setCursor(Qt.SizeVerCursor) 344 | # elif QMouseEvent.pos() in self._right_rect: 345 | # self.setCursor(Qt.SizeHorCursor) 346 | # else: 347 | # self.setCursor(self.cursor) 348 | # # self.setCursor(Qt.ArrowCursor) 349 | # 350 | # # 当鼠标左键点击不放及满足点击区域的要求后,分别实现不同的窗口调整 351 | # if Qt.LeftButton and self._right_drag: 352 | # # 右侧调整窗口宽度 353 | # self.resize(QMouseEvent.pos().x(), self.height()) 354 | # QMouseEvent.accept() 355 | # elif Qt.LeftButton and self._bottom_drag: 356 | # # 下侧调整窗口高度 357 | # self.resize(self.width(), QMouseEvent.pos().y()) 358 | # QMouseEvent.accept() 359 | # elif Qt.LeftButton and self._corner_drag: 360 | # # 右下角同时调整高度和宽度 361 | # self.resize(QMouseEvent.pos().x(), QMouseEvent.pos().y()) 362 | # QMouseEvent.accept() 363 | # elif Qt.LeftButton and self._move_drag: 364 | # # 标题栏拖放窗口位置 365 | # self.move(QMouseEvent.globalPos() - self.move_drag_position) 366 | # QMouseEvent.accept() 367 | 368 | # # 鼠标按下事件 369 | def mousePressEvent(self, e: QMouseEvent): 370 | 371 | if self.lock_sign == 1: 372 | return 373 | 374 | try: 375 | if e.button() == Qt.LeftButton: 376 | self._isTracking = True 377 | self._startPos = QPoint(e.x(), e.y()) 378 | except Exception: 379 | write_error(format_exc()) 380 | 381 | # 鼠标按下事件 mac 382 | # def mousePressEvent(self, event): 383 | # 384 | # if self.lock_sign == 1: 385 | # return 386 | # 387 | # try: 388 | # # 重写鼠标点击的事件 389 | # if (event.button() == Qt.LeftButton) and (event.pos() in self._corner_rect): 390 | # # 鼠标左键点击右下角边界区域 391 | # self._corner_drag = True 392 | # event.accept() 393 | # elif (event.button() == Qt.LeftButton) and (event.pos() in self._right_rect): 394 | # # 鼠标左键点击右侧边界区域 395 | # self._right_drag = True 396 | # event.accept() 397 | # elif (event.button() == Qt.LeftButton) and (event.pos() in self._bottom_rect): 398 | # # 鼠标左键点击下侧边界区域 399 | # self._bottom_drag = True 400 | # event.accept() 401 | # elif (event.button() == Qt.LeftButton) and (event.y() < self.height()): 402 | # # 鼠标左键点击区域 403 | # self._move_drag = True 404 | # self.move_drag_position = event.globalPos() - self.pos() 405 | # event.accept() 406 | # except Exception: 407 | # write_error(format_exc()) 408 | 409 | # # 鼠标松开事件 410 | def mouseReleaseEvent(self, e: QMouseEvent): 411 | 412 | if self.lock_sign == 1: 413 | return 414 | 415 | try: 416 | if e.button() == Qt.LeftButton: 417 | self._isTracking = False 418 | self._startPos = None 419 | self._endPos = None 420 | except Exception: 421 | write_error(format_exc()) 422 | 423 | # 鼠标松开事件 mac 424 | # def mouseReleaseEvent(self, e: QMouseEvent): 425 | # if self.lock_sign == 1: 426 | # return 427 | # 428 | # try: 429 | # if e.button() == Qt.LeftButton: 430 | # self._isTracking = False 431 | # self._startPos = None 432 | # self._endPos = None 433 | # 434 | # # 鼠标释放后,各扳机复位 435 | # self._move_drag = False 436 | # self._corner_drag = False 437 | # self._bottom_drag = False 438 | # self._right_drag = False 439 | # except Exception: 440 | # write_error(format_exc()) 441 | 442 | # 鼠标进入控件事件 443 | def enterEvent(self, QEvent): 444 | 445 | if self.lock_sign == 1: 446 | self.LockButton.show() 447 | self.LockButton.setStyleSheet("background-color:rgba(62, 62, 62, 0.7);") 448 | return 449 | 450 | try: 451 | # 显示所有顶部工具栏控件 452 | self.switchBtn.show() 453 | self.StartButton.show() 454 | self.SettinButton.show() 455 | self.RangeButton.show() 456 | self.OpenButton.show() 457 | self.CopyButton.show() 458 | self.QuitButton.show() 459 | self.MinimizeButton.show() 460 | self.playVoiceButton.show() 461 | self.LockButton.show() 462 | self.languageText.show() 463 | 464 | self.setStyleSheet('QLabel#dragLabel {background-color:rgba(62, 62, 62, 0.3)}') 465 | 466 | except Exception: 467 | write_error(format_exc()) 468 | 469 | def resizeEvent(self, QResizeEvent): 470 | # 重新调整边界范围以备实现鼠标拖放缩放窗口大小,采用三个列表生成式生成三个列表 471 | self._right_rect = [QPoint(x, y) for x in range(self.width() - self._padding, self.width() + 1) 472 | for y in range(1, self.height() - self._padding)] 473 | self._bottom_rect = [QPoint(x, y) for x in range(1, self.width() - self._padding) 474 | for y in range(self.height() - self._padding, self.height() + 1)] 475 | self._corner_rect = [QPoint(x, y) for x in range(self.width() - self._padding, self.width() + 1) 476 | for y in range(self.height() - self._padding, self.height() + 1)] 477 | 478 | # 鼠标离开控件事件 479 | def leaveEvent(self, QEvent): 480 | 481 | try: 482 | # 重置所有控件的位置和大小 483 | width = (self.width() * 213) / 800 484 | height = self.height() - 30 485 | 486 | self.RangeButton.setGeometry(QRect(width - 20 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 487 | self.StartButton.setGeometry(QRect(width + 20 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 488 | self.OpenButton.setGeometry(QRect(width + 60 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 489 | self.CopyButton.setGeometry(QRect(width + 100 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 490 | self.playVoiceButton.setGeometry(QRect(width + 140 * self.rate, 5 * self.rate, 20 * self.rate, 491 | 20 * self.rate)) 492 | self.switchBtn.setGeometry(QRect(width + 180 * self.rate, 5 * self.rate, 50 * self.rate, 20 * self.rate)) 493 | self.languageText.setGeometry(width + 250 * self.rate, 5 * self.rate, 45 * self.rate, 20 * self.rate) 494 | self.SettinButton.setGeometry(QRect(width + 305 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 495 | self.LockButton.setGeometry(QRect(width + 345 * self.rate, 5 * self.rate, 24 * self.rate, 20 * self.rate)) 496 | self.MinimizeButton.setGeometry( 497 | QRect(width + 389 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 498 | self.QuitButton.setGeometry(QRect(width + 429 * self.rate, 5 * self.rate, 20 * self.rate, 20 * self.rate)) 499 | self.translateText.setGeometry(0, 30 * self.rate, self.width(), height * self.rate) 500 | 501 | # 隐藏所有顶部工具栏控件 502 | self.switchBtn.hide() 503 | self.StartButton.hide() 504 | self.SettinButton.hide() 505 | self.RangeButton.hide() 506 | self.OpenButton.hide() 507 | self.CopyButton.hide() 508 | self.QuitButton.hide() 509 | self.MinimizeButton.hide() 510 | self.playVoiceButton.hide() 511 | self.LockButton.hide() 512 | self.languageText.hide() 513 | 514 | self.setStyleSheet('QLabel#dragLabel {background-color:none}') 515 | 516 | except Exception: 517 | write_error(format_exc()) 518 | 519 | # 获取界面预设参数 520 | def get_settin(self): 521 | 522 | with open(folder_path + '/config/settin.json') as file: 523 | self.data = load(file) 524 | 525 | # 翻译模式预设 526 | self.mode = False 527 | # 原文预设值 528 | self.original = '' 529 | 530 | # 透明度预设 531 | self.horizontal = (self.data["horizontal"]) / 100 532 | if self.horizontal == 0: 533 | self.horizontal = 0.01 534 | 535 | # 各翻译源线程状态标志 536 | self.thread_state = 0 537 | 538 | def start_login(self): 539 | 540 | with open(folder_path + '/config/settin.json') as file: 541 | data = load(file) 542 | 543 | if data["sign"] % 2 == 0: 544 | data["sign"] += 1 545 | with open(folder_path + '/config/settin.json', 'w') as file: 546 | dump(data, file, indent=2) 547 | 548 | self.StartButton.setIcon(qticon('fa.play', color='white')) 549 | else: 550 | thread = TranslateThread(self, self.mode) 551 | thread.use_translate_signal.connect(self.use_translate) 552 | thread.start() 553 | thread.exec() 554 | 555 | # 创造翻译线程 556 | def creat_thread(self, fun, original, data, translate_type): 557 | 558 | self.thread_state += 1 # 线程开始,增加线程数 559 | translation_source = UseTranslateThread(fun, original, data, translate_type) 560 | thread = Thread(target=translation_source.run) 561 | thread.setDaemon(True) 562 | translation_source.use_translate_signal.connect(self.display_text) 563 | thread.start() 564 | 565 | # 并发执行所有翻译源 566 | def use_translate(self, signal_list, original, data, result_with_location, translate_result): 567 | 568 | # 翻译界面清屏 569 | self.translateText.clear() 570 | # 设定翻译时的字体类型和大小 571 | self.Font.setFamily(data["fontType"]) 572 | self.Font.setPointSize(data["fontSize"]) 573 | self.translateText.setFont(self.Font) 574 | 575 | if "original" in signal_list or "error" in signal_list: 576 | if data["vis_result"] == "True": 577 | self.vis_res = VisResult(np_img=self.image, result=result_with_location, configs=data, 578 | translate_result=translate_result, save_path=self.save_result_path) 579 | self.vis_res.result_signal.connect(self.display_modify_text) 580 | self.vis_res.show() 581 | if translate_result == '': 582 | self.creat_thread(None, original, data, "original") 583 | else: 584 | if data['showOriginal'] == "True": 585 | self.creat_thread(None, original, data, "original") 586 | self.creat_thread(None, translate_result, data, "translated") 587 | 588 | # 将翻译结果打印 589 | def display_text(self, result, data, translate_type): 590 | 591 | try: 592 | if data["showColorType"] == "False": 593 | self.format.setTextOutline( 594 | QPen(QColor(data["fontColor"][translate_type]), 0.7, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) 595 | self.translateText.mergeCurrentCharFormat(self.format) 596 | self.translateText.append(result) 597 | else: 598 | self.translateText.append("%s" % (data["fontColor"][translate_type], result)) 599 | 600 | # 保存译文 601 | self.save_text(result, translate_type) 602 | self.thread_state -= 1 # 线程结束,减少线程数 603 | except Exception: 604 | write_error(format_exc()) 605 | 606 | # 将修改后的结果打印 607 | def display_modify_text(self, result, data, translate_type, translate_result): 608 | self.translateText.clear() 609 | 610 | if data["showColorType"] == "False": 611 | if translate_result == '': 612 | self.format.setTextOutline( 613 | QPen(QColor(data["fontColor"][translate_type]), 0.7, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) 614 | self.translateText.mergeCurrentCharFormat(self.format) 615 | self.translateText.append(result) 616 | else: 617 | if data["showOriginal"] == "True": 618 | self.format.setTextOutline( 619 | QPen(QColor(data["fontColor"][translate_type]), 0.7, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) 620 | self.translateText.mergeCurrentCharFormat(self.format) 621 | self.translateText.append(result) 622 | 623 | self.format.setTextOutline( 624 | QPen(QColor(data["fontColor"]['translated']), 0.7, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) 625 | self.translateText.mergeCurrentCharFormat(self.format) 626 | self.translateText.append(translate_result) 627 | else: 628 | if translate_result == '': 629 | self.translateText.append("%s" % (data["fontColor"][translate_type], result)) 630 | else: 631 | if data["showOriginal"] == "True": 632 | self.translateText.append("%s" % (data["fontColor"][translate_type], result)) 633 | self.translateText.append("%s" % (data["fontColor"]['translated'], translate_result)) 634 | self.original = result 635 | 636 | # 语音朗读 637 | def play_voice(self): 638 | if not self.original: 639 | return 640 | try: 641 | # thread = Thread(target=Voice, args=(self.original,)) 642 | # thread.setDaemon(True) 643 | # thread.start() 644 | 645 | self.player = None 646 | flag, voice_file = Voice(self.original).save_voice() 647 | if flag: 648 | file = QUrl.fromLocalFile(voice_file) 649 | content = QMediaContent(file) 650 | self.player = QMediaPlayer() 651 | self.player.setMedia(content) 652 | self.player.play() 653 | 654 | except Exception: 655 | write_error(format_exc()) 656 | 657 | def save_text(self, text, translate_type): 658 | 659 | if translate_type == "caiyunPrivate": 660 | content = "\n[私人翻译]\n%s" % text 661 | else: 662 | content = "" 663 | 664 | with open(folder_path + "/config/识别结果.txt", "a+", encoding="utf-8") as file: 665 | file.write(content) 666 | 667 | def open_image(self): 668 | file_choose, file_type = QFileDialog.getOpenFileName(self, "选取文件", self.load_local_img_path, 669 | "jpg (*.jpg);; png (*.png);; jpeg (*.jpeg);;All Files (*)") 670 | img = imread(file_choose) 671 | if img is not None: 672 | self.load_local_img_path = dirname(file_choose) 673 | self.image = img 674 | self.load_local_img = True 675 | self.start_login() 676 | 677 | 678 | if __name__ == '__main__': 679 | import sys 680 | 681 | screen_scale_rate = get_screen_rate() 682 | App = QApplication(sys.argv) 683 | Init = MainInterface(screen_scale_rate, "ming") 684 | Init.QuitButton.clicked.connect(Init.close) 685 | Init.show() 686 | App.exit(App.exec_()) 687 | -------------------------------------------------------------------------------- /src/play_voice.py: -------------------------------------------------------------------------------- 1 | from urllib import parse 2 | from requests import Session 3 | # from playsound import playsound 4 | from traceback import format_exc 5 | from json import load 6 | from js2py import EvalJs 7 | import os 8 | 9 | from src.api import write_error 10 | from configs import folder_path, Config 11 | 12 | config = Config() 13 | 14 | 15 | class Voice(object): 16 | 17 | def __init__(self, text, language=None): 18 | 19 | self.text = text 20 | self.session = Session() 21 | self.session.keep_alive = False 22 | 23 | self.headers = { 24 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36", 25 | "Referer": "https://translate.google.cn/" 26 | } 27 | 28 | def getTk(self): 29 | 30 | with open(folder_path + '/config/GoogleJS.js', encoding='utf8') as f: 31 | js_data = f.read() 32 | 33 | context = EvalJs() 34 | context.execute(js_data) 35 | tk = context.TL(self.text) 36 | 37 | return tk 38 | 39 | def save_voice(self, language=None): 40 | 41 | try: 42 | if language is None: 43 | with open(folder_path + '/config/settin.json') as file: 44 | data = load(file) 45 | language = data["language"] 46 | language = config.voice_language[language] 47 | 48 | content = parse.quote(self.text) 49 | tk = self.getTk() 50 | url = "https://translate.google.cn/translate_tts?ie=UTF-8&q=" + content + "&tl=" + language + "&total=1&idx=0&textlen=107&tk=" + tk + "&client=webapp&prev=input" 51 | res = self.session.get(url, headers=self.headers) 52 | 53 | save_path = folder_path + '/config/voice.mp3' 54 | 55 | if os.path.isfile(save_path): 56 | os.remove(save_path) 57 | 58 | with open(save_path, 'wb') as file: 59 | file.write(res.content) 60 | 61 | # 没有gi解决办法(https://blog.csdn.net/xxxlinttp/article/details/78056467): 62 | # 复制一份/home/ming/miniconda3/envs/python36/lib/python3.6/site-packages/gi到自己的虚拟环境 63 | # playsound(folder_path + '/config/voice.mp3') 64 | return True, save_path 65 | 66 | except Exception: 67 | write_error(format_exc()) 68 | return False, '' 69 | 70 | 71 | if __name__ == '__main__': 72 | # ja en ko zh-CN fr de 73 | # Voice("다음은 서울에 있는 동네 이름에 얽힌 이야기이다.", 'ko') 74 | # Voice("It's snowing! It's time to make a snowman.James runs out. He makes a big pile of snow. He puts a big snowball on top.", 'en') 75 | # Voice('そうすると、可笑しいことや変なこと、滑稽なことや正しくないこと', 'ja') 76 | # flag, path = Voice("钢琴家傅聪确诊新冠系傅雷之子, 啊啊啊啦啦啦").save_voice('zh-CN') 77 | # flag, path = Voice("Je t'aime bien.").save_voice('fr') 78 | flag, path = Voice("Ich mag dich").save_voice('de') 79 | print(flag, path) 80 | -------------------------------------------------------------------------------- /src/range.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from re import findall 4 | import sys 5 | from json import load, dump 6 | from traceback import format_exc 7 | 8 | from PyQt5.QtWidgets import QApplication, QWidget, QDesktopWidget 9 | from PyQt5.QtGui import QPen, QBitmap, QPainter, QBrush 10 | from PyQt5.QtCore import QRect, QPoint, Qt 11 | 12 | sys.path.append(".") 13 | from configs import folder_path 14 | from src.api import write_error 15 | 16 | 17 | class WScreenShot(QWidget): 18 | 19 | def __init__(self, Init, chooseRange, parent=None): 20 | 21 | super(WScreenShot, self).__init__(parent) 22 | self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) # | Qt.Tool) 23 | self.setWindowState(Qt.WindowFullScreen | Qt.WindowActive) 24 | self.setStyleSheet('''background-color:black; ''') 25 | self.setWindowOpacity(0.6) 26 | desktopRect = QDesktopWidget().screenGeometry() 27 | self.setGeometry(desktopRect) 28 | self.setCursor(Qt.CrossCursor) 29 | self.blackMask = QBitmap(desktopRect.size()) 30 | self.blackMask.fill(Qt.black) 31 | self.mask = self.blackMask.copy() 32 | self.isDrawing = False 33 | self.startPoint = QPoint() 34 | self.endPoint = QPoint() 35 | self.Init = Init 36 | self.chooseRange = chooseRange 37 | 38 | def paintEvent(self, event): 39 | 40 | try: 41 | if self.isDrawing: 42 | self.mask = self.blackMask.copy() 43 | pp = QPainter(self.mask) 44 | pen = QPen() 45 | pen.setStyle(Qt.NoPen) 46 | pp.setPen(pen) 47 | brush = QBrush(Qt.white) 48 | pp.setBrush(brush) 49 | pp.drawRect(QRect(self.startPoint, self.endPoint)) 50 | self.setMask(QBitmap(self.mask)) 51 | except Exception: 52 | write_error(format_exc()) 53 | 54 | def mousePressEvent(self, event): 55 | 56 | try: 57 | if event.button() == Qt.LeftButton: 58 | self.startPoint = event.pos() 59 | self.endPoint = self.startPoint 60 | self.isDrawing = True 61 | except Exception: 62 | write_error(format_exc()) 63 | 64 | def mouseMoveEvent(self, event): 65 | 66 | try: 67 | if self.isDrawing: 68 | self.endPoint = event.pos() 69 | self.update() 70 | except Exception: 71 | write_error(format_exc()) 72 | 73 | def getRange(self): 74 | 75 | start = findall(r'(\d+), (\d+)', str(self.startPoint))[0] 76 | end = findall(r'\d+, \d+', str(self.endPoint))[0] 77 | end = end.split(', ') 78 | 79 | X1 = int(start[0]) 80 | Y1 = int(start[1]) 81 | X2 = int(end[0]) 82 | Y2 = int(end[1]) 83 | 84 | if X1 > X2: 85 | tmp = X1 86 | X1 = X2 87 | X2 = tmp 88 | 89 | if Y1 > Y2: 90 | tmp = Y1 91 | Y1 = Y2 92 | Y2 = tmp 93 | 94 | with open(folder_path + '/config/settin.json') as file: 95 | data = load(file) 96 | 97 | data["range"]["X1"] = X1 98 | data["range"]["Y1"] = Y1 99 | data["range"]["X2"] = X2 100 | data["range"]["Y2"] = Y2 101 | 102 | with open(folder_path + '/config/settin.json', 'w') as file: 103 | dump(data, file, indent=2) 104 | 105 | self.chooseRange.setGeometry(X1, Y1, X2 - X1, Y2 - Y1) 106 | self.chooseRange.Label.setGeometry(0, 0, X2 - X1, Y2 - Y1) 107 | self.chooseRange.show() 108 | # screenshot = QApplication.primaryScreen().grabWindow(QApplication.desktop().winId()) 109 | # screenshot.save(folder_path + '/config/full.jpg', format='JPG', quality=100) 110 | # outputRegion = screenshot.copy(QRect(int(X1), int(Y1), int(X2 - X1), int(Y2 - Y1))) 111 | # outputRegion.save(folder_path + '/config/image.jpg', format='JPG', quality=100) 112 | 113 | def updata_Init(self): 114 | 115 | try: 116 | if self.Init.mode == False: 117 | self.Init.start_login() 118 | except Exception: 119 | write_error(format_exc()) 120 | 121 | def mouseReleaseEvent(self, event): 122 | 123 | try: 124 | if event.button() == Qt.LeftButton: 125 | self.endPoint = event.pos() 126 | self.getRange() 127 | 128 | self.close() 129 | self.updata_Init() 130 | except Exception: 131 | write_error(format_exc()) 132 | 133 | def keyPressEvent(self, QKeyEvent): 134 | if QKeyEvent.key() == Qt.Key_Escape: 135 | self.close() 136 | 137 | 138 | if __name__ == '__main__': 139 | from src.init import MainInterface 140 | from src.choose_range import Range 141 | 142 | QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) 143 | app = QApplication(sys.argv) 144 | Init = MainInterface(1, 'admin') 145 | chooseRange = Range(100, 100, 500, 200) 146 | 147 | win = WScreenShot(Init, chooseRange) 148 | win.show() 149 | 150 | app.exec_() 151 | -------------------------------------------------------------------------------- /src/screen_rate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | try: 4 | from win32.lib import win32con 5 | from win32.win32api import GetSystemMetrics 6 | from win32.win32gui import GetDC 7 | from win32.win32print import GetDeviceCaps 8 | 9 | win32_flag = True 10 | except: 11 | win32_flag = False 12 | screen_scale_rate_ = 1 13 | 14 | from src.api import write_error 15 | from configs import Config 16 | 17 | write_error("[INFO] 系统 {} 没有win32, 无法自动获取屏幕缩放比例,设置为: {}".format(Config().platform, screen_scale_rate_)) 18 | 19 | 20 | def get_real_resolution(): 21 | """获取真实的分辨率""" 22 | hDC = GetDC(0) 23 | # 横向分辨率 24 | w = GetDeviceCaps(hDC, win32con.DESKTOPHORZRES) 25 | # 纵向分辨率 26 | h = GetDeviceCaps(hDC, win32con.DESKTOPVERTRES) 27 | return w, h 28 | 29 | 30 | def get_screen_size(): 31 | """获取缩放后的分辨率""" 32 | w = GetSystemMetrics(0) 33 | h = GetSystemMetrics(1) 34 | return w, h 35 | 36 | 37 | def get_screen_rate(): 38 | if win32_flag: 39 | real_resolution = get_real_resolution() 40 | screen_size = get_screen_size() 41 | screen_scale_rate = round(real_resolution[0] / screen_size[0], 2) 42 | else: 43 | screen_scale_rate = screen_scale_rate_ 44 | return screen_scale_rate 45 | 46 | 47 | if __name__ == '__main__': 48 | print("get_screen_rate:", get_screen_rate()) 49 | -------------------------------------------------------------------------------- /src/settin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from json import dump, load 4 | 5 | from PyQt5.QtWidgets import QLabel, QPushButton, QApplication, QWidget, QColorDialog, QTabWidget, QComboBox, \ 6 | QCheckBox, QSpinBox, QFontComboBox, QToolButton, QSlider, QLineEdit 7 | from PyQt5.QtGui import QIcon, QPixmap, QFont 8 | from PyQt5.QtCore import QSize, QRect, Qt 9 | 10 | from src.screen_rate import get_screen_rate 11 | from src.api import MessageBox 12 | from configs import Config, folder_path 13 | 14 | config = Config() 15 | 16 | 17 | class SettinInterface(QWidget): 18 | def __init__(self, screen_scale_rate): 19 | super(SettinInterface, self).__init__() 20 | 21 | if 1.01 <= screen_scale_rate <= 1.49: 22 | self.rate = 1.25 23 | self.px = 80 24 | self.image_sign = 2 25 | else: 26 | self.rate = 1 27 | self.px = 75 28 | self.image_sign = 1 29 | 30 | self.get_settin() 31 | self.setupUi() 32 | 33 | def setupUi(self): 34 | 35 | # 窗口尺寸及不可拉伸 36 | self.setWindowFlags(Qt.WindowStaysOnTopHint) 37 | self.resize(404 * self.rate, 576 * self.rate) 38 | self.setMinimumSize(QSize(404 * self.rate, 576 * self.rate)) 39 | self.setMaximumSize(QSize(404 * self.rate, 576 * self.rate)) 40 | self.setWindowFlags(Qt.WindowMinimizeButtonHint) 41 | 42 | # 窗口标题 43 | self.setWindowTitle("设置") 44 | 45 | # 窗口样式 46 | # self.setStyleSheet("QWidget {""font: 9pt \"华康方圆体W7\";" 47 | # "background-image: url(./config/Background%d.jpg);" 48 | # "background-repeat: no-repeat;" 49 | # "background-size:cover;""}" % self.image_sign) 50 | self.setStyleSheet("QWidget {""font: 9pt \"微软雅黑\"};") # 华康方圆体W7 51 | 52 | # 窗口图标 53 | self.icon = QIcon() 54 | self.icon.addPixmap(QPixmap(folder_path + "/config/logo.ico"), QIcon.Normal, QIcon.On) 55 | self.setWindowIcon(self.icon) 56 | 57 | # 顶部工具栏 58 | self.tabWidget = QTabWidget(self) 59 | self.tabWidget.setGeometry(QRect(-2, 0, 410 * self.rate, 580 * self.rate)) 60 | self.tabWidget.setCurrentIndex(0) 61 | 62 | # 工具栏样式 63 | self.tabWidget.setStyleSheet("QTabBar::tab {""min-width:%dpx;" 64 | "background: rgba(255, 255, 255, 1);" 65 | "}" 66 | "QTabBar::tab:selected {""border-bottom: 2px solid #4796f0;""}" 67 | "QLabel{""background: transparent;""}" 68 | "QCheckBox{""background: transparent;""}" % (self.px) 69 | ) 70 | 71 | # 工具栏2 72 | self.tab_2 = QWidget() 73 | self.tabWidget.addTab(self.tab_2, "") 74 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), "设置") 75 | 76 | # 原语言标签 77 | self.translateSource_label_6 = QLabel(self.tab_2) 78 | self.translateSource_label_6.setGeometry( 79 | QRect(30 * self.rate, 20 * self.rate, 151 * self.rate, 16 * self.rate)) 80 | self.translateSource_label_6.setText("待识别的语言类型:") 81 | 82 | # 原语言comboBox 83 | self.language_comboBox = QComboBox(self.tab_2) 84 | self.language_comboBox.setGeometry( 85 | QRect(190 * self.rate, 20 * self.rate, 150 * self.rate, 22 * self.rate)) 86 | for idx, language_name in enumerate(config.language_name): 87 | self.language_comboBox.addItem("") 88 | self.language_comboBox.setItemText(idx, language_name[1]) 89 | self.language_comboBox.setStyleSheet("background: rgba(255, 255, 255, 0.4);") 90 | self.language_comboBox.setCurrentIndex(self.language) 91 | 92 | # 是否显示识别结果checkBox 93 | self.vis_result_checkBox = QCheckBox(self.tab_2) 94 | self.vis_result_checkBox.setGeometry( 95 | QRect(30 * self.rate, 52 * self.rate, 300 * self.rate, 16 * self.rate)) 96 | self.vis_result_checkBox.setChecked(self.vis_result) 97 | self.vis_result_checkBox.setText("可视化识别结果(对识别结果进行修改及导出)") 98 | 99 | # 自动复制到剪贴板checkBox 100 | self.Clipboard_checkBox = QCheckBox(self.tab_2) 101 | self.Clipboard_checkBox.setGeometry( 102 | QRect(30 * self.rate, 80 * self.rate, 231 * self.rate, 16 * self.rate)) 103 | self.Clipboard_checkBox.setChecked(self.showClipboard) 104 | self.Clipboard_checkBox.setText("识别结果自动复制到剪贴板") 105 | 106 | # 字体大小设定标签 107 | self.fontSize_label = QLabel(self.tab_2) 108 | self.fontSize_label.setGeometry(QRect(30 * self.rate, 120 * self.rate, 145 * self.rate, 16 * self.rate)) 109 | self.fontSize_label.setText("显示文字大小:") 110 | 111 | # 字体大小设定 112 | self.fontSize_spinBox = QSpinBox(self.tab_2) 113 | self.fontSize_spinBox.setGeometry( 114 | QRect(190 * self.rate, 120 * self.rate, 50 * self.rate, 25 * self.rate)) 115 | self.fontSize_spinBox.setMinimum(10) 116 | self.fontSize_spinBox.setMaximum(30) 117 | self.fontSize_spinBox.setStyleSheet("background: rgba(255, 255, 255, 0)") 118 | self.fontSize_spinBox.setValue(self.fontSize) 119 | 120 | # 字体样式设定标签 121 | self.translate_label = QLabel(self.tab_2) 122 | self.translate_label.setGeometry(QRect(30 * self.rate, 145 * self.rate, 145 * self.rate, 20 * self.rate)) 123 | self.translate_label.setText("显示字体类型:") 124 | 125 | # 字体样式设定 126 | self.fontComboBox = QFontComboBox(self.tab_2) 127 | self.fontComboBox.setGeometry(QRect(190 * self.rate, 145 * self.rate, 151 * self.rate, 25 * self.rate)) 128 | self.fontComboBox.setStyleSheet("background: rgba(255, 255, 255, 0.4)") 129 | self.fontComboBox.activated[str].connect(self.get_fontType) 130 | self.ComboBoxFont = QFont(self.fontType) 131 | self.fontComboBox.setCurrentFont(self.ComboBoxFont) 132 | 133 | # 字体颜色设定标签 134 | self.colour_label = QLabel(self.tab_2) 135 | self.colour_label.setGeometry(QRect(30 * self.rate, 172 * self.rate, 340 * self.rate, 25 * self.rate)) 136 | self.colour_label.setText("显示文字颜色:") 137 | 138 | # 字体颜色按钮 139 | self.originalColour_toolButton = QToolButton(self.tab_2) 140 | self.originalColour_toolButton.setGeometry( 141 | QRect(190 * self.rate, 175 * self.rate, 71 * self.rate, 25 * self.rate)) 142 | self.originalColour_toolButton.setStyleSheet( 143 | "background: rgba(255, 255, 255, 0.4); color: {};".format(self.originalColor)) 144 | self.originalColour_toolButton.clicked.connect(lambda: self.get_font_color()) 145 | self.originalColour_toolButton.setText("选择颜色") 146 | 147 | # 显示颜色样式checkBox 148 | self.showColorType_checkBox = QCheckBox(self.tab_2) 149 | self.showColorType_checkBox.setGeometry( 150 | QRect(30 * self.rate, 200 * self.rate, 340 * self.rate, 20 * self.rate)) 151 | self.showColorType_checkBox.setChecked(self.showColorType) 152 | self.showColorType_checkBox.setText("是否使用实心字体样式(不勾选则显示描边字体样式)") 153 | 154 | # 截屏键快捷键checkBox 155 | self.shortcutKey2_checkBox = QCheckBox(self.tab_2) 156 | self.shortcutKey2_checkBox.setGeometry( 157 | QRect(30 * self.rate, 250 * self.rate, 160 * self.rate, 16 * self.rate)) 158 | self.shortcutKey2_checkBox.setStyleSheet("background: transparent;") 159 | self.shortcutKey2_checkBox.setChecked(self.showHotKey2) 160 | self.shortcutKey2_checkBox.setText("是否使用截屏快捷键:") 161 | 162 | # 截屏键的快捷键 163 | self.HotKey2_ComboBox = QComboBox(self.tab_2) 164 | self.HotKey2_ComboBox.setGeometry( 165 | QRect(200 * self.rate, 250 * self.rate, 120 * self.rate, 21 * self.rate)) 166 | self.HotKey2_ComboBox.setStyleSheet("background: rgba(255, 255, 255, 0.4);") 167 | for index, HotKey in enumerate(self.HotKeys): 168 | self.HotKey2_ComboBox.addItem("") 169 | self.HotKey2_ComboBox.setItemText(index, HotKey) 170 | self.HotKey2_ComboBox.setCurrentIndex(self.showHotKey1Value2) 171 | 172 | # 翻译键快捷键checkBox 173 | self.shortcutKey1_checkBox = QCheckBox(self.tab_2) 174 | self.shortcutKey1_checkBox.setGeometry( 175 | QRect(30 * self.rate, 280 * self.rate, 160 * self.rate, 16 * self.rate)) 176 | self.shortcutKey1_checkBox.setStyleSheet("background: transparent;") 177 | self.shortcutKey1_checkBox.setChecked(self.showHotKey1) 178 | self.shortcutKey1_checkBox.setText("是否使用识别快捷键:") 179 | 180 | # 翻译键的快捷键 181 | self.HotKey1_ComboBox = QComboBox(self.tab_2) 182 | self.HotKey1_ComboBox.setGeometry( 183 | QRect(200 * self.rate, 280 * self.rate, 120 * self.rate, 21 * self.rate)) 184 | self.HotKey1_ComboBox.setStyleSheet("background: rgba(255, 255, 255, 0.4);") 185 | for index, HotKey in enumerate(self.HotKeys): 186 | self.HotKey1_ComboBox.addItem("") 187 | self.HotKey1_ComboBox.setItemText(index, HotKey) 188 | self.HotKey1_ComboBox.setCurrentIndex(self.showHotKey1Value1) 189 | 190 | # 是否翻译 191 | self.translate_checkBox = QCheckBox(self.tab_2) 192 | self.translate_checkBox.setGeometry( 193 | QRect(30 * self.rate, 315 * self.rate, 300 * self.rate, 16 * self.rate)) 194 | self.translate_checkBox.setChecked(self.need_translate) 195 | self.translate_checkBox.setText("是否翻译为汉语") 196 | 197 | # 是否翻译 198 | self.show_org_checkBox = QCheckBox(self.tab_2) 199 | self.show_org_checkBox.setGeometry( 200 | QRect(30 * self.rate, 340 * self.rate, 300 * self.rate, 16 * self.rate)) 201 | self.show_org_checkBox.setChecked(self.showOriginal) 202 | self.show_org_checkBox.setText("翻译后是否显示原文") 203 | 204 | # 翻译框透明度设定标签1 205 | self.tab4_label_1 = QLabel(self.tab_2) 206 | self.tab4_label_1.setGeometry(QRect(30 * self.rate, 380 * self.rate, 211 * self.rate, 16 * self.rate)) 207 | self.tab4_label_1.setText("调节显示界面的透明度") 208 | 209 | # 翻译框透明度设定 210 | self.horizontalSlider = QSlider(self.tab_2) 211 | self.horizontalSlider.setGeometry( 212 | QRect(30 * self.rate, 400 * self.rate, 347 * self.rate, 22 * self.rate)) 213 | self.horizontalSlider.setStyleSheet("background: transparent;") 214 | self.horizontalSlider.setMaximum(100) 215 | self.horizontalSlider.setOrientation(Qt.Horizontal) 216 | self.horizontalSlider.setValue(self.horizontal) 217 | self.horizontalSlider.valueChanged.connect(self.get_horizontal) 218 | 219 | # 翻译框透明度设定标签2 220 | self.tab2_label_2 = QLabel(self.tab_2) 221 | self.tab2_label_2.setGeometry(QRect(30 * self.rate, 420 * self.rate, 61 * self.rate, 20 * self.rate)) 222 | self.tab2_label_2.setText("完全透明") 223 | 224 | # 翻译框透明度设定标签3 225 | self.tab2_label_3 = QLabel(self.tab_2) 226 | self.tab2_label_3.setGeometry(QRect(310 * self.rate, 420 * self.rate, 71 * self.rate, 20 * self.rate)) 227 | self.tab2_label_3.setText("完全不透明") 228 | 229 | # 工具栏3 230 | self.tab_3 = QWidget() 231 | self.tabWidget.addTab(self.tab_3, "") 232 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), "关于") 233 | 234 | self.tab3_label = QLabel(self.tab_3) 235 | self.tab3_label.setGeometry(QRect(30 * self.rate, 75 * self.rate, 100 * self.rate, 40 * self.rate)) 236 | self.tab3_label.setText("说明:") 237 | 238 | self.tab3_label2 = QLabel(self.tab_3) 239 | self.tab3_label2.setTextInteractionFlags(Qt.TextSelectableByMouse) 240 | self.tab3_label2.setWordWrap(True) 241 | self.tab3_label2.setGeometry(QRect(50 * self.rate, 100 * self.rate, 400 * self.rate, 80 * self.rate)) 242 | self.tab3_label2.setText( 243 | "Dango-OCR是一款开源的OCR文字识别软件。\n如果在使用过程中有什么问题或者建议,欢迎微信交流(itlane)\n" 244 | "或者在github(https://github.com/zhangming8/Dango-ocr)\n上留言") 245 | 246 | self.tab3_label3 = QLabel(self.tab_3) 247 | self.tab3_label3.setWordWrap(True) 248 | self.tab3_label3.setGeometry(QRect(30 * self.rate, 350 * self.rate, 400 * self.rate, 80 * self.rate)) 249 | self.tab3_label3.setText( 250 | "参考:\n https://github.com/zhangming8/ocr_algo_server\n " 251 | "https://github.com/PaddlePaddle/PaddleOCR\n " 252 | "https://github.com/PantsuDango/Dango-Translator") 253 | 254 | # 设置保存按钮 255 | self.SaveButton = QPushButton(self) 256 | self.SaveButton.setGeometry(QRect(85 * self.rate, 515 * self.rate, 90 * self.rate, 30 * self.rate)) 257 | self.SaveButton.setStyleSheet("background: rgba(255, 255, 255, 0.4);font: 12pt;") 258 | self.SaveButton.setText("保存设置") 259 | 260 | # 设置返回按钮 261 | self.CancelButton = QPushButton(self) 262 | self.CancelButton.setGeometry(QRect(232 * self.rate, 515 * self.rate, 90 * self.rate, 30 * self.rate)) 263 | self.CancelButton.setStyleSheet("background: rgba(255, 255, 255, 0.4);font: 12pt") 264 | self.CancelButton.setText("退 出") 265 | 266 | def get_settin(self): # 获取所有预设值 267 | 268 | with open(folder_path + '/config/settin.json') as file: 269 | self.data = load(file) 270 | 271 | # 获取各翻译源颜色预设值 272 | self.originalColor = self.data["fontColor"]["original"] 273 | 274 | # 获取翻译字体大小预设值 275 | self.fontSize = self.data["fontSize"] 276 | 277 | # 获取翻译字体样式预设值 278 | self.fontType = self.data["fontType"] 279 | 280 | # 获取颜色样式预设值 281 | self.showColorType = self.data["showColorType"] 282 | if self.showColorType == "True": 283 | self.showColorType = True 284 | else: 285 | self.showColorType = False 286 | 287 | # 获取是否显示原文预设值 288 | self.showOriginal = self.data["showOriginal"] 289 | if self.showOriginal == "True": 290 | self.showOriginal = True 291 | else: 292 | self.showOriginal = False 293 | 294 | # 获取是否将原文复制到剪贴板预设值 295 | self.showClipboard = self.data["showClipboard"] 296 | if self.showClipboard == "True": 297 | self.showClipboard = True 298 | else: 299 | self.showClipboard = False 300 | 301 | self.vis_result = self.data.get("vis_result", False) 302 | if self.vis_result == "True": 303 | self.vis_result = True 304 | else: 305 | self.vis_result = False 306 | 307 | self.need_translate = self.data.get("need_translate", False) 308 | if self.need_translate == "True": 309 | self.need_translate = True 310 | else: 311 | self.need_translate = False 312 | 313 | # 所有可设置的快捷键 314 | self.HotKeys = ['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 315 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 316 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 317 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 318 | 'Back', 'Tab', 'Space', 'Left', 'Up', 'Right', 'Down', 'Delete', 319 | 'Numpad0', 'Numpad1', 'Numpad2', 'Numpad3', 'Numpad4', 'Numpad5', 'Numpad6', 'Numpad7', 320 | 'Numpad8', 'Numpad9'] 321 | self.QtHotKeys = [Qt.Key_F1, Qt.Key_F2, Qt.Key_F3, Qt.Key_F4, Qt.Key_F5, 322 | Qt.Key_F6, Qt.Key_F7, Qt.Key_F8, Qt.Key_F9, Qt.Key_F10, 323 | Qt.Key_F11, Qt.Key_F12, Qt.Key_A, Qt.Key_B, Qt.Key_C, 324 | Qt.Key_D, Qt.Key_E, Qt.Key_F, Qt.Key_G, Qt.Key_H, 325 | Qt.Key_I, Qt.Key_J, Qt.Key_K, Qt.Key_L, Qt.Key_M, 326 | Qt.Key_N, Qt.Key_O, Qt.Key_P, Qt.Key_Q, Qt.Key_R, 327 | Qt.Key_S, Qt.Key_T, Qt.Key_U, Qt.Key_V, Qt.Key_W, 328 | Qt.Key_X, Qt.Key_Y, Qt.Key_Z, Qt.Key_0, Qt.Key_1, 329 | Qt.Key_2, Qt.Key_3, Qt.Key_4, Qt.Key_5, Qt.Key_6, 330 | Qt.Key_7, Qt.Key_8, Qt.Key_9, Qt.Key_Back, Qt.Key_Tab, 331 | Qt.Key_Space, Qt.Key_Left, Qt.Key_Up, Qt.Key_Right, 332 | Qt.Key_Down, Qt.Key_Delete, Qt.Key_0, Qt.Key_1, Qt.Key_2, 333 | Qt.Key_3, Qt.Key_4, Qt.Key_5, Qt.Key_6, Qt.Key_7, 334 | Qt.Key_8, Qt.Key_9] 335 | self.QtHotKeysMaps = {} 336 | for idx in range(len(self.HotKeys)): 337 | self.QtHotKeysMaps[self.HotKeys[idx]] = self.QtHotKeys[idx] 338 | 339 | # 获取翻译键快捷键的热键预设值 340 | self.showHotKey1Value1 = self.data["showHotKeyValue1"] 341 | self.showHotKey1Value1 = self.HotKeys.index(self.showHotKey1Value1) 342 | 343 | # 获取范围键快捷键的热键预设值 344 | self.showHotKey1Value2 = self.data["showHotKeyValue2"] 345 | self.showHotKey1Value2 = self.HotKeys.index(self.showHotKey1Value2) 346 | 347 | # 获取是否启用翻译键快捷键预设值 348 | self.showHotKey1 = self.data["showHotKey1"] 349 | if self.showHotKey1 == "True": 350 | self.showHotKey1 = True 351 | else: 352 | self.showHotKey1 = False 353 | 354 | # 获取是否启用范围键快捷键预设值 355 | self.showHotKey2 = self.data["showHotKey2"] 356 | if self.showHotKey2 == "True": 357 | self.showHotKey2 = True 358 | else: 359 | self.showHotKey2 = False 360 | 361 | # 获取文本框透明度预设值 362 | self.horizontal = self.data["horizontal"] 363 | 364 | # 获取翻译语言预设值 365 | self.language = config.language_map_reverse[self.data["language"]] 366 | 367 | def get_font_color(self): # 各翻译源字体颜色 368 | color = QColorDialog.getColor() 369 | self.originalColor = color.name() 370 | self.originalColour_toolButton.setStyleSheet( 371 | "background: rgba(255, 255, 255, 0.4);color: {};".format(color.name())) 372 | self.data["fontColor"]["original"] = self.originalColor 373 | 374 | def get_fontType(self, text): # 字体样式 375 | 376 | self.fontType = text 377 | self.data["fontType"] = self.fontType 378 | 379 | def showColorType_state(self): # 颜色样式 380 | 381 | if self.showColorType_checkBox.isChecked(): 382 | self.showColorType = "True" 383 | else: 384 | self.showColorType = "False" 385 | self.data["showColorType"] = self.showColorType 386 | 387 | def showClipboard_state(self): # 是否将原文自动复制到剪贴板 388 | 389 | if self.Clipboard_checkBox.isChecked(): 390 | self.showClipboard = "True" 391 | else: 392 | self.showClipboard = "False" 393 | self.data["showClipboard"] = self.showClipboard 394 | 395 | def VisResult_state(self): 396 | if self.vis_result_checkBox.isChecked(): 397 | self.vis_result = "True" 398 | else: 399 | self.vis_result = "False" 400 | self.data["vis_result"] = self.vis_result 401 | 402 | def NeedTranslate_state(self): 403 | if self.translate_checkBox.isChecked(): 404 | self.need_translate = "True" 405 | else: 406 | self.need_translate = "False" 407 | self.data["need_translate"] = self.need_translate 408 | 409 | def ShowOrigion_state(self): 410 | if self.show_org_checkBox.isChecked(): 411 | self.showOriginal = "True" 412 | else: 413 | self.showOriginal = "False" 414 | self.data["showOriginal"] = self.showOriginal 415 | 416 | def showHotKey1_state(self): # 是否启用翻译键快捷键 417 | 418 | if self.shortcutKey1_checkBox.isChecked(): 419 | self.showHotKey1 = "True" 420 | else: 421 | self.showHotKey1 = "False" 422 | self.data["showHotKey1"] = self.showHotKey1 423 | 424 | def showHotKey2_state(self): # 是否启用范围键快捷键 425 | 426 | if self.shortcutKey2_checkBox.isChecked(): 427 | self.showHotKey2 = "True" 428 | else: 429 | self.showHotKey2 = "False" 430 | self.data["showHotKey2"] = self.showHotKey2 431 | 432 | def get_horizontal(self): # 文本框透明度 433 | 434 | self.horizontal = self.horizontalSlider.value() 435 | self.data["horizontal"] = self.horizontal 436 | 437 | def save_fontSize(self): # 翻译源字体大小 438 | 439 | self.data["fontSize"] = self.fontSize_spinBox.value() 440 | 441 | def range(self): 442 | 443 | with open(folder_path + '/config/settin.json') as file: 444 | data1 = load(file) 445 | 446 | self.data["range"]["X1"] = data1["range"]["X1"] 447 | self.data["range"]["Y1"] = data1["range"]["Y1"] 448 | self.data["range"]["X2"] = data1["range"]["X2"] 449 | self.data["range"]["Y2"] = data1["range"]["Y2"] 450 | 451 | def save_language(self): # 保存翻译语种 452 | 453 | self.data["language"] = config.language_map[self.language_comboBox.currentIndex()][0] 454 | 455 | def save_showHotKeyValue1(self): # 保存翻译键快捷键 456 | HotKey_index = self.HotKey1_ComboBox.currentIndex() 457 | self.data["showHotKeyValue1"] = self.HotKeys[HotKey_index] 458 | 459 | def save_showHotKeyValue2(self): # 保存范围键快捷键 460 | HotKey_index = self.HotKey2_ComboBox.currentIndex() 461 | self.data["showHotKeyValue2"] = self.HotKeys[HotKey_index] 462 | 463 | def save_settin(self): 464 | 465 | self.range() 466 | self.get_horizontal() 467 | self.save_fontSize() 468 | 469 | self.showColorType_state() 470 | self.showClipboard_state() 471 | self.VisResult_state() 472 | self.NeedTranslate_state() 473 | self.ShowOrigion_state() 474 | self.save_language() 475 | 476 | self.showHotKey1_state() 477 | self.showHotKey2_state() 478 | self.save_showHotKeyValue1() 479 | self.save_showHotKeyValue2() 480 | 481 | with open(folder_path + '/config/settin.json', 'w') as file: 482 | dump(self.data, file, indent=2) 483 | 484 | MessageBox('保存设置', '保存成功啦 ヾ(๑╹◡╹)ノ"') 485 | 486 | 487 | if __name__ == "__main__": 488 | import sys 489 | 490 | screen_scale_rate = get_screen_rate() 491 | APP = QApplication(sys.argv) 492 | Settin = SettinInterface(screen_scale_rate) 493 | Settin.SaveButton.clicked.connect(Settin.save_language) 494 | Settin.show() 495 | sys.exit(APP.exec_()) 496 | -------------------------------------------------------------------------------- /src/switch.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow 4 | from PyQt5.QtGui import QFont, QColor, QLinearGradient, QPainter, QPainterPath 5 | from PyQt5.QtCore import QRect, Qt, pyqtSignal, QTimer, QRectF 6 | 7 | 8 | class SwitchBtn(QWidget): 9 | # 信号 10 | checkedChanged = pyqtSignal(bool) 11 | 12 | def __init__(self, parent=None): 13 | super(QWidget, self).__init__(parent) 14 | 15 | self.checked = False 16 | self.bgColorOff = QColor(255, 255, 255) 17 | # self.bgColorOn = QColor(0, 0, 0) 18 | # 漸變色背景 19 | self.bgColorOn = QLinearGradient(0, 0, self.width(), self.height()) 20 | self.bgColorOn.setColorAt(0, QColor('#ffcef9')); 21 | self.bgColorOn.setColorAt(1, QColor('#ff7cbc')); 22 | 23 | self.sliderColorOff = QColor('#dedede') 24 | self.sliderColorOn = QColor('#fefefe') # #bee0ee 25 | 26 | self.textColorOff = QColor(143, 143, 143) 27 | self.textColorOn = QColor(255, 255, 255) 28 | 29 | self.textOff = "手动" 30 | self.textOn = "自动" 31 | 32 | self.space = 2 33 | self.rectRadius = 5 34 | 35 | self.step = self.width() / 50 36 | self.startX = 0 37 | self.endX = 0 38 | 39 | self.timer = QTimer(self) # 初始化一个定时器 40 | self.timer.timeout.connect(self.updateValue) # 计时结束调用operate()方法 41 | 42 | # self.timer.start(5) # 设置计时间隔并启动 43 | 44 | self.setFont(QFont("华康方圆体W7", 9)) 45 | 46 | # self.resize(55,22) 47 | 48 | def updateValue(self): 49 | if self.checked: 50 | if self.startX < self.endX: 51 | self.startX = self.startX + self.step 52 | else: 53 | self.startX = self.endX 54 | self.timer.stop() 55 | else: 56 | if self.startX > self.endX: 57 | self.startX = self.startX - self.step 58 | else: 59 | self.startX = self.endX 60 | self.timer.stop() 61 | 62 | self.update() 63 | 64 | def mousePressEvent(self, event): 65 | self.checked = not self.checked 66 | # 发射信号 67 | self.checkedChanged.emit(self.checked) 68 | 69 | # 每次移动的步长为宽度的50分之一 70 | self.step = self.width() / 50 71 | # 状态切换改变后自动计算终点坐标 72 | if self.checked: 73 | self.endX = self.width() - self.height() 74 | else: 75 | self.endX = 0 76 | self.timer.start(5) 77 | 78 | def paintEvent(self, evt): 79 | # 绘制准备工作, 启用反锯齿 80 | painter = QPainter() 81 | 82 | painter.begin(self) 83 | 84 | painter.setRenderHint(QPainter.Antialiasing) 85 | 86 | # 绘制背景 87 | self.drawBg(evt, painter) 88 | # 绘制滑块 89 | self.drawSlider(evt, painter) 90 | # 绘制文字 91 | self.drawText(evt, painter) 92 | 93 | painter.end() 94 | 95 | def drawText(self, event, painter): 96 | painter.save() 97 | 98 | if self.checked: 99 | painter.setPen(self.textColorOn) 100 | painter.drawText(self.space, 0, self.width() / 2 + self.space * 2, self.height(), Qt.AlignCenter, 101 | self.textOn) 102 | else: 103 | painter.setPen(self.textColorOff) 104 | painter.drawText(self.width() / 2 - self.space * 2, 0, self.width() / 2 - self.space, self.height(), 105 | Qt.AlignCenter, self.textOff) 106 | 107 | painter.restore() 108 | 109 | def drawBg(self, event, painter): 110 | painter.save() 111 | painter.setPen(Qt.NoPen) 112 | 113 | if self.checked: 114 | painter.setBrush(self.bgColorOn) 115 | else: 116 | painter.setBrush(self.bgColorOff) 117 | 118 | rect = QRect(0, 0, self.width(), self.height()) 119 | # 半径为高度的一半 120 | radius = rect.height() / 2 121 | # 圆的宽度为高度 122 | circleWidth = rect.height() 123 | 124 | path = QPainterPath() 125 | path.moveTo(radius, rect.left()) 126 | path.arcTo(QRectF(rect.left(), rect.top(), circleWidth, circleWidth), 90, 180) 127 | path.lineTo(rect.width() - radius, rect.height()) 128 | path.arcTo(QRectF(rect.width() - rect.height(), rect.top(), circleWidth, circleWidth), 270, 180) 129 | path.lineTo(radius, rect.top()) 130 | 131 | painter.drawPath(path) 132 | painter.restore() 133 | 134 | def drawSlider(self, event, painter): 135 | painter.save() 136 | 137 | if self.checked: 138 | painter.setBrush(self.sliderColorOn) 139 | else: 140 | painter.setBrush(self.sliderColorOff) 141 | 142 | rect = QRect(0, 0, self.width(), self.height()) 143 | sliderWidth = rect.height() - self.space * 2 144 | sliderRect = QRect(self.startX + self.space, self.space, sliderWidth, sliderWidth) 145 | painter.setPen(Qt.NoPen) # 没有线。比如QPainter.drawRect()填充,但没有绘制任何边界线 146 | painter.drawEllipse(sliderRect) 147 | 148 | painter.restore() 149 | 150 | 151 | class MainWindow(QMainWindow): 152 | def __init__(self, parent=None): 153 | super(MainWindow, self).__init__(parent) 154 | self.resize(400, 200) 155 | self.switchBtn = SwitchBtn(self) 156 | self.switchBtn.setGeometry(10, 10, 70, 30) 157 | self.switchBtn.checkedChanged.connect(self.getState) 158 | self.status = self.statusBar() 159 | self.status.showMessage("this is a example", 5000) 160 | self.setWindowTitle("PyQt") 161 | 162 | def getState(self, checked): 163 | print("checked=", checked) 164 | 165 | 166 | if __name__ == "__main__": 167 | app = QApplication(sys.argv) 168 | form = MainWindow() 169 | # form = SwitchBtn() 170 | form.show() 171 | sys.exit(app.exec_()) 172 | -------------------------------------------------------------------------------- /src/translate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from time import time, sleep 4 | from json import load, dump 5 | from cv2 import cvtColor, COLOR_BGR2GRAY, calcHist, resize 6 | from numpy import fromstring, uint8 7 | from pyperclip import copy 8 | from traceback import format_exc 9 | from difflib import SequenceMatcher 10 | from qtawesome import icon as qticon 11 | 12 | from PyQt5.QtWidgets import QApplication 13 | from PyQt5.QtCore import QThread, pyqtSignal 14 | 15 | from src.api import ocr, write_error 16 | from configs import folder_path, Config 17 | 18 | config = Config() 19 | 20 | 21 | def pixmap_to_array(pixmap, channels_count=4): 22 | size = pixmap.size() 23 | width = size.width() 24 | height = size.height() 25 | 26 | image = pixmap.toImage() 27 | s = image.bits().asstring(width * height * channels_count) 28 | img = fromstring(s, dtype=uint8).reshape((height, width, channels_count)) 29 | img = img[:, :, :3] 30 | return img.astype(uint8) 31 | 32 | 33 | # 截图 34 | def image_cut(data): 35 | x1 = data["range"]['X1'] + 2 # 不截虚线框 36 | y1 = data["range"]['Y1'] + 2 37 | x2 = data["range"]['X2'] - 2 38 | y2 = data["range"]['Y2'] - 2 39 | 40 | image = None 41 | try: 42 | screen = QApplication.primaryScreen() 43 | pix = screen.grabWindow(QApplication.desktop().winId(), x1, y1, x2 - x1, y2 - y1) 44 | # pix.save(folder_path + '/config/image.jpg') 45 | image = pixmap_to_array(pix) 46 | # if config.debug: 47 | # save_img = folder_path + '/config/image.jpg' 48 | # print("保存截图: {}".format(save_img)) 49 | # imwrite(save_img, image) 50 | 51 | except Exception: 52 | write_error(format_exc()) 53 | return image 54 | 55 | 56 | # 判断原文相似度 57 | def get_equal_rate(str1, str2): 58 | score = SequenceMatcher(None, str1, str2).quick_ratio() 59 | return score 60 | 61 | 62 | # 计算单通道的直方图的相似值 63 | def calculate(image1, image2): 64 | hist1 = calcHist([image1], [0], None, [256], [0.0, 255.0]) 65 | hist2 = calcHist([image2], [0], None, [256], [0.0, 255.0]) 66 | # 计算直方图的重合度 67 | degree = 0 68 | for i in range(len(hist1)): 69 | if hist1[i] != hist2[i]: 70 | degree = degree + (1 - abs(float(hist1[i]) - float(hist2[i])) / max(float(hist1[i]), float(hist2[i]))) 71 | else: 72 | degree = degree + 1 73 | degree = degree / float(len(hist1)) 74 | return degree 75 | 76 | 77 | # 判断图片相似度 78 | def compare_image(imageA, imageB): 79 | if imageA is None or imageB is None: 80 | return 0.2 81 | grayA = cvtColor(imageA, COLOR_BGR2GRAY) 82 | grayB = cvtColor(imageB, COLOR_BGR2GRAY) 83 | 84 | if grayA.shape != grayB.shape: 85 | new_shape = [(grayA.shape[0] + grayB.shape[0]) // 2, (grayA.shape[1] + grayB.shape[1]) // 2] 86 | grayA = resize(grayA, (new_shape[1], new_shape[0])) 87 | grayB = resize(grayB, (new_shape[1], new_shape[0])) 88 | else: 89 | if (imageA == imageB).all(): 90 | return 1. 91 | 92 | score = calculate(grayA, grayB) 93 | return score 94 | 95 | 96 | # 翻译主函数 97 | def translate(window, data, use_translate_signal): 98 | text = window.translateText.toPlainText() 99 | 100 | if window.load_local_img: 101 | score = 0.2 102 | else: 103 | if "欢迎~ 么么哒~" in text[:10] or (not text[:1]): 104 | score = 0.1 105 | window.image = image_cut(data) 106 | else: 107 | image_last = window.image 108 | window.image = image_cut(data) 109 | score = compare_image(image_last, window.image) 110 | 111 | if config.debug: 112 | print("图片相似性: {}, 设置的阈值: {}".format(score, config.similarity_score)) 113 | 114 | if score <= config.similarity_score: 115 | sign, original, result_with_location, translate_result = ocr(data, window.image) 116 | 117 | if config.debug: 118 | print("original:", original) 119 | 120 | signal_list = list() 121 | 122 | # 原文相似度 123 | str_score = get_equal_rate(original, window.original) 124 | 125 | if window.load_local_img and sign: 126 | window.load_local_img = False 127 | str_score = 0 128 | window.original = '' 129 | 130 | if sign and original and (original != window.original) and str_score < 0.95: 131 | 132 | window.original = original 133 | 134 | # 是否复制到剪贴板 135 | if data["showClipboard"] == 'True': 136 | copy(original) 137 | 138 | signal_list.append("original") 139 | 140 | # 保存原文 141 | content = "\n\n[原文]\n%s" % original 142 | with open(folder_path + "/config/识别结果.txt", "a+", encoding="utf-8") as file: 143 | file.write(content) 144 | if translate_result != '': 145 | file.write("\n[翻译]\n{}".format(translate_result)) 146 | 147 | use_translate_signal.emit(signal_list, original, data, result_with_location, translate_result) 148 | 149 | elif not sign: 150 | signal_list.append("error") 151 | use_translate_signal.emit(signal_list, original, data, result_with_location, translate_result) 152 | 153 | 154 | class TranslateThread(QThread): 155 | use_translate_signal = pyqtSignal(list, str, dict, list, str) 156 | 157 | def __init__(self, window, mode): 158 | 159 | self.window = window 160 | self.mode = mode 161 | super(TranslateThread, self).__init__() 162 | 163 | def run(self): 164 | 165 | with open(folder_path + '/config/settin.json') as file: 166 | data = load(file) 167 | 168 | if not self.mode: 169 | try: 170 | if self.window.thread_state == 0: 171 | translate(self.window, data, self.use_translate_signal) 172 | 173 | except Exception: 174 | write_error(format_exc()) 175 | else: 176 | data["sign"] += 1 177 | with open(folder_path + '/config/settin.json', 'w') as file: 178 | dump(data, file, indent=2) 179 | try: 180 | if data["sign"] % 2 == 0: 181 | self.window.StartButton.setIcon(qticon('fa.pause', color='white')) 182 | 183 | while True: 184 | s1 = time() 185 | with open(folder_path + '/config/settin.json') as file: 186 | data = load(file) 187 | 188 | if data["sign"] % 2 == 0: 189 | try: 190 | if self.window.thread_state == 0: 191 | s2 = time() 192 | if s2 - s1 < config.delay_time: 193 | sleep_time = config.delay_time - (s2 - s1) 194 | if config.debug: 195 | print("自动模式 限制帧率, sleep 时间: {}".format(sleep_time)) 196 | sleep(sleep_time) 197 | 198 | translate(self.window, data, self.use_translate_signal) 199 | 200 | except Exception: 201 | write_error(format_exc()) 202 | break 203 | else: 204 | self.window.StartButton.setIcon(qticon('fa.play', color='white')) 205 | break 206 | 207 | except Exception: 208 | write_error(format_exc()) 209 | -------------------------------------------------------------------------------- /src/vis_result.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from os.path import dirname 4 | from time import localtime, time, strftime 5 | from docx import Document 6 | import numpy as np 7 | from traceback import format_exc 8 | from cv2 import imread, cvtColor, COLOR_BGR2RGB, imwrite 9 | import sys 10 | 11 | from PyQt5.QtCore import Qt, pyqtSignal, QPointF, QRect 12 | from PyQt5.QtWidgets import QTextEdit, QPushButton, QFileDialog, QApplication, QWidget, QLabel 13 | from PyQt5.QtGui import QPixmap, QImage, QPalette, QBrush, QPainter, QPen, QColor, QPolygonF, QIcon 14 | 15 | sys.path.append(".") 16 | from configs import folder_path 17 | from src.api import write_error 18 | 19 | 20 | class VisResult(QWidget): 21 | result_signal = pyqtSignal(str, dict, str, str) 22 | 23 | def __init__(self, np_img, result, configs, translate_result, save_path): 24 | super(VisResult, self).__init__() 25 | self.setWindowState(Qt.WindowActive) 26 | # 窗口置顶 27 | self.setWindowFlags(Qt.WindowStaysOnTopHint) 28 | self.setWindowTitle("修改识别结果") 29 | # 窗口图标 30 | self.icon = QIcon() 31 | self.icon.addPixmap(QPixmap(folder_path + "/config/logo.ico"), QIcon.Normal, QIcon.On) 32 | self.setWindowIcon(self.icon) 33 | 34 | self.np_img = np_img 35 | self.results = result 36 | img = cvtColor(self.np_img, COLOR_BGR2RGB) 37 | img_h, img_w, img_c = img.shape 38 | self.img_w = img_w 39 | 40 | img_show_h, img_show_w = max(50, img_h), max(200, img_w) 41 | img_show = np.zeros((img_show_h * 3, img_show_w * 2, img_c), dtype=np.uint8) + 255 42 | img_show[:img_h, :img_w, :] = img 43 | img_show[0:img_h, img_w, :] = 128 44 | 45 | self.setMinimumHeight(img_show_h + 60) 46 | self.setMinimumWidth(img_show_w * 2) 47 | self.setMaximumHeight(img_show_h + 60) 48 | self.setMaximumWidth(img_show_w * 2) 49 | 50 | frame = QImage(img_show.data, img_show.shape[1], img_show.shape[0], img_show.shape[1] * img_show.shape[2], 51 | QImage.Format_RGB888) 52 | pix = QPixmap(frame) 53 | palette1 = QPalette() 54 | palette1.setBrush(self.backgroundRole(), QBrush(pix)) # 背景图片 55 | self.setPalette(palette1) 56 | self.setAutoFillBackground(False) 57 | self.draw_text(img_w) 58 | 59 | # 设置保存按钮 60 | self.SaveButton = QPushButton(self) 61 | self.SaveButton.setGeometry(QRect(img_show_w - 170, img_show_h + 20, 90, 30)) 62 | self.SaveButton.setStyleSheet("background: rgba(255, 255, 255, 0.4);font: 12pt;") 63 | self.SaveButton.setText("确 定") 64 | self.SaveButton.clicked.connect(self.send_text) 65 | 66 | # 设置导出按钮 67 | self.SaveButton = QPushButton(self) 68 | self.SaveButton.setGeometry(QRect(img_show_w - 45, img_show_h + 20, 90, 30)) 69 | self.SaveButton.setStyleSheet("background: rgba(255, 255, 255, 0.4);font: 12pt;") 70 | self.SaveButton.setText("导 出") 71 | self.SaveButton.clicked.connect(self.save_text) 72 | 73 | # 设置返回按钮 74 | self.CancelButton = QPushButton(self) 75 | self.CancelButton.setGeometry(QRect(img_show_w + 175 - 90, img_show_h + 20, 90, 30)) 76 | self.CancelButton.setStyleSheet("background: rgba(255, 255, 255, 0.4);font: 12pt") 77 | self.CancelButton.setText("取 消") 78 | self.CancelButton.clicked.connect(self.close) 79 | 80 | self.configs = configs 81 | self.translate_result = translate_result 82 | self.default_save_path = save_path 83 | 84 | # 绘制事件 85 | def paintEvent(self, event): 86 | painter = QPainter(self) 87 | 88 | # 在左边原图上画多边形 89 | pen = QPen(QColor(255, 0, 0)) # set lineColor 90 | pen.setWidth(1) # set lineWidth 91 | brush = QBrush(QColor(143, 143, 143, 100)) # set fillColor 92 | painter.setPen(pen) 93 | painter.setBrush(brush) 94 | self.draw_polygon(painter, 0) 95 | 96 | # 在右边画结果 97 | # pen = QPen(QColor(0, 0, 0)) # set lineColor 98 | # pen.setWidth(1) # set lineWidth 99 | # brush = QBrush(QColor(143, 143, 143, 0)) # set fillColor 100 | # painter.setPen(pen) 101 | # painter.setBrush(brush) 102 | # self.draw_polygon(painter, self.img_w) 103 | 104 | painter.end() 105 | 106 | def draw_polygon(self, qp, img_w): 107 | # 绘制多边形 108 | for res in self.results: 109 | text_region = res["text_region"] 110 | 111 | polygon = QPolygonF() 112 | for region in text_region: 113 | polygon.append(QPointF(img_w + region[0], region[1])) 114 | qp.drawPolygon(polygon) 115 | 116 | def draw_text(self, img_w): 117 | self.vis_text_result = [] 118 | for res in self.results: 119 | text = res['text'] 120 | text_region = res["text_region"] 121 | 122 | box_h = text_region[2][1] - text_region[0][1] 123 | box_w = text_region[2][0] - text_region[0][0] 124 | box_h = max(20, box_h) 125 | 126 | vis_text = QTextEdit(self) 127 | vis_text.setGeometry(QRect(img_w + text_region[0][0], text_region[0][1], box_w, box_h)) 128 | vis_text.setStyleSheet("QTextEdit {""border-width:1; border-style:outset""}" 129 | "QTextEdit:focus {""border: 2px dashed #9265d1;""}") 130 | vis_text.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 131 | vis_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 132 | vis_text.setPlainText(text) 133 | self.vis_text_result.append(vis_text) 134 | 135 | def send_text(self): 136 | sentence = [] 137 | for vis_text in self.vis_text_result: 138 | sentence.append(vis_text.toPlainText()) 139 | sentence = " ".join(sentence) 140 | self.result_signal.emit(sentence, self.configs, 'original', self.translate_result) 141 | self.close() 142 | 143 | def save_text(self): 144 | try: 145 | time_array = localtime(int(time())) 146 | str_date = strftime("DangoOCR_%Y-%m-%d_%H-%M-%S", time_array) 147 | default_name = self.default_save_path[0] + "/" + str_date 148 | save_p, extend = QFileDialog.getSaveFileName(self, 'save file', default_name, "txt (*.txt);;docx (*.docx)") 149 | 150 | if '.txt' in extend: 151 | if save_p[-4:] != '.txt': 152 | save_p += '.txt' 153 | imwrite(save_p[:-4] + ".jpg", self.np_img) 154 | with open(save_p, 'w', encoding='utf-8') as f: 155 | for vis_text in self.vis_text_result: 156 | f.write(vis_text.toPlainText() + "\n") 157 | elif '.docx' in extend: 158 | if save_p[-5:] != '.docx': 159 | save_p += '.docx' 160 | imwrite(save_p[:-5] + ".jpg", self.np_img) 161 | document = Document() 162 | for vis_text in self.vis_text_result: 163 | document.add_paragraph(vis_text.toPlainText()) 164 | document.save(save_p) 165 | if save_p != '': 166 | self.default_save_path[0] = dirname(save_p) 167 | except Exception: 168 | write_error(format_exc()) 169 | 170 | 171 | if __name__ == '__main__': 172 | img = imread(folder_path + '/config/image.jpg') 173 | print(folder_path) 174 | result = [{'text': '钢琴家傅聪确诊新冠系傅雷之子', 'confidence': 0.9817190170288086, 175 | 'text_region': [[4, 12], [205, 11], [205, 28], [4, 29]]}, 176 | {'text': '中英间定期客运航线航班暂停运行', 'confidence': 0.9990035891532898, 177 | 'text_region': [[5, 44], [216, 44], [216, 60], [5, 60]]}, 178 | {'text': '蚂蚁回应被约谈:成立整改工作组', 'confidence': 0.9838894605636597, 179 | 'text_region': [[3, 74], [207, 75], [207, 94], [3, 93]]}, 180 | {'text': '深圳新增1例无症状曾2次来京出差', 'confidence': 0.9958600997924805, 181 | 'text_region': [[5, 108], [221, 108], [221, 124], [5, 124]]}, 182 | {'text': '女童成老赖案:监控拍下其父藏户', 'confidence': 0.981416642665863, 183 | 'text_region': [[5, 141], [209, 141], [209, 156], [5, 156]]}, 184 | {'text': '北京3例确诊者曾在全聚德聚餐', 'confidence': 0.9947283864021301, 185 | 'text_region': [[4, 171], [197, 172], [197, 188], [4, 187]]}, 186 | {'text': '日本全面限制新入境不含中国大陆', 'confidence': 0.9981253743171692, 187 | 'text_region': [[5, 203], [221, 203], [221, 222], [5, 222]]}, 188 | {'text': '2020最特别的一件衣服', 'confidence': 0.9995226263999939, 189 | 'text_region': [[4, 235], [151, 235], [151, 254], [4, 254]]}, 190 | {'text': '央行副行长就约谈蚂蚁集团答记者问', 'confidence': 0.9819984436035156, 191 | 'text_region': [[5, 268], [232, 268], [232, 284], [5, 284]]}, 192 | {'text': '降温预报图冷到发紫', 'confidence': 0.9980568289756775, 193 | 'text_region': [[3, 298], [133, 298], [133, 317], [3, 317]]}] 194 | 195 | app = QApplication(sys.argv) 196 | win = VisResult(np_img=img, result=result, configs={}, translate_result='', save_path=[folder_path]) 197 | win.show() 198 | 199 | app.exec_() 200 | --------------------------------------------------------------------------------