├── .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 |
--------------------------------------------------------------------------------