├── .gitignore
├── LICENSE
├── README.md
├── app.py
├── autoupdate
├── logo.ico
├── update.py
└── 自动更新程序.exe
├── config
├── background
│ ├── register.gif
│ └── settin.jpg
├── icon
│ ├── loading.gif
│ └── slider.png
├── other
│ ├── NotoSansSC-Regular.otf
│ ├── finish.wav
│ ├── image.jpg
│ └── 华康方圆体W7.TTC
└── tools
│ ├── chromedriver.exe
│ ├── geckodriver.exe
│ └── msedgedriver.exe
├── requirements.txt
├── translator
├── __init__.py
├── all.py
├── api.py
├── huoshan.py
├── ocr
│ ├── __init__.py
│ ├── baidu.py
│ └── dango.py
├── public
│ ├── __init__.py
│ └── youdao.py
├── sound.py
├── update_chrome_driver.py
├── update_edge_driver.py
└── upload_firefox_driver.py
├── ui
├── __init__.py
├── aliyun.py
├── baidu.py
├── caiyun.py
├── chatgpt.py
├── desc.py
├── edit.py
├── filter.py
├── hotkey.py
├── huoshan.py
├── image.py
├── key.py
├── login.py
├── manga.py
├── progress_bar.py
├── range.py
├── register.py
├── settin.py
├── static
│ ├── __init__.py
│ ├── background.py
│ └── icon.py
├── switch.py
├── tencent.py
├── trans_history.py
├── translation.py
├── xiaoniu.py
└── youdao.py
└── utils
├── __init__.py
├── check_font.py
├── config.py
├── email.py
├── enctry.py
├── http.py
├── hwnd.py
├── logger.py
├── message.py
├── offline_ocr.py
├── port.py
├── range.py
├── screen_rate.py
├── sqlite.py
├── test.py
├── thread.py
├── translater.py
├── update.py
└── zip.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.vscode
3 | /.idea
4 | /logs
5 | /config/config.yaml
6 | /translator/logs/
7 | /config/image.jpg
8 | /config/new_image.jpg
9 | /config/翻译历史.txt
10 | /config/dango.lock
11 | /config/draw.jpg
12 | /翻译历史.txt
13 | /config/other/new_image.jpg
14 | /ocr/.paddleocr
15 | /config/other/image.jpg
16 | /config/tools/Driver_Notes/
17 | /ocr.zip
18 | /ocr
19 | chromedriver_win32.zip
20 | /config/cloud_config.json
21 | /config/other/button.wav
22 | /2.3.0.2
23 | /config/tools
24 | ocr.json
25 | ipt.json
26 | rdr.json
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 团子翻译器 - 基于OCR的生肉翻译软件
2 |
3 |
4 | [](https://github.com/PantsuDango/Dango-Translator)
5 | []()
6 | []()
7 | []()
8 | []()
9 | [](https://github.com/PantsuDango/ImageHub/blob/master/DangoTranslate/public/%E4%BD%9C%E8%80%85.png)
10 | [](https://github.com/PantsuDango/ImageHub/blob/master/DangoTranslate/public/qrcode_1740921893259.jpg)
11 |
12 |
13 | ## 简介
14 |
15 | 团子翻译器是一款生肉翻译软件,通过OCR识别屏幕特定范围内的文字,然后将识别到的文字调取各种翻译源,并实时输出翻译结果。
16 |
17 | + 搭载了离线OCR, 项目地址: [DangoOCR](https://github.com/PantsuDango/DangoOCR)
18 | + 搭载了在线OCR和漫画OCR, 官网地址: [星河云OCR](https://cloud.stariver.org.cn/auth/login.html)
19 | + 实现自动模式,实时识别区域内的文本并翻译
20 | + 配置了15种翻译源
21 | + 账号系统, 能够自动云端保存配置
22 | + 另有图片翻译功能, 实现对生肉漫画图片自动识别、翻译、消字、嵌字
23 |
24 |
25 |
26 | ## 使用教程
27 |
28 | [翻译器使用文档教程](https://dango-docs.ap-sh.starivercs.cn/#/5.0/basic/dangotranslator)
29 |
30 | ## 安装版下载
31 |
32 | - 群文件下载: [](https://github.com/PantsuDango/ImageHub/blob/master/DangoTranslate/public/qrcode_1740921893259.jpg)
33 | - 官网下载: [下载地址](https://translator.dango.cloud)
34 | - 网盘系在: [夸克网盘](https://pan.quark.cn/s/eb5663a0edf2)
35 | - Github Releases: [Ver6.0.1](https://nfd.ap-sh.starivercs.cn/ec/bc73f09f0474c59e5045125e3751fa071go)
36 |
37 | ## 更新日志
38 | []()
39 | []()
40 |
41 | #### 版本号:6.0.1
42 | #### 更新时间 2025/04/07
43 |
44 | #### 全局:
45 | + 修复了各种可能导致软件闪退的问题
46 | + 修复词库界面编辑文本的时候, 如果最小化了界面, 会导致编辑的文本消失的问题
47 | + 修复翻译历史界面, 如果最小化了界面, 再放大需要重新加载数据导致卡顿的问题
48 |
49 | #### 实时翻译:
50 | + 用户未配置就误打开chatgpt的开关, 导致整体翻译耗时过长, 现在加上了开启限制, 需要经过测试通过才可开启
51 |
52 | #### 漫画翻译:
53 | + 界面原本默认全屏, 现在支持自定义缩放尺寸
54 | + 轮廓颜色样式新增可设置为, 和当前字体色相反
55 | + 调整了界面上滚动条的颜色, 并加粗
56 | + 点击译图上的文本块时, 现在滚轮会自动滑到译文编辑的区域, 避免小屏幕用户需要频繁滑动滚轮
57 | + 手动擦除和笔刷填涂现在笔头改为了圆形
58 | + 进一步优化了手动擦除和笔刷填涂后的图片质量
59 | + 修复样式模版操作的时候, 消息提示窗上的模版名称错误的问题
60 | + 原图和译图下方的缩放比例处, 新增了一组比例的下拉框, 可以快速切换比例的
61 | + 高级设置-其他一栏里, 新增了翻译环节出错后可设置最大重试次数的功能
62 | + 优化了原图和译图, 拖拽移动图片的速度, 使其移动更平滑
63 | + 更多更新日志: [查看](https://dango-docs.ap-sh.starivercs.cn/#/5.0/develop/changelog)
64 |
65 |
66 | ## 原理说明
67 |
68 | ### 实时翻译
69 | 
70 | ### 图片翻译
71 | 
72 |
73 |
74 | ## 特别鸣谢
75 |
76 | [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR) 在线&本地OCR均基于此框架搭建
77 |
78 | [QPT打包工具](https://github.com/GT-ZhangAcer/QPT) 本地OCR基于此工具打包
79 |
80 | [GT-Zhang](https://github.com/GT-ZhangAcer) 在线&本地OCR开发过程给予了诸多帮助
81 |
82 | [C4a15Wh](https://c4a15wh.cn) 在线OCR, 星河云架构开发
83 |
84 | [Cypas_Nya](https://blog.ayano.top) 在线教程文档, 星河云开发
85 |
86 | [艾梦](https://github.com/HighCWu) 漫画翻译/在线OCR, 星河云模型开发
87 |
88 |
89 | ## 软件预览
90 |
91 | #### 使用效果
92 | 
93 | 
94 |
95 | #### 登录界面
96 |
97 | #### 主界面
98 |
99 |
100 | #### 漫画翻译
101 | 
102 | 
103 | 
104 |
105 | #### 设置界面
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | ## 开源协议
114 | 本项目使用GNU LESSER GENERAL PUBLIC LICENSE(LGPL)开源协议
115 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | from PyQt5.QtWidgets import *
2 | from PyQt5.QtCore import *
3 | from PyQt5.QtGui import *
4 | from traceback import format_exc
5 | import base64
6 | import sys
7 | import os
8 |
9 | import utils.logger
10 | import utils.config
11 | import utils.screen_rate
12 | import utils.check_font
13 | import utils.thread
14 | import utils.http
15 | import utils.email
16 | import utils.message
17 | import utils.port
18 | import utils.update
19 | import utils.hwnd
20 | import utils.sqlite
21 |
22 | import ui.login
23 | import ui.register
24 | import ui.translation
25 | import ui.filter
26 | import ui.range
27 | import ui.settin
28 | import ui.static.icon
29 | import ui.trans_history
30 | import ui.manga
31 |
32 | import translator.update_chrome_driver
33 | import translator.update_edge_driver
34 | import translator.upload_firefox_driver
35 |
36 |
37 | class DangoTranslator :
38 |
39 | # 配置初始化
40 | def __init__(self) :
41 |
42 | # 错误日志
43 | self.logger = utils.logger.setLog()
44 | # 本地配置
45 | self.yaml = utils.config.openConfig(self.logger)
46 | # 版本号
47 | self.yaml["version"] = "4.5.8"
48 | # 配置中心
49 | dict_info = utils.config.getDictInfo(self.yaml["dict_info_url"], self.logger)
50 | if dict_info :
51 | self.yaml["dict_info"] = dict_info
52 | # 屏幕分辨率
53 | self.yaml["screen_scale_rate"] = utils.screen_rate.getScreenRate(self.logger)
54 | # 保存配置
55 | utils.config.saveConfig(self.yaml, self.logger)
56 | # selenium引擎加载完成信号: 0-进行中, 1-成功, 2-失败
57 | self.chrome_driver_finish = 0
58 | self.firefox_driver_finish = 0
59 | self.edge_driver_finish = 0
60 | # 是否屏蔽绑定邮箱消息窗
61 | self.checkBindEmailSign = False
62 | # 记录截图坐标
63 | self.range = (0, 0, 0, 0)
64 | # 在线ocr可用性
65 | self.online_ocr_sign = False
66 | # 连接db
67 | utils.sqlite.connectTranslationDB(self.logger)
68 | # 同步旧翻译历史文件
69 | utils.thread.createThread(utils.sqlite.initTranslationDB, self)
70 |
71 |
72 | # 登录
73 | def login(self, auto_login=False) :
74 |
75 | # 是否为自动登录
76 | if auto_login :
77 | thread = utils.thread.createCheckAutoLoginQThread(self)
78 | thread.signal.connect(self.autoLoginCheck)
79 | utils.thread.runQThread(thread)
80 | else :
81 | if not self.login_ui.login() :
82 | return
83 |
84 | # 从本地获取配置信息
85 | self.config = utils.config.readCloudConfigFormLocal(self.logger)
86 | if not self.config :
87 | # 从云端获取配置信息
88 | self.config = utils.config.getDangoSettin(self)
89 | # 配置转换组包
90 | utils.config.configConvert(self)
91 | # 登录OCR服务获取token
92 | utils.thread.createThread(utils.http.loginDangoOCR, self)
93 |
94 | # 翻译界面
95 | self.translation_ui = ui.translation.Translation(self)
96 | if not auto_login:
97 | self.login_ui.close()
98 | self.translation_ui.show()
99 | # 设置界面
100 | self.settin_ui = ui.settin.Settin(self)
101 | # 翻译界面设置页面按键信号
102 | self.translation_ui.settin_button.clicked.connect(self.clickSettin)
103 | # 屏蔽词界面
104 | self.filter_ui = ui.filter.Filter(self)
105 | # 范围框界面
106 | self.range_ui = ui.range.Range(self)
107 | self.range_ui.show()
108 | # 多范围参数页面
109 | self.multi_range_ui = ui.range.MultiRange(self)
110 | self.translation_ui.multi_range_button.clicked.connect(self.clickMultiRange)
111 | # 翻译历史页面
112 | self.trans_history_ui = ui.trans_history.TransHistory(self)
113 | self.translation_ui.trans_history_button.clicked.connect(self.clickTransHistory)
114 | # 图片翻译页面
115 | self.manga_ui = ui.manga.Manga(self)
116 | self.translation_ui.manga_button.clicked.connect(self.clickManga)
117 |
118 | # 检查邮箱
119 | thread = utils.thread.createCheckBindEmailQThread(self)
120 | thread.signal.connect(self.register_ui.showBindEmailMessage)
121 | utils.thread.runQThread(thread)
122 | # 自动启动本地OCR
123 | utils.thread.createThread(self.autoOpenOfflineOCR)
124 | # 界面置顶
125 | self.hwndObj = utils.hwnd.WindowHwnd(self)
126 | if self.config["setTop"] :
127 | self.hwndObj.run()
128 | # 清理历史日志缓存
129 | utils.thread.createThread(utils.logger.clearLog)
130 | # 登录后自动打开图片翻译界面
131 | if self.yaml["auto_open_manga_use"] :
132 | self.clickManga()
133 |
134 |
135 | # 点击图片翻译键
136 | def clickManga(self) :
137 |
138 | self.translation_ui.hide()
139 | self.range_ui.hide()
140 | self.trans_history_ui.hide()
141 | self.manga_ui.show()
142 |
143 |
144 | # 点击翻译历史键
145 | def clickTransHistory(self) :
146 |
147 | self.trans_history_ui.show()
148 | self.range_ui.hide()
149 | self.translation_ui.hide()
150 |
151 |
152 | # 自动登录后检查
153 | def autoLoginCheck(self, message) :
154 |
155 | self.checkBindEmailSign = True
156 | utils.message.MessageBox("自动登录失败", message, self.yaml["screen_scale_rate"])
157 |
158 | # 如果自动登录失败就返回登录界面
159 | self.login_ui = ui.login.Login(self)
160 | self.login_ui.login_button.clicked.connect(self.login)
161 | # 登录界面注册按键
162 | self.login_ui.register_button.clicked.connect(self.register_ui.clickRegister)
163 | # 登录界面忘记密码按键
164 | self.login_ui.forget_password_button.clicked.connect(self.register_ui.clickForgetPassword)
165 | # 关闭已经打开的界面
166 | if self.translation_ui:
167 | self.translation_ui.close()
168 | if self.range_ui:
169 | self.range_ui.close()
170 | self.login_ui.show()
171 |
172 |
173 | # 按下多范围键后做的事情
174 | def clickMultiRange(self) :
175 |
176 | self.translation_ui.hide()
177 | self.multi_range_ui.show()
178 | self.range_ui.show()
179 |
180 |
181 | # 按下设置键后做的事情
182 | def clickSettin(self) :
183 |
184 | # 直接跳转到正在使用的ocr页签
185 | if self.settin_ui.online_ocr_use :
186 | self.settin_ui.ocr_tab_widget.setCurrentIndex(0)
187 | elif self.settin_ui.offline_ocr_use :
188 | self.settin_ui.ocr_tab_widget.setCurrentIndex(1)
189 | elif self.settin_ui.baidu_ocr_use :
190 | self.settin_ui.ocr_tab_widget.setCurrentIndex(2)
191 |
192 | self.translation_ui.close()
193 | self.range_ui.close()
194 | self.settin_ui.show()
195 |
196 |
197 | # 自动打开本地OCR
198 | def autoOpenOfflineOCR(self) :
199 |
200 | if not self.config["offlineOCR"] :
201 | return
202 | if not utils.port.detectPort(self.yaml["port"]) :
203 | try :
204 | # 启动本地OCR
205 | os.startfile(self.yaml["ocr_cmd_path"])
206 | except Exception :
207 | self.logger.error(format_exc())
208 |
209 |
210 | # 初始化资源
211 | def InitLoadFile(self) :
212 |
213 | # 更新谷歌浏览器引擎文件
214 | utils.thread.createThread(translator.update_chrome_driver.updateChromeDriver, self)
215 | # 更新Edge浏览器引擎文件
216 | utils.thread.createThread(translator.update_edge_driver.updateEdgeDriver, self)
217 | # 更新火狐浏览器引擎文件
218 | utils.thread.createThread(translator.upload_firefox_driver.updateFirefoxDriver, self)
219 |
220 | # 加载注册界面图片
221 | qq_group_url = self.yaml["dict_info"]["register_image_url"]
222 | utils.http.downloadFile(qq_group_url, "./config/background/register.gif", self.logger)
223 | # 加载设置界面图片
224 | settin_image_url = self.yaml["dict_info"]["settin_image_url"]
225 | utils.http.downloadFile(settin_image_url, "./config/background/settin.jpg", self.logger)
226 | # 加载测试ocr图片
227 | if not os.path.exists("./config/other/image.jpg") :
228 | test_image_url = self.yaml["dict_info"]["test_image"]
229 | utils.http.downloadFile(test_image_url, "./config/other/image.jpg", self.logger)
230 |
231 |
232 | # 启动图标
233 | def showSplash(self) :
234 |
235 | self.splash = QSplashScreen(ui.static.icon.APP_LOGO_SPLASH, Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
236 | self.splash.resize(int(250*self.yaml["screen_scale_rate"]), int(50*self.yaml["screen_scale_rate"]))
237 | self.splash.setStyleSheet("font: 15pt '华康方圆体W7';")
238 | self.splash.showMessage("团子翻译器启动中...", Qt.AlignVCenter | Qt.AlignRight)
239 | self.splash.show()
240 | QCoreApplication.processEvents()
241 |
242 |
243 | # 主函数
244 | def main(self) :
245 |
246 | # 自适应高分辨率
247 | QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
248 | app = QApplication(sys.argv)
249 | # 加载静态资源
250 | ui.static.icon.initIcon(self.yaml["screen_scale_rate"])
251 | # 连接配置中心
252 | if not self.yaml.get("dict_info", {}) :
253 | utils.message.serverClientFailMessage(self)
254 | # 更新缺失的运行库
255 | utils.thread.createThread(utils.update.updatePilFile(self))
256 | # 更新自动更新程序
257 | utils.thread.createThread(utils.update.updateAutoUpdateFile(self))
258 | # 更新图片翻译字体
259 | utils.thread.createThread(utils.update.updateManFontFile(self))
260 | # 启动图标
261 | self.showSplash()
262 | # 检查是否为测试版本
263 | utils.message.checkIsTestVersion(self)
264 | # 检查字体
265 | utils.check_font.checkFont(self.logger)
266 | # 检查版本更新线程
267 | if "Beta" not in self.yaml["version"] :
268 | thread = utils.thread.createCheckVersionQThread(self)
269 | thread.signal.connect(lambda: utils.message.showCheckVersionMessage(self))
270 | utils.thread.runQThread(thread)
271 | # 初始化图片资源
272 | utils.thread.createThread(self.InitLoadFile)
273 | # 注册页面
274 | self.register_ui = ui.register.Register(self)
275 |
276 | # 是否自动登录
277 | if not self.yaml["auto_login"] :
278 | # 登录界面
279 | self.login_ui = ui.login.Login(self)
280 | self.login_ui.login_button.clicked.connect(self.login)
281 |
282 | # 登录界面注册按键
283 | self.login_ui.register_button.clicked.connect(self.register_ui.clickRegister)
284 | # 登录界面忘记密码按键
285 | self.login_ui.forget_password_button.clicked.connect(self.register_ui.clickForgetPassword)
286 |
287 | self.login_ui.show()
288 | else :
289 | # 自动登录
290 | self.login(auto_login=True)
291 |
292 | self.splash.close()
293 | app.exit(app.exec_())
294 |
295 |
296 | if __name__ == "__main__" :
297 |
298 | app = DangoTranslator()
299 | app.main()
--------------------------------------------------------------------------------
/autoupdate/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/autoupdate/logo.ico
--------------------------------------------------------------------------------
/autoupdate/update.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 | import yaml
4 | import os
5 | import shutil
6 | import sys
7 |
8 | # 打开本地配置文件
9 | def openConfig() :
10 |
11 | try :
12 | with open("./app/config/config.yaml", "r", encoding="utf-8") as file :
13 | config = yaml.load(file.read(), Loader=yaml.FullLoader)
14 | except Exception as err :
15 | return None, "获取本地版本号失败: %s"%err
16 |
17 | local_version = config.get("version", "")
18 | print("当前版本号:", local_version)
19 |
20 | return local_version, None
21 |
22 |
23 | # 发送http请求
24 | def post(url, body, timeout=5) :
25 |
26 | proxies = {
27 | "http": None,
28 | "https": None
29 | }
30 |
31 | try :
32 | # 消除https警告
33 | requests.packages.urllib3.disable_warnings()
34 | except Exception :
35 | pass
36 |
37 | try :
38 | with requests.post(url, data=json.dumps(body), proxies=proxies, verify=False, timeout=timeout) as response :
39 | try :
40 | response.encoding = "utf-8"
41 | result = json.loads(response.text)
42 | except Exception :
43 | response.encoding = "gb18030"
44 | result = json.loads(response.text)
45 | except Exception as err :
46 | return None, err
47 |
48 | return result, None
49 |
50 |
51 | # 获取最新版本号
52 | def getVersion() :
53 |
54 | res, err = post("https://trans.dango.cloud/DangoTranslate/ShowDict", {})
55 | if not res :
56 | res, err = post("https://43.154.0.93/DangoTranslate/ShowDict", {})
57 | if not res :
58 | res, err = post("https://dango.c4a15wh.cn/DangoTranslate/ShowDict", {})
59 | if err :
60 | return None, None, "获取最新版本号失败: %s"%err
61 | latest_version = res.get("Result", {}).get("latest_version", "")
62 | update_version = res.get("Result", {}).get("update_version", "")
63 |
64 | print("最新版本号:", latest_version)
65 | print("最新版本下载地址:", update_version)
66 |
67 | return latest_version, update_version, None
68 |
69 |
70 | # 下载
71 | def progressbar(url):
72 |
73 | size = 0
74 | chunk_size = 1024
75 | try :
76 | response = requests.get(url, stream=True)
77 | content_size = int(response.headers["content-length"])
78 | if response.status_code == 200 :
79 | print("文件大小:{size:.2f} MB".format(size=content_size/chunk_size/1024))
80 | with open("temp.exe", "wb") as file :
81 | for data in response.iter_content(chunk_size=chunk_size) :
82 | file.write(data)
83 | size +=len(data)
84 | print("\r"+"[下载进度]:%s%.2f%%"%(">"*int(size*50/content_size), float(size/content_size * 100)) , end=" ")
85 | return None
86 | except Exception as err :
87 | try :
88 | os.remove("temp.exe")
89 | except Exception :
90 | pass
91 | return "下载失败: {}\n\n请尝试手动复制下载链接下载, 然后将'temp'文件名修改为'团子翻译器', 并复制替换{}/app目录内的'团子翻译器'即可".format(
92 | err, os.path.abspath('.'))
93 |
94 |
95 | def main() :
96 |
97 | print(">>> 获取版本信息...")
98 |
99 | # 获取本地版本号
100 | local_version, err = openConfig()
101 | if err :
102 | return print(err)
103 |
104 | # 获取最新版本号
105 | latest_version, update_version, err = getVersion()
106 | if err :
107 | return print(err)
108 | if local_version == latest_version :
109 | return print("当前已是最新版本, 您无需更新")
110 |
111 | # 下载最新版本
112 | print("\n>>> 开始下载最新版本...")
113 | err = progressbar(update_version)
114 | if err :
115 | return print(err)
116 |
117 | # TODO(团子): 替换文件失败往往是因为安装目录受到保护,第三方程序无法进行写入,可以考虑重新编译更新程序,并同样加上uac管理员权限
118 | # 替换版本文件
119 | print("\n>>> 替换新版本文件...")
120 | try :
121 | shutil.move("temp.exe", "./app/团子翻译器.exe")
122 | print("更新完成, 正在自动重启翻译器...")
123 | os.startfile("团子翻译器.exe")
124 | sys.exit()
125 | except Exception as err :
126 | return print(
127 | "替换新版本文件失败: {}\n\n请尝试手动进行替换, 新版本文件位于\n{}目录下,文件名为'temp',请将其文件名修改为'团子翻译器', 并复制替换{}/app内的'团子翻译器'即可".format(
128 | err, os.path.abspath('.'), os.path.abspath('.')))
129 |
130 |
131 | if __name__ == "__main__" :
132 |
133 | main()
134 | input("\n请按任意键退出...")
--------------------------------------------------------------------------------
/autoupdate/自动更新程序.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/autoupdate/自动更新程序.exe
--------------------------------------------------------------------------------
/config/background/register.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/config/background/register.gif
--------------------------------------------------------------------------------
/config/background/settin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/config/background/settin.jpg
--------------------------------------------------------------------------------
/config/icon/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/config/icon/loading.gif
--------------------------------------------------------------------------------
/config/icon/slider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/config/icon/slider.png
--------------------------------------------------------------------------------
/config/other/NotoSansSC-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/config/other/NotoSansSC-Regular.otf
--------------------------------------------------------------------------------
/config/other/finish.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/config/other/finish.wav
--------------------------------------------------------------------------------
/config/other/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/config/other/image.jpg
--------------------------------------------------------------------------------
/config/other/华康方圆体W7.TTC:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/config/other/华康方圆体W7.TTC
--------------------------------------------------------------------------------
/config/tools/chromedriver.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/config/tools/chromedriver.exe
--------------------------------------------------------------------------------
/config/tools/geckodriver.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/config/tools/geckodriver.exe
--------------------------------------------------------------------------------
/config/tools/msedgedriver.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/config/tools/msedgedriver.exe
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | selenium==3.141.0
2 | opencv_python==4.2.0.34
3 | pywin32==304
4 | QtAwesome==0.7.1
5 | scikit_image==0.15.0
6 | system_hotkey==1.0.3
7 | tencentcloud_sdk_python==3.0.170
8 | pyperclip==1.8.0
9 | requests==2.23.0
10 | Pillow==8.4.0
11 | PyQt5==5.15.6
12 | PyYAML==6.0
13 | skimage==0.0
14 | winreglib==0.1.0
--------------------------------------------------------------------------------
/translator/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/translator/__init__.py
--------------------------------------------------------------------------------
/translator/huoshan.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import hashlib
3 | import hmac
4 | import json
5 | from urllib.parse import quote
6 |
7 |
8 | def norm_query(params):
9 | query = ""
10 | for key in sorted(params.keys()):
11 | if type(params[key]) == list:
12 | for k in params[key]:
13 | query = (
14 | query + quote(key, safe="-_.~") + "=" + quote(k, safe="-_.~") + "&"
15 | )
16 | else:
17 | query = (query + quote(key, safe="-_.~") + "=" + quote(params[key], safe="-_.~") + "&")
18 | query = query[:-1]
19 | return query.replace("+", "%20")
20 |
21 |
22 | # sha256 非对称加密
23 | def hmac_sha256(key: bytes, content: str):
24 | return hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest()
25 |
26 |
27 | # sha256 hash算法
28 | def hash_sha256(content: str):
29 | return hashlib.sha256(content.encode("utf-8")).hexdigest()
30 |
31 |
32 | # 签名请求头构建
33 | def header(ak, sk, text):
34 |
35 | # 初始化身份证明结构体
36 | credential = {
37 | "access_key_id": ak,
38 | "secret_access_key": sk,
39 | "service": "translate",
40 | "region": "cn-north-1",
41 | }
42 | # 初始化签名结构体
43 | request_param = {
44 | "body": json.dumps({
45 | "TargetLanguage": "zh",
46 | 'TextList': text.split("\n"),
47 | }),
48 | "host": "translate.volcengineapi.com",
49 | "path": "/",
50 | "method": "POST",
51 | "content_type": "application/json",
52 | "date": datetime.datetime.utcnow(),
53 | "query": {"Action": "TranslateText", "Version": "2020-06-01"},
54 | }
55 | # 初始化签名结果的结构体
56 | x_date = request_param["date"].strftime("%Y%m%dT%H%M%SZ")
57 | short_x_date = x_date[:8]
58 | x_content_sha256 = hash_sha256(request_param["body"])
59 | sign_result = {
60 | "Host": request_param["host"],
61 | "X-Content-Sha256": x_content_sha256,
62 | "X-Date": x_date,
63 | "Content-Type": request_param["content_type"],
64 | }
65 | # 计算 Signature 签名。
66 | signed_headers_str = ";".join(
67 | ["content-type", "host", "x-content-sha256", "x-date"]
68 | )
69 | canonical_request_str = "\n".join(
70 | [request_param["method"].upper(),
71 | request_param["path"],
72 | norm_query(request_param["query"]),
73 | "\n".join(
74 | [
75 | "content-type:" + request_param["content_type"],
76 | "host:" + request_param["host"],
77 | "x-content-sha256:" + x_content_sha256,
78 | "x-date:" + x_date,
79 | ]
80 | ),
81 | "",
82 | signed_headers_str,
83 | x_content_sha256,
84 | ]
85 | )
86 | hashed_canonical_request = hash_sha256(canonical_request_str)
87 | credential_scope = "/".join([short_x_date, credential["region"], credential["service"], "request"])
88 | string_to_sign = "\n".join(["HMAC-SHA256", x_date, credential_scope, hashed_canonical_request])
89 |
90 | # 最终计算的签名字符串
91 | k_date = hmac_sha256(credential["secret_access_key"].encode("utf-8"), short_x_date)
92 | k_region = hmac_sha256(k_date, credential["region"])
93 | k_service = hmac_sha256(k_region, credential["service"])
94 | k_signing = hmac_sha256(k_service, "request")
95 | signature = hmac_sha256(k_signing, string_to_sign).hex()
96 |
97 | sign_result["Authorization"] = "HMAC-SHA256 Credential={}, SignedHeaders={}, Signature={}".format(
98 | credential["access_key_id"] + "/" + credential_scope,
99 | signed_headers_str,
100 | signature,
101 | )
102 |
103 | return sign_result
--------------------------------------------------------------------------------
/translator/ocr/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/translator/ocr/__init__.py
--------------------------------------------------------------------------------
/translator/ocr/baidu.py:
--------------------------------------------------------------------------------
1 | from traceback import format_exc
2 | import requests
3 | import base64
4 | import utils.message
5 | import utils.http
6 |
7 | IMAGE_PATH = "./config/image.jpg"
8 | TEST_IMAGE_PATH = "./config/other/image.jpg"
9 |
10 | # 获取访问百度OCR用的token
11 | def getAccessToken(object) :
12 |
13 | client_id = object.config["OCR"]["Key"]
14 | client_secret = object.config["OCR"]["Secret"]
15 |
16 | host = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s"%(client_id, client_secret)
17 | proxies = {"http": None, "https": None}
18 |
19 | try:
20 | response = requests.get(host, proxies=proxies, timeout=5)
21 |
22 | except TypeError :
23 | object.logger.error(format_exc())
24 | utils.message.MessageBox("百度OCR错误",
25 | "需要翻译器目录的路径设置为纯英文\n"
26 | "否则无法在非简中区的电脑系统下运行使用 ")
27 |
28 | except Exception :
29 | object.logger.error(format_exc())
30 | utils.message.MessageBox("百度OCR错误",
31 | "啊咧... 百度OCR连接失败惹 (つД`)\n"
32 | "你可能会无法使用百度OCR\n"
33 | "1. 可能开了代理或者加速器, 请尝试关闭它们\n"
34 | "2. 可能是校园网屏蔽或者是自身网络断开了\n"
35 | "3. 如都无法解决, 请更换使用团子本地或在线OCR ")
36 |
37 | else :
38 | try :
39 | response.encoding = "utf-8"
40 | result_json = response.json()
41 |
42 | access_token = result_json.get("access_token", "")
43 | if access_token :
44 | object.config["AccessToken"] = access_token
45 |
46 | else :
47 | error = response.json()["error"]
48 | error_description = response.json()["error_description"]
49 |
50 | if error_description == "unknown client id":
51 | utils.message.MessageBox("百度OCR错误",
52 | "你可能会无法使用百度OCR\n"
53 | "你的百度OCR API Key填错啦 ヽ(#`Д´)ノ ")
54 |
55 | elif error_description == "Client authentication failed":
56 | utils.message.MessageBox("百度OCR错误",
57 | "你可能会无法使用百度OCR\n"
58 | "你的百度OCR Secret Key填错啦 ヽ(#`Д´)ノ ")
59 |
60 | else:
61 | utils.message.MessageBox("百度OCR错误",
62 | "啊咧...OCR连接失败惹... (つД`)\n"
63 | "你可能会无法使用百度OCR\n"
64 | "error:%s\n"
65 | "error_description:%s "
66 | %(error, error_description))
67 |
68 | except Exception :
69 | object.logger.error(format_exc())
70 | utils.message.MessageBox("百度OCR错误",
71 | "出现了出乎意料的问题..."
72 | "你可能会无法使用百度OCR\n"
73 | "%s"%(format_exc()))
74 |
75 |
76 | # 百度ocr
77 | def baiduOCR(object, test=False) :
78 |
79 | # 获取配置
80 | language = object.config.get("language", "JAP")
81 | if language == "RU" :
82 | language = "RUS"
83 | access_token = object.config.get("AccessToken", "")
84 | show_translate_row = object.config.get("showTranslateRow", False)
85 | ocr_config = object.config.get("OCR", {})
86 | high_precision = ocr_config.get("highPrecision", False)
87 | branch_line_use = object.config.get("BranchLineUse", False)
88 |
89 | # 鉴权token
90 | if not access_token :
91 | return False, "百度OCR错误: 还未注册百度OCR密钥, 不可使用\n请于[设置]-[识别设定]-[百度OCR]页面内, 点击[注册]按钮完成注册并填入密钥后再使用"
92 | # 是否使用高精度模式
93 | if show_translate_row == True or high_precision :
94 | request_url = "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic"
95 | else :
96 | request_url = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic"
97 | # 是否为接口测试
98 | path = IMAGE_PATH
99 | if test :
100 | path = TEST_IMAGE_PATH
101 | language = "JAP"
102 |
103 | # 封装请求体
104 | with open(path, "rb") as file :
105 | image = base64.b64encode(file.read())
106 | params = {"image": image, "language_type": language}
107 | headers = {"content-type": "application/x-www-form-urlencoded", "Connection": "close"}
108 | proxies = {"http": None, "https": None}
109 | request_url = request_url + "?access_token=" + access_token
110 |
111 | # 请求百度ocr
112 | try:
113 | resp = requests.post(request_url, data=params, headers=headers, proxies=proxies, timeout=5).json()
114 | except Exception:
115 | object.logger.error(format_exc())
116 | return False, "百度OCR错误: 网络超时, 请尝试重试\n如有正在使用任何代理或加速器, 请尝试关闭后重试\n如果频繁出现, 建议切换其他OCR使用"
117 | if not resp :
118 | return False, "百度OCR错误: 网络超时, 请尝试重试\n如有正在使用任何代理或加速器, 请尝试关闭后重试\n如果频繁出现, 建议切换其他OCR使用"
119 |
120 | # 正常解析
121 | if "words_result" in resp:
122 | words = resp.get("words_result", [])
123 | content = ""
124 | if show_translate_row == True :
125 | # 竖向翻译模式
126 | if words:
127 | for word in words[::-1]:
128 | content += word["words"] + ","
129 | content = content.replace(",", "")
130 | else:
131 | # 横向翻译模式
132 | for index, word in enumerate(words):
133 | if branch_line_use and (index + 1 != len(words)):
134 | if language == "ENG":
135 | content += word["words"] + " \n"
136 | else:
137 | content += word["words"] + "\n"
138 | else:
139 | if language == "ENG":
140 | content += word["words"] + " "
141 | else:
142 | content += word["words"]
143 | return True, content
144 | # 错误解析
145 | else :
146 | # 如果接口调用错误
147 | error_code = resp.get("error_code", 0)
148 | error_msg = resp.get("error_msg", "")
149 |
150 | content = "百度OCR错误: 错误码-%d, 错误信息-%s\n"%(error_code, error_msg)
151 | if error_code == 6 :
152 | content += "开通的服务类型非通用文字识别, 请检查百度OCR的注册网页, 注册的类型是不是通用文字识别"
153 | elif error_code == 17 :
154 | if show_translate_row == True :
155 | content += "竖排翻译模式每日额度已用光, 请关闭竖排翻译模式, 或切换其他OCR使用竖排翻译"
156 | else:
157 | content += "无使用额度或免费额度已用光, 可更换本地OCR或在线OCR"
158 | elif error_code == 18 :
159 | content += "使用频率过快, 如为自动翻译可调整自动翻译时间间隔(建议1s), 手动翻译请降低使用频率"
160 |
161 | elif error_code == 111 :
162 | content += "缓存密钥已过期, 请进入设置页面一次, 以重新生成缓存密钥"
163 |
164 | elif error_code == 216202 :
165 | content += "识别范围过小无法识别, 请重新框选要翻译的区域"
166 |
167 | else:
168 | content += "错误未知或未收录"
169 | return False, content
--------------------------------------------------------------------------------
/translator/public/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/translator/public/__init__.py
--------------------------------------------------------------------------------
/translator/public/youdao.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import hashlib
3 | import time
4 | import random
5 |
6 | # 有道翻译
7 | class YDDict():
8 |
9 | # 获取加密参数
10 | @staticmethod
11 | def get_data(keyword):
12 | md = hashlib.md5()
13 | lts = str(int(time.time() * 1000))
14 | salt = lts + str(random.randrange(10))
15 | md.update("fanyideskweb{}{}Tbh5E8=q6U3EXe+&L[4c@".format(keyword, salt).encode("utf8"))
16 | sign = md.hexdigest()
17 | return lts, salt, sign
18 |
19 | # 翻译
20 | def translate(self, keyword):
21 | url = 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule'
22 | headers = {
23 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36",
24 | "Referer": "https://fanyi.youdao.com/",
25 | "Host": "fanyi.youdao.com",
26 | "Origin": "https://fanyi.youdao.com",
27 | "Cache-Control": "no-cache",
28 | "Connection": "keep-alive",
29 | }
30 | lts, salt, sign = self.get_data(keyword)
31 | data = {
32 | "i": keyword,
33 | "from": "AUTO",
34 | "to": "zh-CHS",
35 | "smartresult": "dict",
36 | "client": "fanyideskweb",
37 | "salt": salt,
38 | "sign": sign,
39 | "lts": lts,
40 | "bv": "1744f6d1b31aab2b4895998c6078a934",
41 | "doctype": "json",
42 | "version": "2.1",
43 | "keyfrom": "fanyi.web",
44 | "action": "FY_BY_REALTlME",
45 | }
46 |
47 | content = "公共有道: 我抽风啦, 请尝试重新翻译! 如果频繁出现, 建议直接注册使用私人翻译"
48 | try :
49 | resp = requests.post(url, headers=headers, data=data).json()
50 | error_code = resp["errorCode"]
51 | if error_code == 0 :
52 | trans = resp["translateResult"][0]
53 | content = ""
54 | for val in trans :
55 | content += val["tgt"]
56 |
57 | except Exception :
58 | pass
59 |
60 | return content
61 |
62 | if __name__ == "__main__" :
63 |
64 | jap = "に傍にいてくれるものではありません。ちょっとしたきっかけで、いなくなってしまうものです。"
65 | eng = "Time goes by so fast, people go in and out of your life. You must never miss the opportunity to tell these people how much they mean to you"
66 | kor = "어느 날 임금님께서 신하들 과 나라의 이곳저곳 을 시찰하게 됐습니다. 그러던 중 어느 시골의 넓은 들판에 이르렀는데, 그곳에는 많은 염소 떼가 있었습니다."
67 | rus = "Я понял, что и я получил подаяние от моего брата."
68 | youdao = YDDict()
69 | content = youdao.translate(eng)
70 | print(content)
--------------------------------------------------------------------------------
/translator/sound.py:
--------------------------------------------------------------------------------
1 | from selenium import webdriver
2 | from traceback import format_exc
3 | import utils.thread
4 | import time
5 | import base64
6 | import os
7 | import winsound
8 |
9 | FINISH_SOUND_FILE_PATH = "./config/other/finish.wav"
10 |
11 |
12 | # 音乐朗读模块
13 | class Sound() :
14 |
15 | def __init__(self, object) :
16 |
17 | self.object = object
18 | self.logger = object.logger
19 | self.content = ""
20 | self.url = "https://fanyi.qq.com/"
21 |
22 |
23 | # 开启引擎
24 | def openWebdriver(self):
25 |
26 | try:
27 | # 使用谷歌浏览器
28 | option = webdriver.ChromeOptions()
29 | option.add_argument("--headless")
30 | self.browser = webdriver.Chrome(executable_path="./config/tools/chromedriver.exe",
31 | service_log_path="nul",
32 | options=option)
33 | except Exception:
34 | self.logger.error(format_exc())
35 |
36 | try:
37 | # 使用火狐浏览器
38 | option = webdriver.FirefoxOptions()
39 | option.add_argument("--headless")
40 | self.browser = webdriver.Firefox(executable_path="./config/tools/geckodriver.exe",
41 | service_log_path="nul",
42 | options=option)
43 | except Exception:
44 | self.logger.error(format_exc())
45 |
46 | try:
47 | # 使用Edge浏览器
48 | EDGE = {
49 | "browserName": "MicrosoftEdge",
50 | "version": "",
51 | "platform": "WINDOWS",
52 | "ms:edgeOptions": {
53 | 'extensions': [],
54 | 'args': [
55 | '--headless',
56 | '--disable-gpu',
57 | '--remote-debugging-port=9222',
58 | ]}
59 | }
60 | self.browser = webdriver.Edge(executable_path="./config/tools/msedgedriver.exe",
61 | service_log_path="nul",
62 | capabilities=EDGE)
63 | except Exception:
64 | self.logger.error(format_exc())
65 | self.close()
66 |
67 | self.refreshWeb()
68 |
69 |
70 | # 刷新页面
71 | def refreshWeb(self) :
72 |
73 | self.content = ""
74 | try :
75 | self.browser.get(self.url)
76 | self.browser.maximize_window()
77 | self.transInit()
78 | except Exception :
79 | self.logger.error(format_exc())
80 | self.close()
81 |
82 |
83 | # 点击动作延时
84 | def browserClickTimeout(self, xpath, timeout=1):
85 |
86 | start = time.time()
87 | while True:
88 | try:
89 | self.browser.find_element_by_xpath(xpath).click()
90 | break
91 | except Exception:
92 | if time.time() - start > timeout:
93 | break
94 | time.sleep(0.1)
95 |
96 |
97 | # 翻译页面初始化
98 | def transInit(self) :
99 |
100 | try:
101 | self.browser.find_element_by_xpath(self.object.yaml["dict_info"]["tencent_xpath"]).click()
102 | except Exception:
103 | pass
104 | language = self.object.config["language"]
105 |
106 | self.browserClickTimeout('//*[@id="language-button-group-source"]/div[1]')
107 | if language == "JAP":
108 | self.browserClickTimeout('//*[@id="language-button-group-source"]/div[2]/ul/li[4]/span')
109 | elif language == "ENG":
110 | self.browserClickTimeout('//*[@id="language-button-group-source"]/div[2]/ul/li[3]/span')
111 | elif language == "KOR":
112 | self.browserClickTimeout('//*[@id="language-button-group-source"]/div[2]/ul/li[5]/span')
113 |
114 |
115 | # 播放音乐
116 | def playSound(self, content) :
117 |
118 | try :
119 | if content != self.content :
120 | # 清空文本框
121 | self.browserClickTimeout('/html/body/div[2]/div[2]/div[2]/div[1]/div[2]')
122 | # 输入要朗读的文本
123 | self.browser.find_element_by_xpath('/html/body/div[2]/div[2]/div[2]/div[1]/div[1]/textarea').send_keys(content)
124 | self.browser.find_element_by_xpath('//*[@id="language-button-group-translate"]/div').click()
125 | self.content = content
126 |
127 | # 判断是否已经开始朗读
128 | while True :
129 | start = time.time()
130 | # 点击朗读键
131 | self.browser.find_element_by_xpath('/html/body/div[2]/div[2]/div[2]/div[1]/div[3]').click()
132 | time.sleep(0.1)
133 | try :
134 | # 通过播放图标的css属性判断是否已经开始朗读
135 | self.browser.find_element_by_css_selector('body > div.layout-container > div.textpanel > div.textpanel-container.clearfix > div.textpanel-source.active > div.textpanel-tool.tool-voice.ani')
136 | break
137 | except Exception :
138 | # 设置如果5s都无法播放就超时
139 | now = time.time()
140 | if now - start >= 5 :
141 | return
142 |
143 | # 判断朗读是否结束
144 | while True :
145 | start = time.time()
146 | time.sleep(0.1)
147 | try :
148 | # 通过播放图标的css属性判断是否已经结束朗读
149 | self.browser.find_element_by_css_selector('body > div.layout-container > div.textpanel > div.textpanel-container.clearfix > div.textpanel-source.active > div.textpanel-tool.tool-voice.ani')
150 | # 这是如果60s都无法结束就超时
151 | now = time.time()
152 | if now - start >= 60 :
153 | return
154 | except Exception :
155 | break
156 |
157 | except Exception as err:
158 | if str(err) != "'Sound' object has no attribute 'browser'":
159 | self.logger.error(format_exc())
160 |
161 |
162 | def close(self) :
163 |
164 | try :
165 | self.browser.close()
166 | self.browser.quit()
167 | except Exception :
168 | self.logger.error(format_exc())
169 |
170 |
171 | # 播放系统提示音
172 | def playSystemSound() :
173 |
174 | try :
175 | if os.path.exists(FINISH_SOUND_FILE_PATH) :
176 | winsound.PlaySound(FINISH_SOUND_FILE_PATH, winsound.SND_FILENAME)
177 | else :
178 | winsound.PlaySound("*", winsound.SND_ALIAS)
179 | except Exception :
180 | pass
--------------------------------------------------------------------------------
/translator/update_chrome_driver.py:
--------------------------------------------------------------------------------
1 | from selenium import webdriver
2 | from difflib import SequenceMatcher
3 | from traceback import format_exc
4 | import re
5 | import zipfile
6 | import os
7 | import json
8 | import utils.http
9 |
10 | CHROMEDRIVER_PATH = "./config/tools/chromedriver.exe"
11 | DRIVER_DIR_PATH = "./config/tools"
12 | DRIVER_ZIP_NAME = "chromedriver_win32.zip"
13 |
14 | # 判断原文相似度
15 | def getEqualRate(str1, str2) :
16 |
17 | return SequenceMatcher(None, str1, str2).quick_ratio()*100
18 |
19 |
20 | # 获取浏览器版本号
21 | def checkChromeVersion(object) :
22 |
23 | option = webdriver.ChromeOptions()
24 | option.add_argument("--headless")
25 | try:
26 | driver = webdriver.Chrome(executable_path=CHROMEDRIVER_PATH,
27 | service_log_path="nul",
28 | options=option)
29 | driver.close()
30 | driver.quit()
31 | object.chrome_driver_finish = 1
32 | except Exception as err :
33 | regex = re.findall("Current browser version is (.+?) with binary", str(err))
34 | if regex :
35 | return regex[0]
36 | else :
37 | object.chrome_driver_finish = 2
38 |
39 |
40 | # 获取谷歌引擎文件下载信息
41 | def getChromeVersionInfo(chrome_version, logger) :
42 |
43 | # 获取所有引擎版本
44 | url = "https://registry.npmmirror.com/-/binary/chromedriver/"
45 | res = utils.http.get(url, logger)
46 | if not res :
47 | return
48 | try :
49 | res = json.loads(res)
50 | except Exception :
51 | logger.error(format_exc())
52 | return
53 |
54 | driver_version = ""
55 | max_score = 0
56 | for val in res :
57 | # 正则过滤无关的内容
58 | regex = re.findall("\d{2,3}\.0\.\d{4}\.\d{1,3}", val["name"])
59 | if not regex :
60 | continue
61 | # 文本对比找出最匹配的引擎版本
62 | score = getEqualRate(regex[0], chrome_version)
63 | if score > max_score :
64 | max_score = score
65 | driver_version = regex[0]
66 | elif score == max_score :
67 | if regex[0] > driver_version :
68 | driver_version = regex[0]
69 |
70 | if driver_version :
71 | return driver_version
72 |
73 |
74 | # 下载引擎文件
75 | def downloadDriver(driver_version, object) :
76 |
77 | url = "https://registry.npmmirror.com/-/binary/chromedriver/{}/chromedriver_win32.zip".format(driver_version)
78 | if not utils.http.downloadFile(url, DRIVER_ZIP_NAME, object.logger) :
79 | object.chrome_driver_finish = 2
80 | return
81 |
82 | # 解压压缩包
83 | try :
84 | zip_file = zipfile.ZipFile(DRIVER_ZIP_NAME)
85 | zip_list = zip_file.namelist()
86 | for f in zip_list :
87 | if f != "chromedriver.exe" :
88 | continue
89 | zip_file.extract(f, DRIVER_DIR_PATH)
90 | zip_file.close()
91 | # 删除压缩包
92 | os.remove(DRIVER_ZIP_NAME)
93 | object.chrome_driver_finish = 1
94 | except Exception :
95 | object.logger.error(format_exc())
96 | object.chrome_driver_finish = 2
97 |
98 |
99 | # 校验谷歌浏览器引擎文件
100 | def updateChromeDriver(object) :
101 |
102 | # 获取浏览器版本
103 | chrome_version = checkChromeVersion(object)
104 | if not chrome_version :
105 | return
106 |
107 | # 获取谷歌引擎文件下载信息
108 | driver_version = getChromeVersionInfo(chrome_version, object.logger)
109 | if not driver_version :
110 | object.chrome_driver_finish = 2
111 | return
112 |
113 | # 下载引擎文件
114 | downloadDriver(driver_version, object)
--------------------------------------------------------------------------------
/translator/update_edge_driver.py:
--------------------------------------------------------------------------------
1 | from selenium import webdriver
2 | from traceback import format_exc
3 | import re
4 | import zipfile
5 | import os
6 | import utils.http
7 |
8 | EDGE_DRIVER_PATH = "./config/tools/msedgedriver.exe"
9 | DRIVER_DIR_PATH = "./config/tools"
10 | DRIVER_ZIP_NAME = "edgedriver_win64.zip"
11 |
12 |
13 | # 获取浏览器版本号
14 | def checkEdgeVersion(object) :
15 |
16 | EDGE = {
17 | "browserName": "MicrosoftEdge",
18 | "platform": "WINDOWS",
19 | "ms:edgeOptions": {
20 | 'extensions': [],
21 | 'args': [
22 | '--headless',
23 | ]}
24 | }
25 | try:
26 | driver = webdriver.Edge(executable_path=EDGE_DRIVER_PATH,
27 | service_log_path="nul",
28 | capabilities=EDGE)
29 | driver.close()
30 | driver.quit()
31 | object.edge_driver_finish = 1
32 | except Exception as err :
33 | regex = re.findall("\d+\.\d+\.\d+\.\d+", str(err))
34 | if regex :
35 | return regex[0]
36 | else :
37 | object.edge_driver_finish = 2
38 |
39 |
40 | # 下载引擎文件
41 | def downloadDriver(driver_version, object) :
42 |
43 | url = "https://msedgedriver.azureedge.net/{}/edgedriver_win64.zip".format(driver_version)
44 | if not utils.http.downloadFile(url, DRIVER_ZIP_NAME, object.logger) :
45 | object.edge_driver_finish = 2
46 | return
47 |
48 | # 解压压缩包
49 | try :
50 | zip_file = zipfile.ZipFile(DRIVER_ZIP_NAME)
51 | zip_list = zip_file.namelist()
52 | for f in zip_list :
53 | if f != "msedgedriver.exe" :
54 | continue
55 | zip_file.extract(f, DRIVER_DIR_PATH)
56 | zip_file.close()
57 | # 删除压缩包
58 | os.remove(DRIVER_ZIP_NAME)
59 | object.edge_driver_finish = 1
60 | except Exception :
61 | object.logger.error(format_exc())
62 | object.edge_driver_finish = 2
63 |
64 |
65 | # 校验Edge浏览器引擎文件
66 | def updateEdgeDriver(object) :
67 |
68 | # 获取浏览器版本
69 | driver_version = checkEdgeVersion(object)
70 | if not driver_version :
71 | return
72 |
73 | # 下载引擎文件
74 | downloadDriver(driver_version, object)
--------------------------------------------------------------------------------
/translator/upload_firefox_driver.py:
--------------------------------------------------------------------------------
1 | from selenium import webdriver
2 | from traceback import format_exc, print_exc
3 | import re
4 | import zipfile
5 | import os
6 | import json
7 | import utils.http
8 |
9 | FIREFOX_DRIVER_PATH = "./config/tools/geckodriver.exe"
10 | DRIVER_DIR_PATH = "./config/tools"
11 | DRIVER_ZIP_NAME = "geckodriver-win64.zip"
12 |
13 |
14 | # 获取浏览器版本号
15 | def checkFirefoxVersion(object) :
16 |
17 | try:
18 | option = webdriver.FirefoxOptions()
19 | option.add_argument("--headless")
20 | driver = webdriver.Firefox(executable_path=FIREFOX_DRIVER_PATH,
21 | service_log_path="nul",
22 | options=option)
23 | driver.close()
24 | driver.quit()
25 | object.firefox_driver_finish = 1
26 | except Exception :
27 | if "newSession" in format_exc() :
28 | return True
29 | else :
30 | object.firefox_driver_finish = 2
31 |
32 |
33 | # 获取Firefox下载信息
34 | def getFirefoxVersionInfo(logger) :
35 |
36 | url = "https://liushilive.github.io/github_selenium_drivers/search_plus_index.json"
37 | res = utils.http.get(url, logger)
38 | if not res :
39 | return
40 | try:
41 | res = json.loads(res)
42 | text = res["md/Firefox.html"]["body"]
43 | except Exception:
44 | logger.error(format_exc())
45 | return
46 | regex = re.findall("geckodriver-v[\d.]+-win64.zip", text)
47 | if not regex :
48 | return
49 |
50 | return regex[0]
51 |
52 |
53 | # 下载浏览器引擎
54 | def downloadDriver(driver_version, object) :
55 |
56 | # 下载引擎文件
57 | url = "https://npm.taobao.org/mirrors/geckodriver/v0.30.0/%s"%driver_version
58 | if not utils.http.downloadFile(url, DRIVER_ZIP_NAME, object.logger) :
59 | object.firefox_driver_finish = 2
60 | return
61 |
62 | # 解压压缩包
63 | try:
64 | zip_file = zipfile.ZipFile(DRIVER_ZIP_NAME)
65 | zip_list = zip_file.namelist()
66 | for f in zip_list:
67 | if f != "geckodriver.exe":
68 | continue
69 | zip_file.extract(f, DRIVER_DIR_PATH)
70 | zip_file.close()
71 | # 删除压缩包
72 | os.remove(DRIVER_ZIP_NAME)
73 | object.firefox_driver_finish = 1
74 | except Exception :
75 | object.logger.error(format_exc())
76 | object.firefox_driver_finish = 2
77 |
78 |
79 | # 校验火狐浏览器引擎文件
80 | def updateFirefoxDriver(object) :
81 |
82 | # 校验浏览器版本
83 | sign = checkFirefoxVersion(object)
84 | if not sign :
85 | return
86 |
87 | # 获取Firefox引擎信息
88 | driver_version = getFirefoxVersionInfo(object.logger)
89 | if not driver_version :
90 | object.firefox_driver_finish = 2
91 | return
92 |
93 | # 下载浏览器引擎
94 | downloadDriver(driver_version, object)
--------------------------------------------------------------------------------
/ui/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/ui/__init__.py
--------------------------------------------------------------------------------
/ui/aliyun.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtWidgets import *
6 |
7 | import ui.static.icon
8 | import utils.test
9 | import webbrowser
10 |
11 |
12 | # 私人阿里云设置界面
13 | class AliyunSetting(QWidget) :
14 |
15 | def __init__(self, object) :
16 |
17 | super(AliyunSetting, self).__init__()
18 | self.object = object
19 | self.logger = object.logger
20 | self.getInitConfig()
21 | self.ui()
22 |
23 |
24 | def ui(self) :
25 |
26 | # 窗口尺寸及不可拉伸
27 | self.resize(self.window_width, self.window_height)
28 | self.setMinimumSize(QSize(self.window_width, self.window_height))
29 | self.setMaximumSize(QSize(self.window_width, self.window_height))
30 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
31 |
32 | # 窗口标题
33 | self.setWindowTitle("私人阿里云翻译设置 - 退出会自动保存")
34 | # 窗口图标
35 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
36 | # 鼠标样式
37 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
38 | # 界面样式
39 | self.setStyleSheet("QWidget { font: 9pt '华康方圆体W7';"
40 | "color: %s;"
41 | "background: rgba(255, 255, 255, 1); }"
42 | "QLineEdit { background: transparent;"
43 | "border-width:0;"
44 | "border-style:outset;"
45 | "border-bottom: 2px solid %s; }"
46 | "QLineEdit:focus { border-bottom: 2px "
47 | "dashed %s; }"
48 | "QLabel {color: %s;}"
49 | "QPushButton { background: %s; border-radius: %spx; color: rgb(255, 255, 255); }"
50 | "QPushButton:hover { background-color: #83AAF9; }"
51 | "QPushButton:pressed { background-color: #4480F9; padding-left: 3px; padding-top: 3px; }"
52 | %(self.color, self.color, self.color, self.color, self.color, 6.66*self.rate))
53 |
54 | # secret_id 输入框
55 | label = QLabel(self)
56 | self.customSetGeometry(label, 20, 20, 330, 20)
57 | label.setText("SecretId: ")
58 | self.secret_id_text = QLineEdit(self)
59 | self.customSetGeometry(self.secret_id_text, 20, 40, 330, 25)
60 | self.secret_id_text.setText(self.object.config["aliyunAPI"]["Key"])
61 | self.secret_id_text.setCursor(ui.static.icon.EDIT_CURSOR)
62 |
63 | # secret_key 输入框
64 | label = QLabel(self)
65 | self.customSetGeometry(label, 20, 80, 330, 20)
66 | label.setText("SecretKey: ")
67 | self.secret_key_text = QLineEdit(self)
68 | self.customSetGeometry(self.secret_key_text, 20, 100, 330, 25)
69 | self.secret_key_text.setText(self.object.config["aliyunAPI"]["Secret"])
70 | self.secret_key_text.setCursor(ui.static.icon.EDIT_CURSOR)
71 |
72 | # 测试按钮
73 | button = QPushButton(self)
74 | self.customSetGeometry(button, 65, 150, 60, 20)
75 | button.setText("测试")
76 | button.clicked.connect(lambda: utils.test.testAliyun(
77 | self.object, self.filterNullWord(self.secret_id_text), self.filterNullWord(self.secret_key_text)))
78 | button.setCursor(ui.static.icon.SELECT_CURSOR)
79 |
80 | # 注册按钮
81 | button = QPushButton(self)
82 | self.customSetGeometry(button, 155, 150, 60, 20)
83 | button.setText("注册")
84 | button.clicked.connect(self.openTutorial)
85 | button.setCursor(ui.static.icon.SELECT_CURSOR)
86 |
87 | # 查额度按钮
88 | button = QPushButton(self)
89 | self.customSetGeometry(button, 245, 150, 60, 20)
90 | button.setText("查额度")
91 | button.clicked.connect(self.openQueryQuota)
92 | button.setCursor(ui.static.icon.SELECT_CURSOR)
93 |
94 |
95 | # 初始化配置
96 | def getInitConfig(self) :
97 |
98 | # 界面缩放比例
99 | self.rate = self.object.yaml["screen_scale_rate"]
100 | # 界面尺寸
101 | self.window_width = int(370 * self.rate)
102 | self.window_height = int(200 * self.rate)
103 | # 颜色
104 | self.color = "#5B8FF9"
105 |
106 |
107 | # 根据分辨率定义控件位置尺寸
108 | def customSetGeometry(self, object, x, y, w, h) :
109 |
110 | object.setGeometry(QRect(int(x * self.rate),
111 | int(y * self.rate), int(w * self.rate),
112 | int(h * self.rate)))
113 |
114 |
115 | # 过滤空字符
116 | def filterNullWord(self, obj) :
117 |
118 | text = obj.text().strip()
119 | obj.setText(text)
120 | return text
121 |
122 |
123 | # 打开注册教程
124 | def openTutorial(self) :
125 |
126 | try :
127 | url = self.object.yaml["dict_info"]["aliyun_tutorial"]
128 | webbrowser.open(url, new=0, autoraise=True)
129 | except Exception :
130 | self.logger.error(format_exc())
131 | utils.message.MessageBox("私人阿里云注册",
132 | "请尝试手动打开此地址:\n%s " % url)
133 |
134 |
135 | # 打开查询额度地址
136 | def openQueryQuota(self):
137 |
138 | url = "https://usercenter2.aliyun.com/finance/expense-report/expense-detail"
139 | try:
140 | webbrowser.open(url, new=0, autoraise=True)
141 | except Exception :
142 | self.logger.error(format_exc())
143 | utils.message.MessageBox("私人阿里云额度查询",
144 | "打开地址失败, 请尝试手动打开此网页下载\n%s " % url)
145 |
146 |
147 | # 窗口关闭处理
148 | def closeEvent(self, event) :
149 |
150 | self.object.config["aliyunAPI"]["Key"] = self.filterNullWord(self.secret_id_text)
151 | self.object.config["aliyunAPI"]["Secret"] = self.filterNullWord(self.secret_key_text)
--------------------------------------------------------------------------------
/ui/baidu.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtWidgets import *
6 |
7 | import ui.static.icon
8 | import utils.test
9 | import webbrowser
10 |
11 |
12 | # 私人百度设置界面
13 | class BaiduSetting(QWidget) :
14 |
15 | def __init__(self, object) :
16 |
17 | super(BaiduSetting, self).__init__()
18 | self.object = object
19 | self.logger = object.logger
20 | self.getInitConfig()
21 | self.ui()
22 |
23 |
24 | def ui(self) :
25 |
26 | # 窗口尺寸及不可拉伸
27 | self.resize(self.window_width, self.window_height)
28 | self.setMinimumSize(QSize(self.window_width, self.window_height))
29 | self.setMaximumSize(QSize(self.window_width, self.window_height))
30 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
31 |
32 | # 窗口标题
33 | self.setWindowTitle("私人百度翻译设置 - 退出会自动保存")
34 | # 窗口图标
35 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
36 | # 鼠标样式
37 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
38 | # 界面样式
39 | self.setStyleSheet("QWidget { font: 9pt '华康方圆体W7';"
40 | "color: %s;"
41 | "background: rgba(255, 255, 255, 1); }"
42 | "QLineEdit { background: transparent;"
43 | "border-width:0;"
44 | "border-style:outset;"
45 | "border-bottom: 2px solid %s; }"
46 | "QLineEdit:focus { border-bottom: 2px "
47 | "dashed %s; }"
48 | "QLabel {color: %s;}"
49 | "QPushButton { background: %s; border-radius: %spx; color: rgb(255, 255, 255); }"
50 | "QPushButton:hover { background-color: #83AAF9; }"
51 | "QPushButton:pressed { background-color: #4480F9; padding-left: 3px; padding-top: 3px; }"
52 | %(self.color, self.color, self.color, self.color, self.color, 6.66*self.rate))
53 |
54 | # secret_id 输入框
55 | label = QLabel(self)
56 | self.customSetGeometry(label, 20, 20, 330, 20)
57 | label.setText("APP ID: ")
58 | self.secret_id_text = QLineEdit(self)
59 | self.customSetGeometry(self.secret_id_text, 20, 40, 330, 25)
60 | self.secret_id_text.setText(self.object.config["baiduAPI"]["Key"])
61 | self.secret_id_text.setCursor(ui.static.icon.EDIT_CURSOR)
62 |
63 | # secret_key 输入框
64 | label = QLabel(self)
65 | self.customSetGeometry(label, 20, 80, 330, 20)
66 | label.setText("密钥: ")
67 | self.secret_key_text = QLineEdit(self)
68 | self.customSetGeometry(self.secret_key_text, 20, 100, 330, 25)
69 | self.secret_key_text.setText(self.object.config["baiduAPI"]["Secret"])
70 | self.secret_key_text.setCursor(ui.static.icon.EDIT_CURSOR)
71 |
72 | # 测试按钮
73 | button = QPushButton(self)
74 | self.customSetGeometry(button, 65, 150, 60, 20)
75 | button.setText("测试")
76 | button.clicked.connect(lambda: utils.test.testBaidu(
77 | self.object, self.filterNullWord(self.secret_id_text), self.filterNullWord(self.secret_key_text)))
78 | button.setCursor(ui.static.icon.SELECT_CURSOR)
79 |
80 | # 注册按钮
81 | button = QPushButton(self)
82 | self.customSetGeometry(button, 155, 150, 60, 20)
83 | button.setText("注册")
84 | button.clicked.connect(self.openTutorial)
85 | button.setCursor(ui.static.icon.SELECT_CURSOR)
86 |
87 | # 查额度按钮
88 | button = QPushButton(self)
89 | self.customSetGeometry(button, 245, 150, 60, 20)
90 | button.setText("查额度")
91 | button.clicked.connect(self.openQueryQuota)
92 | button.setCursor(ui.static.icon.SELECT_CURSOR)
93 |
94 |
95 | # 初始化配置
96 | def getInitConfig(self) :
97 |
98 | # 界面缩放比例
99 | self.rate = self.object.yaml["screen_scale_rate"]
100 | # 界面尺寸
101 | self.window_width = int(370 * self.rate)
102 | self.window_height = int(200 * self.rate)
103 | # 颜色
104 | self.color = "#5B8FF9"
105 |
106 |
107 | # 根据分辨率定义控件位置尺寸
108 | def customSetGeometry(self, object, x, y, w, h) :
109 |
110 | object.setGeometry(QRect(int(x * self.rate),
111 | int(y * self.rate), int(w * self.rate),
112 | int(h * self.rate)))
113 |
114 |
115 | # 过滤空字符
116 | def filterNullWord(self, obj) :
117 |
118 | text = obj.text().strip()
119 | obj.setText(text)
120 | return text
121 |
122 |
123 | # 打开注册教程
124 | def openTutorial(self) :
125 |
126 | try :
127 | url = self.object.yaml["dict_info"]["baidu_tutorial"]
128 | webbrowser.open(url, new=0, autoraise=True)
129 | except Exception :
130 | self.logger.error(format_exc())
131 | utils.message.MessageBox("私人百度注册",
132 | "请尝试手动打开此地址:\n%s " % url)
133 |
134 |
135 | # 打开查询额度地址
136 | def openQueryQuota(self):
137 |
138 | url = "https://fanyi-api.baidu.com/api/trans/product/desktop"
139 | try:
140 | webbrowser.open(url, new=0, autoraise=True)
141 | except Exception :
142 | self.logger.error(format_exc())
143 | utils.message.MessageBox("私人百度额度查询",
144 | "打开地址失败, 请尝试手动打开此网页下载\n%s " % url)
145 |
146 |
147 | # 窗口关闭处理
148 | def closeEvent(self, event) :
149 |
150 | self.object.config["baiduAPI"]["Key"] = self.filterNullWord(self.secret_id_text)
151 | self.object.config["baiduAPI"]["Secret"] = self.filterNullWord(self.secret_key_text)
--------------------------------------------------------------------------------
/ui/caiyun.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtWidgets import *
6 |
7 | import ui.static.icon
8 | import utils.test
9 | import webbrowser
10 |
11 |
12 | # 私人彩云设置界面
13 | class CaiyunSetting(QWidget) :
14 |
15 | def __init__(self, object) :
16 |
17 | super(CaiyunSetting, self).__init__()
18 | self.object = object
19 | self.logger = object.logger
20 | self.getInitConfig()
21 | self.ui()
22 |
23 |
24 | def ui(self) :
25 |
26 | # 窗口尺寸及不可拉伸
27 | self.resize(self.window_width, self.window_height)
28 | self.setMinimumSize(QSize(self.window_width, self.window_height))
29 | self.setMaximumSize(QSize(self.window_width, self.window_height))
30 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
31 |
32 | # 窗口标题
33 | self.setWindowTitle("私人彩云翻译设置 - 退出会自动保存")
34 | # 窗口图标
35 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
36 | # 鼠标样式
37 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
38 | # 界面样式
39 | self.setStyleSheet("QWidget { font: 9pt '华康方圆体W7';"
40 | "color: %s;"
41 | "background: rgba(255, 255, 255, 1); }"
42 | "QLineEdit { background: transparent;"
43 | "border-width:0;"
44 | "border-style:outset;"
45 | "border-bottom: 2px solid %s; }"
46 | "QLineEdit:focus { border-bottom: 2px "
47 | "dashed %s; }"
48 | "QLabel {color: %s;}"
49 | "QPushButton { background: %s; border-radius: %spx; color: rgb(255, 255, 255); }"
50 | "QPushButton:hover { background-color: #83AAF9; }"
51 | "QPushButton:pressed { background-color: #4480F9; padding-left: 3px; padding-top: 3px; }"
52 | %(self.color, self.color, self.color, self.color, self.color, 6.66*self.rate))
53 |
54 | # secret_id 输入框
55 | label = QLabel(self)
56 | self.customSetGeometry(label, 20, 20, 330, 20)
57 | label.setText("令牌: ")
58 | self.secret_key_text = QLineEdit(self)
59 | self.customSetGeometry(self.secret_key_text, 20, 40, 330, 25)
60 | self.secret_key_text.setText(self.object.config["caiyunAPI"])
61 | self.secret_key_text.setCursor(ui.static.icon.EDIT_CURSOR)
62 |
63 | # 测试按钮
64 | button = QPushButton(self)
65 | self.customSetGeometry(button, 65, 90, 60, 20)
66 | button.setText("测试")
67 | button.clicked.connect(lambda: utils.test.testCaiyun(
68 | self.object, self.filterNullWord(self.secret_key_text)))
69 | button.setCursor(ui.static.icon.SELECT_CURSOR)
70 |
71 | # 注册按钮
72 | button = QPushButton(self)
73 | self.customSetGeometry(button, 155, 90, 60, 20)
74 | button.setText("注册")
75 | button.clicked.connect(self.openTutorial)
76 | button.setCursor(ui.static.icon.SELECT_CURSOR)
77 |
78 | # 查额度按钮
79 | button = QPushButton(self)
80 | self.customSetGeometry(button, 245, 90, 60, 20)
81 | button.setText("查额度")
82 | button.clicked.connect(self.openQueryQuota)
83 | button.setCursor(ui.static.icon.SELECT_CURSOR)
84 |
85 |
86 | # 初始化配置
87 | def getInitConfig(self) :
88 |
89 | # 界面缩放比例
90 | self.rate = self.object.yaml["screen_scale_rate"]
91 | # 界面尺寸
92 | self.window_width = int(370 * self.rate)
93 | self.window_height = int(140 * self.rate)
94 | # 颜色
95 | self.color = "#5B8FF9"
96 |
97 |
98 | # 根据分辨率定义控件位置尺寸
99 | def customSetGeometry(self, object, x, y, w, h) :
100 |
101 | object.setGeometry(QRect(int(x * self.rate),
102 | int(y * self.rate), int(w * self.rate),
103 | int(h * self.rate)))
104 |
105 |
106 | # 过滤空字符
107 | def filterNullWord(self, obj) :
108 |
109 | text = obj.text().strip()
110 | obj.setText(text)
111 | return text
112 |
113 |
114 | # 打开注册教程
115 | def openTutorial(self) :
116 |
117 | try :
118 | url = self.object.yaml["dict_info"]["caiyun_tutorial"]
119 | webbrowser.open(url, new=0, autoraise=True)
120 | except Exception :
121 | self.logger.error(format_exc())
122 | utils.message.MessageBox("私人彩云注册",
123 | "请尝试手动打开此地址:\n%s " % url)
124 |
125 |
126 | # 打开查询额度地址
127 | def openQueryQuota(self):
128 |
129 | url = "https://dashboard.caiyunapp.com/v1/token/5e818320d4b84b00d29a9316/?type=2"
130 | try:
131 | webbrowser.open(url, new=0, autoraise=True)
132 | except Exception :
133 | self.logger.error(format_exc())
134 | utils.message.MessageBox("私人彩云额度查询",
135 | "打开地址失败, 请尝试手动打开此网页下载\n%s " % url)
136 |
137 |
138 | # 窗口关闭处理
139 | def closeEvent(self, event) :
140 |
141 | self.object.config["caiyunAPI"] = self.filterNullWord(self.secret_key_text)
--------------------------------------------------------------------------------
/ui/desc.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtWidgets import *
6 | import ui.static.icon
7 | import ui.manga
8 |
9 |
10 | # 说明界面
11 | class Desc(QWidget) :
12 |
13 | def __init__(self, object) :
14 |
15 | super(Desc, self).__init__()
16 |
17 | self.object = object
18 | self.getInitConfig()
19 | self.ui()
20 |
21 |
22 | def ui(self) :
23 |
24 | # 窗口尺寸及不可拉伸
25 | self.resize(self.window_width, self.window_height)
26 | self.setMinimumSize(QSize(self.window_width, self.window_height))
27 | self.setMaximumSize(QSize(self.window_width, self.window_height))
28 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
29 |
30 | # 窗口标题
31 | self.setWindowTitle("说明")
32 | # 窗口图标
33 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
34 | # 鼠标样式
35 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
36 | # 设置字体
37 | self.setStyleSheet("font: %spt '%s';"%(self.font_size, self.font_type))
38 |
39 | # 说明框
40 | self.desc_text = QTextBrowser(self)
41 | self.desc_text.setGeometry(QRect(0, 0, self.window_width, self.window_height))
42 | self.desc_text.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
43 | self.desc_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
44 | self.desc_text.setStyleSheet("QTextBrowser { border-width: 0;"
45 | "border-style: outset;"
46 | "border-top:0px solid #e8f3f9;"
47 | "color: #5B8FF9;"
48 | "background: rgba(255, 255, 255, 0.7); }")
49 | self.desc_text.setCursor(ui.static.icon.PIXMAP_CURSOR)
50 |
51 | # 加载背景图
52 | pixmap = ui.static.icon.MANGA_SETTING_PIXMAP.scaled(self.width(), self.height(),
53 | Qt.KeepAspectRatio, Qt.SmoothTransformation)
54 | # 样式设定页面背景
55 | label = QLabel(self)
56 | label.setAlignment(Qt.AlignCenter)
57 | label.setGeometry(QRect(0, 0, self.window_width, self.window_height))
58 | label.setPixmap(pixmap)
59 | label.lower()
60 |
61 |
62 | # 初始化配置
63 | def getInitConfig(self):
64 |
65 | # 界面缩放比例
66 | self.rate = self.object.yaml["screen_scale_rate"]
67 | # 界面字体
68 | self.font_type = "华康方圆体W7"
69 | # 界面字体大小
70 | self.font_size = 10
71 | # 界面尺寸
72 | self.window_width = int(250 * self.rate)
73 | self.window_height = int(300 * self.rate)
74 |
75 |
76 | # 根据分辨率定义控件位置尺寸
77 | def customSetGeometry(self, object, x, y, w, h):
78 |
79 | object.setGeometry(QRect(int(x * self.rate),
80 | int(y * self.rate), int(w * self.rate),
81 | int(h * self.rate)))
82 |
83 |
84 | # 添加消息文本
85 | def appendDescText(self, text) :
86 |
87 | self.desc_text.append(text)
--------------------------------------------------------------------------------
/ui/edit.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtWidgets import *
6 | import ui.static.icon
7 | import utils.translater
8 |
9 |
10 | # 说明界面
11 | class Edit(QWidget) :
12 |
13 | def __init__(self, object) :
14 |
15 | super(Edit, self).__init__()
16 |
17 | self.object = object
18 | self.getInitConfig()
19 | self.ui()
20 |
21 |
22 | def ui(self) :
23 |
24 | # 窗口尺寸及不可拉伸
25 | self.resize(self.window_width, self.window_height)
26 | self.setMinimumSize(QSize(self.window_width, self.window_height))
27 | self.setMaximumSize(QSize(self.window_width, self.window_height))
28 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
29 |
30 | # 窗口标题
31 | self.setWindowTitle("修改原文并重新翻译")
32 | # 窗口图标
33 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
34 | # 鼠标样式
35 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
36 | # 设置字体
37 | self.setStyleSheet("font: %spt '%s';"%(self.font_size, self.font_type))
38 |
39 | # 编辑框
40 | self.edit_text = QTextBrowser(self)
41 | self.customSetGeometry(self.edit_text, 0, 0, 500, 230)
42 | #self.edit_text.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
43 | #self.edit_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
44 | self.edit_text.setStyleSheet("QTextBrowser { border-width: 0;"
45 | "border-style: outset;"
46 | "border-top:0px solid #e8f3f9;"
47 | "color: #5B8FF9;"
48 | "background: rgba(255, 255, 255, 0.7); }")
49 | self.edit_text.setCursor(ui.static.icon.PIXMAP_CURSOR)
50 | self.edit_text.setReadOnly(False)
51 |
52 | # 重新翻译按钮
53 | button = QPushButton(self)
54 | self.customSetGeometry(button, 200, 240, 100, 50)
55 | button.setText("重新翻译")
56 | button.clicked.connect(self.flushTranslate)
57 | button.setCursor(ui.static.icon.SELECT_CURSOR)
58 |
59 |
60 | # 初始化配置
61 | def getInitConfig(self):
62 |
63 | # 界面缩放比例
64 | self.rate = self.object.yaml["screen_scale_rate"]
65 | # 界面字体
66 | self.font_type = "华康方圆体W7"
67 | # 界面字体大小
68 | self.font_size = 15
69 | # 界面尺寸
70 | self.window_width = int(500 * self.rate)
71 | self.window_height = int(300 * self.rate)
72 |
73 |
74 | # 根据分辨率定义控件位置尺寸
75 | def customSetGeometry(self, object, x, y, w, h):
76 |
77 | object.setGeometry(QRect(int(x * self.rate),
78 | int(y * self.rate), int(w * self.rate),
79 | int(h * self.rate)))
80 |
81 |
82 | # 刷新翻译
83 | def flushTranslate(self) :
84 |
85 | thread = utils.translater.Translater(self.object)
86 | thread.clear_text_sign.connect(self.object.translation_ui.clearText)
87 | thread.hide_range_ui_sign.connect(self.object.range_ui.hideUI)
88 | thread.flushTranslate(self.edit_text.toPlainText())
--------------------------------------------------------------------------------
/ui/filter.py:
--------------------------------------------------------------------------------
1 | from PyQt5.QtCore import *
2 | from PyQt5.QtGui import *
3 | from PyQt5.QtWidgets import *
4 |
5 | import utils.thread
6 | import ui.static.icon
7 | import ui.static.background
8 |
9 |
10 | # 屏蔽词界面
11 | class Filter(QWidget) :
12 |
13 | def __init__(self, object) :
14 |
15 | super(Filter, self).__init__()
16 | self.object = object
17 | self.getInitConfig()
18 | self.ui()
19 | utils.thread.createThread(self.refreshTable)
20 |
21 |
22 | def ui(self) :
23 |
24 | self.resize(self.window_width, self.window_height)
25 | self.setMinimumSize(QSize(self.window_width, self.window_height))
26 | self.setMaximumSize(QSize(self.window_width, self.window_height))
27 | self.setWindowFlags(Qt.WindowStaysOnTopHint)
28 | self.setWindowTitle("屏蔽词设置")
29 | # 窗口图标
30 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
31 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
32 | # 设置字体
33 | font = QFont()
34 | font.setFamily(self.font_type)
35 | font.setPointSize(self.font_size)
36 | self.setFont(font)
37 |
38 | # 背景, 长宽比1.424
39 | label = QLabel(self)
40 | self.customSetGeometry(label, 0, 0, 230, 300)
41 | pixmap = ui.static.icon.MANGA_SETTING_PIXMAP.scaled(self.width(), self.height(),
42 | Qt.KeepAspectRatio, Qt.SmoothTransformation)
43 | label.setPixmap(pixmap)
44 |
45 | # 表格
46 | self.table_widget = QTableWidget(self)
47 | self.customSetGeometry(self.table_widget, 0, 0, 230, 270)
48 | self.table_widget.setColumnCount(2)
49 | # 表格无边框
50 | self.table_widget.setFrameShape(QFrame.Box)
51 | # 表头信息
52 | self.table_widget.setHorizontalHeaderLabels(["屏蔽词", "替换词"])
53 | # 不显示行号
54 | self.table_widget.verticalHeader().setVisible(False)
55 | # 表头不塌陷
56 | self.table_widget.horizontalHeader().setHighlightSections(False)
57 | # 表头属性
58 | self.table_widget.horizontalHeader().setStyleSheet("QHeaderView::section {background: transparent;"
59 | "font: %spt %s; }"%(self.font_size, self.font_type))
60 | # 表格铺满窗口
61 | self.table_widget.horizontalHeader().setStretchLastSection(True)
62 | self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
63 | # 禁用水平滚动条
64 | self.table_widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
65 | # 表格行和列的大小设为与内容相匹配
66 | self.table_widget.resizeColumnsToContents()
67 | self.table_widget.resizeRowsToContents()
68 | self.table_widget.setStyleSheet("background-color:rgba(255, 255, 255, 0.3);")
69 | # 信号
70 | self.table_widget.itemChanged.connect(self.ifItemChanged)
71 | self.table_widget.setCursor(ui.static.icon.EDIT_CURSOR)
72 |
73 | # 添加屏蔽词按钮
74 | self.add_button = QPushButton(self)
75 | self.customSetGeometry(self.add_button, 0, 270, 115, 30)
76 | self.add_button.setStyleSheet("background: rgba(255, 255, 255, 0.4);")
77 | self.add_button.setText("添加")
78 | self.add_button.clicked.connect(self.addFilter)
79 | self.add_button.setCursor(ui.static.icon.SELECT_CURSOR)
80 |
81 | # 删除屏蔽词按钮
82 | self.delete_button = QPushButton(self)
83 | self.customSetGeometry(self.delete_button, 115, 270, 115, 30)
84 | self.delete_button.setStyleSheet("background: rgba(255, 255, 255, 0.4);")
85 | self.delete_button.setText("删除")
86 | self.delete_button.clicked.connect(self.deleteFilter)
87 | self.delete_button.setCursor(ui.static.icon.SELECT_CURSOR)
88 |
89 |
90 | # 初始化配置
91 | def getInitConfig(self):
92 |
93 | # 界面缩放比例
94 | self.rate = self.object.yaml["screen_scale_rate"]
95 | # 界面尺寸
96 | self.window_width = int(230 * self.rate)
97 | self.window_height = int(300 * self.rate)
98 | # 界面字体
99 | self.font_type = "华康方圆体W7"
100 | # 界面字体大小
101 | self.font_size = 10
102 |
103 |
104 | # 根据分辨率定义控件位置尺寸
105 | def customSetGeometry(self, object, x, y, w, h):
106 |
107 | object.setGeometry(QRect(int(x * self.rate),
108 | int(y * self.rate), int(w * self.rate),
109 | int(h * self.rate)))
110 |
111 |
112 | # 添加一行
113 | def addFilter(self) :
114 |
115 | row = self.table_widget.rowCount()
116 | self.table_widget.insertRow(row)
117 |
118 | for x in range(2) :
119 | item = QTableWidgetItem()
120 | item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
121 | self.table_widget.setItem(row, x, item)
122 |
123 | self.updateTable()
124 |
125 |
126 | # 删除一行
127 | def deleteFilter(self) :
128 |
129 | row = self.table_widget.currentRow()
130 | self.table_widget.removeRow(row)
131 |
132 | self.updateTable()
133 |
134 |
135 | # 将屏蔽词更新保存设置
136 | def updateTable(self) :
137 |
138 | row = self.table_widget.rowCount()
139 | col = self.table_widget.columnCount()
140 |
141 | table = []
142 | for i in range(row):
143 | val = []
144 | for j in range(col):
145 | try :
146 | val.append(self.table_widget.item(i, j).text())
147 | except Exception:
148 | pass
149 | table.append(val)
150 |
151 | self.object.config["Filter"] = table
152 |
153 |
154 | # 当表格的数据发生改变时
155 | def ifItemChanged(self) :
156 |
157 | self.table_widget.viewport().update()
158 | # 如果数据没变就跳过
159 | select_item = self.table_widget.selectedItems()
160 | if len(select_item) == 0 :
161 | return
162 |
163 | self.updateTable()
164 |
165 |
166 | # 更新表格信息
167 | def refreshTable(self) :
168 |
169 | data = self.object.config.get("Filter", [])
170 | self.table_widget.setRowCount(len(data))
171 |
172 | for i in range(len(data)) :
173 | for j in range(2):
174 | item = QTableWidgetItem("%s" % data[i][j])
175 | item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
176 | self.table_widget.setItem(i, j, item)
177 |
178 |
179 | # 窗口关闭处理
180 | def closeEvent(self, event) :
181 |
182 | self.object.translation_ui.show()
--------------------------------------------------------------------------------
/ui/hotkey.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtWidgets import *
6 |
7 | import utils.message
8 | import utils.thread
9 | import ui.static.icon
10 |
11 |
12 | # 快捷键界面
13 | class HotKey(QWidget):
14 |
15 | def __init__(self, object):
16 |
17 | super(HotKey, self).__init__()
18 | self.object = object
19 | self.getInitConfig()
20 | self.ui()
21 |
22 | def ui(self):
23 |
24 | # 窗口尺寸及不可拉伸
25 | self.resize(self.window_width, self.window_height)
26 | self.setMinimumSize(QSize(self.window_width, self.window_height))
27 | self.setMaximumSize(QSize(self.window_width, self.window_height))
28 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
29 | # 窗口图标
30 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
31 | # 鼠标样式
32 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
33 | # 界面样式
34 | self.setStyleSheet("QWidget { font: 12pt '华康方圆体W7'; "
35 | "background: rgb(255, 255, 255); "
36 | "color: #5B8FF9; }"
37 | "QPushButton { background: %s;"
38 | "border-radius: %spx;"
39 | "color: rgb(255, 255, 255); }"
40 | "QPushButton:hover { background-color: #83AAF9; }"
41 | "QPushButton:pressed { background-color: #4480F9;"
42 | "padding-left:3px;"
43 | "padding-top:3px; }"
44 | % (self.color_2, 6.66*self.rate))
45 |
46 | self.default_label = QLabel(self)
47 | self.customSetGeometry(self.default_label, 30, 10, 300, 50)
48 | self.default_label.setText("不支持单键\n"
49 | "仅支持 ctrl/shift/win/alt + 任意键\n"
50 | "示例 ctrl+z / alt+f1")
51 | self.default_label.setStyleSheet("font: 10pt '华康方圆体W7';")
52 |
53 | # 键位一
54 | comboBox_list_1 = ["ctrl", "win", "alt", "shift"]
55 | self.comboBox_1 = QComboBox(self)
56 | self.customSetGeometry(self.comboBox_1, 30, 80, 100, 20)
57 | for index, val in enumerate(comboBox_list_1) :
58 | self.comboBox_1.addItem("")
59 | self.comboBox_1.setItemText(index, val)
60 | self.comboBox_1.setStyleSheet("background: rgba(255, 255, 255, 1);")
61 | self.comboBox_1.setCursor(ui.static.icon.EDIT_CURSOR)
62 |
63 | label = QLabel(self)
64 | self.customSetGeometry(label, 145, 80, 50, 20)
65 | label.setText("+")
66 |
67 | self.choice_range_label = QLabel(self)
68 | self.customSetGeometry(self.choice_range_label, 170, 80, 100, 20)
69 | self.choice_range_label.setText("f1-f4")
70 | self.choice_range_label.hide()
71 |
72 | # 键位二
73 | comboBox_list_2 = ["ctrl", "win", "alt", "shift"]
74 | comboBox_list_2 += [chr(ch) for ch in range(97, 123)]
75 | comboBox_list_2 += [chr(ch) for ch in range(48, 58)]
76 | comboBox_list_2 += ["f"+str(ch) for ch in range(0, 10)]
77 | self.comboBox_2 = QComboBox(self)
78 | self.customSetGeometry(self.comboBox_2, 170, 80, 100, 20)
79 | for index, val in enumerate(comboBox_list_2):
80 | self.comboBox_2.addItem("")
81 | self.comboBox_2.setItemText(index, val)
82 | self.comboBox_2.setStyleSheet("background: rgba(255, 255, 255, 1);")
83 | self.comboBox_2.setCursor(ui.static.icon.EDIT_CURSOR)
84 |
85 | # 确定按钮
86 | self.sure_button = QPushButton(self)
87 | self.customSetGeometry(self.sure_button, 130, 160, 70, 25)
88 | self.sure_button.setText("确定")
89 | self.sure_button.setCursor(ui.static.icon.SELECT_CURSOR)
90 |
91 | # 取消按钮
92 | button = QPushButton(self)
93 | self.customSetGeometry(button, 210, 160, 70, 25)
94 | button.setText("取消")
95 | button.setCursor(ui.static.icon.SELECT_CURSOR)
96 | button.clicked.connect(self.close)
97 |
98 | # 初始化配置
99 | def getInitConfig(self):
100 |
101 | # 界面缩放比例
102 | self.rate = self.object.yaml["screen_scale_rate"]
103 | # 界面尺寸
104 | self.window_width = int(300 * self.rate)
105 | self.window_height = int(200 * self.rate)
106 | # 所使用的颜色
107 | self.color_1 = "#595959" # 灰色
108 | self.color_2 = "#5B8FF9" # 蓝色
109 | # 快捷键映射关系
110 | self.hotkey_map = {
111 | "ctrl": "control",
112 | "win": "super"
113 | }
114 |
115 |
116 | # 根据分辨率定义控件位置尺寸
117 | def customSetGeometry(self, object, x, y, w, h):
118 |
119 | object.setGeometry(QRect(int(x * self.rate),
120 | int(y * self.rate), int(w * self.rate),
121 | int(h * self.rate)))
122 |
123 |
124 | # 按下确定键
125 | def sure(self, key_type) :
126 |
127 | if key_type != "choiceRange" :
128 | if self.comboBox_1.currentText() == self.comboBox_2.currentText() :
129 | utils.message.MessageBox("这是来自团子的警告~",
130 | "键位一和键位二不可重复ヽ(・ω・´メ) ")
131 | return
132 |
133 | content = self.comboBox_1.currentText() + "+" + self.comboBox_2.currentText()
134 |
135 | # 翻译快捷键改变键位
136 | if key_type == "translate" :
137 | # 注销旧快捷键
138 | if self.object.config["showHotKey1"] == True :
139 | self.object.translation_ui.unRegisterTranslateHotkey()
140 | # 改变快捷键键位
141 | self.object.settin_ui.translate_hotkey_button.setText(content)
142 | self.object.config["translateHotkeyValue1"] = self.comboBox_1.currentText()
143 | self.object.config["translateHotkeyValue2"] = self.comboBox_2.currentText()
144 | # 注册新快捷键
145 | if self.object.config["showHotKey1"] == True :
146 | self.object.translation_ui.registerTranslateHotkey()
147 |
148 | # 范围快捷键改变键位
149 | elif key_type == "range" :
150 | # 注销旧快捷键
151 | if self.object.config["showHotKey2"] == True :
152 | self.object.translation_ui.unRegisterRangeHotkey()
153 | self.object.settin_ui.range_hotkey_button.setText(content)
154 | self.object.config["rangeHotkeyValue1"] = self.comboBox_1.currentText()
155 | self.object.config["rangeHotkeyValue2"] = self.comboBox_2.currentText()
156 | # 注册新快捷键
157 | if self.object.config["showHotKey2"] == True :
158 | self.object.translation_ui.registerRangeHotkey()
159 |
160 | # 隐藏范围快捷键改变键位
161 | elif key_type == "hideRange" :
162 | # 注销旧快捷键
163 | if self.object.config["showHotKey3"] == True or self.object.config["showHotKey3"] == True :
164 | self.object.translation_ui.unRegisterHideRangeHotkey()
165 | self.object.settin_ui.hide_range_hotkey_button.setText(content)
166 | self.object.config["hideRangeHotkeyValue1"] = self.comboBox_1.currentText()
167 | self.object.config["hideRangeHotkeyValue2"] = self.comboBox_2.currentText()
168 | # 注册新快捷键
169 | if self.object.config["showHotKey3"] == True or self.object.config["showHotKey3"] == True :
170 | self.object.translation_ui.registerHideRangeHotkey()
171 |
172 | # 切换范围快捷键改变键位
173 | elif key_type == "choiceRange" :
174 | # 注销旧快捷键
175 | if self.object.config["choiceRangeHotKeyUse"] == True:
176 | self.object.multi_range_ui.UnRegisterChoiceRangeHotkey()
177 | self.object.multi_range_ui.choice_range_hotkey_button.setText(self.comboBox_1.currentText() + " + " + "f1-f4")
178 | self.object.config["choiceRangeHotkeyValue"] = self.comboBox_1.currentText()
179 | if self.object.config["choiceRangeHotKeyUse"] == True :
180 | self.object.multi_range_ui.RegisterChoiceRangeHotkey()
181 |
182 | self.close()
--------------------------------------------------------------------------------
/ui/huoshan.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtWidgets import *
6 |
7 | import ui.static.icon
8 | import utils.test
9 | import webbrowser
10 |
11 |
12 | # 私人火山设置界面
13 | class HuoshanSetting(QWidget) :
14 |
15 | def __init__(self, object) :
16 |
17 | super(HuoshanSetting, self).__init__()
18 | self.object = object
19 | self.logger = object.logger
20 | self.getInitConfig()
21 | self.ui()
22 |
23 |
24 | def ui(self) :
25 |
26 | # 窗口尺寸及不可拉伸
27 | self.resize(self.window_width, self.window_height)
28 | self.setMinimumSize(QSize(self.window_width, self.window_height))
29 | self.setMaximumSize(QSize(self.window_width, self.window_height))
30 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
31 |
32 | # 窗口标题
33 | self.setWindowTitle("私人火山翻译设置 - 退出会自动保存")
34 | # 窗口图标
35 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
36 | # 鼠标样式
37 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
38 | # 界面样式
39 | self.setStyleSheet("QWidget { font: 9pt '华康方圆体W7';"
40 | "color: %s;"
41 | "background: rgba(255, 255, 255, 1); }"
42 | "QLineEdit { background: transparent;"
43 | "border-width:0;"
44 | "border-style:outset;"
45 | "border-bottom: 2px solid %s; }"
46 | "QLineEdit:focus { border-bottom: 2px "
47 | "dashed %s; }"
48 | "QLabel {color: %s;}"
49 | "QPushButton { background: %s; border-radius: %spx; color: rgb(255, 255, 255); }"
50 | "QPushButton:hover { background-color: #83AAF9; }"
51 | "QPushButton:pressed { background-color: #4480F9; padding-left: 3px; padding-top: 3px; }"
52 | %(self.color, self.color, self.color, self.color, self.color, 6.66*self.rate))
53 |
54 | # secret_id 输入框
55 | label = QLabel(self)
56 | self.customSetGeometry(label, 20, 20, 330, 20)
57 | label.setText("Access Key ID: ")
58 | self.secret_id_text = QLineEdit(self)
59 | self.customSetGeometry(self.secret_id_text, 20, 40, 330, 25)
60 | self.secret_id_text.setText(self.object.config["huoshanAPI"]["Key"])
61 | self.secret_id_text.setCursor(ui.static.icon.EDIT_CURSOR)
62 |
63 | # secret_key 输入框
64 | label = QLabel(self)
65 | self.customSetGeometry(label, 20, 80, 330, 20)
66 | label.setText("Secret Access Key: ")
67 | self.secret_key_text = QLineEdit(self)
68 | self.customSetGeometry(self.secret_key_text, 20, 100, 330, 25)
69 | self.secret_key_text.setText(self.object.config["huoshanAPI"]["Secret"])
70 | self.secret_key_text.setCursor(ui.static.icon.EDIT_CURSOR)
71 |
72 | # 测试按钮
73 | button = QPushButton(self)
74 | self.customSetGeometry(button, 65, 150, 60, 20)
75 | button.setText("测试")
76 | button.clicked.connect(lambda: utils.test.testHuoshan(
77 | self.object, self.filterNullWord(self.secret_id_text), self.filterNullWord(self.secret_key_text)))
78 | button.setCursor(ui.static.icon.SELECT_CURSOR)
79 |
80 | # 注册按钮
81 | button = QPushButton(self)
82 | self.customSetGeometry(button, 155, 150, 60, 20)
83 | button.setText("注册")
84 | button.clicked.connect(self.openTutorial)
85 | button.setCursor(ui.static.icon.SELECT_CURSOR)
86 |
87 | # 查额度按钮
88 | button = QPushButton(self)
89 | self.customSetGeometry(button, 245, 150, 60, 20)
90 | button.setText("查额度")
91 | button.clicked.connect(self.openQueryQuota)
92 | button.setCursor(ui.static.icon.SELECT_CURSOR)
93 |
94 |
95 | # 初始化配置
96 | def getInitConfig(self) :
97 |
98 | # 界面缩放比例
99 | self.rate = self.object.yaml["screen_scale_rate"]
100 | # 界面尺寸
101 | self.window_width = int(370 * self.rate)
102 | self.window_height = int(200 * self.rate)
103 | # 颜色
104 | self.color = "#5B8FF9"
105 |
106 |
107 | # 根据分辨率定义控件位置尺寸
108 | def customSetGeometry(self, object, x, y, w, h) :
109 |
110 | object.setGeometry(QRect(int(x * self.rate),
111 | int(y * self.rate), int(w * self.rate),
112 | int(h * self.rate)))
113 |
114 |
115 | # 过滤空字符
116 | def filterNullWord(self, obj) :
117 |
118 | text = obj.text().strip()
119 | obj.setText(text)
120 | return text
121 |
122 |
123 | # 打开注册教程
124 | def openTutorial(self) :
125 |
126 | try :
127 | url = self.object.yaml["dict_info"]["huoshan_tutorial"]
128 | webbrowser.open(url, new=0, autoraise=True)
129 | except Exception :
130 | self.logger.error(format_exc())
131 | utils.message.MessageBox("私人火山注册",
132 | "请尝试手动打开此地址:\n%s " % url)
133 |
134 |
135 | # 打开查询额度地址
136 | def openQueryQuota(self):
137 |
138 | url = "https://console.volcengine.com/translate/usage"
139 | try:
140 | webbrowser.open(url, new=0, autoraise=True)
141 | except Exception :
142 | self.logger.error(format_exc())
143 | utils.message.MessageBox("私人火山额度查询",
144 | "打开地址失败, 请尝试手动打开此网页下载\n%s " % url)
145 |
146 |
147 | # 窗口关闭处理
148 | def closeEvent(self, event) :
149 |
150 | self.object.config["huoshanAPI"]["Key"] = self.filterNullWord(self.secret_id_text)
151 | self.object.config["huoshanAPI"]["Secret"] = self.filterNullWord(self.secret_key_text)
--------------------------------------------------------------------------------
/ui/key.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtWidgets import *
6 |
7 | import ui.static.icon
8 |
9 |
10 | # 密钥界面
11 | class Key(QWidget) :
12 |
13 | def __init__(self, object) :
14 |
15 | super(Key, self).__init__()
16 | self.object = object
17 | self.getInitConfig()
18 | self.ui()
19 |
20 |
21 | def ui(self) :
22 |
23 | # 窗口尺寸及不可拉伸
24 | self.resize(self.window_width, self.window_height)
25 | self.setMinimumSize(QSize(self.window_width, self.window_height))
26 | self.setMaximumSize(QSize(self.window_width, self.window_height))
27 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
28 |
29 | # 窗口标题
30 | self.setWindowTitle("说明")
31 | # 窗口图标
32 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
33 | # 鼠标样式
34 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
35 | # 界面样式
36 | self.setStyleSheet("QWidget { font: 9pt '华康方圆体W7';"
37 | "color: %s;"
38 | "background: rgba(255, 255, 255, 1); }"
39 | "QLineEdit { background: transparent;"
40 | "border-width:0;"
41 | "border-style:outset;"
42 | "border-bottom: 2px solid %s; }"
43 | "QLineEdit:focus { border-bottom: 2px "
44 | "dashed %s; }"
45 | %(self.color, self.color, self.color))
46 |
47 | # 百度OCR API Key 输入框
48 | self.baidu_ocr_key_textEdit = QLineEdit(self)
49 | self.customSetGeometry(self.baidu_ocr_key_textEdit, 20, 10, 330, 25)
50 | self.baidu_ocr_key_textEdit.setPlaceholderText("百度OCR API Key")
51 | self.baidu_ocr_key_textEdit.setText(self.object.config["OCR"]["Key"])
52 | self.baidu_ocr_key_textEdit.setCursor(ui.static.icon.EDIT_CURSOR)
53 | self.baidu_ocr_key_textEdit.hide()
54 |
55 | # 百度OCR Secret Key 输入框
56 | self.baidu_ocr_secret_textEdit = QLineEdit(self)
57 | self.customSetGeometry(self.baidu_ocr_secret_textEdit, 20, 45, 330, 25)
58 | self.baidu_ocr_secret_textEdit.setPlaceholderText("百度OCR Secret Key")
59 | self.baidu_ocr_secret_textEdit.setText(self.object.config["OCR"]["Secret"])
60 | self.baidu_ocr_secret_textEdit.setCursor(ui.static.icon.EDIT_CURSOR)
61 | self.baidu_ocr_secret_textEdit.hide()
62 |
63 |
64 | # 初始化配置
65 | def getInitConfig(self) :
66 |
67 | # 界面缩放比例
68 | self.rate = self.object.yaml["screen_scale_rate"]
69 | # 界面尺寸
70 | self.window_width = int(370 * self.rate)
71 | self.window_height = int(95 * self.rate)
72 | # 颜色
73 | self.color = "#5B8FF9"
74 |
75 |
76 | # 根据分辨率定义控件位置尺寸
77 | def customSetGeometry(self, object, x, y, w, h) :
78 |
79 | object.setGeometry(QRect(int(x * self.rate),
80 | int(y * self.rate), int(w * self.rate),
81 | int(h * self.rate)))
82 |
83 |
84 | # 过滤空字符
85 | def filterNullWord(self, obj) :
86 |
87 | text = obj.text().strip()
88 | obj.setText(text)
89 | return text
90 |
91 |
92 | # 窗口关闭处理
93 | def closeEvent(self, event) :
94 |
95 | self.object.config["OCR"]["Key"] = self.filterNullWord(self.baidu_ocr_key_textEdit)
96 | self.object.config["OCR"]["Secret"] = self.filterNullWord(self.baidu_ocr_secret_textEdit)
97 |
98 | self.baidu_ocr_key_textEdit.hide()
99 | self.baidu_ocr_secret_textEdit.hide()
--------------------------------------------------------------------------------
/ui/login.py:
--------------------------------------------------------------------------------
1 | from PyQt5.QtCore import *
2 | from PyQt5.QtGui import *
3 | from PyQt5.QtWidgets import *
4 |
5 | import qtawesome
6 | import re
7 |
8 | import utils.message
9 | import utils.http
10 | import utils.config
11 | import utils.enctry
12 | import ui.static.icon
13 | import ui.static.background
14 |
15 |
16 | # 登录界面
17 | class Login(QWidget) :
18 |
19 | def __init__(self, object) :
20 |
21 | super(Login, self).__init__()
22 | self.object = object
23 | self.logger = object.logger
24 | self.getInitConfig()
25 | self.ui()
26 |
27 |
28 | def ui(self):
29 |
30 | # 窗口尺寸
31 | self.resize(int(500*self.rate), int(566*self.rate))
32 |
33 | # 窗口无标题栏、窗口置顶、窗口透明
34 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
35 | self.setAttribute(Qt.WA_TranslucentBackground)
36 | # 窗口图标
37 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
38 | # 鼠标样式
39 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
40 | # 设置字体
41 | font = QFont()
42 | font.setFamily(self.font_type)
43 | font.setPointSize(self.font_size)
44 | self.setFont(font)
45 |
46 | # 界面样式
47 | self.setStyleSheet("QLineEdit { background: transparent;"
48 | "border-width:0; "
49 | "border-style:outset; "
50 | "color: %s; "
51 | "font-weight: bold;"
52 | "border-bottom: 2px solid %s; }"
53 | "QTextEdit:focus { border-bottom: 2px dashed %s; }"
54 | %(self.color, self.color, self.color))
55 |
56 | # 背景图片, 长宽比: 1.13
57 | label = QLabel(self)
58 | self.customSetGeometry(label, 0, 0, 500, 566)
59 | pixmap = ui.static.icon.LOGIN_PIXMAP.scaled(self.width(), self.height(),
60 | Qt.KeepAspectRatio, Qt.SmoothTransformation)
61 | label.setPixmap(pixmap)
62 |
63 | # 矩形框
64 | label = QLabel(self)
65 | self.customSetGeometry(label, 60, 355, 400, 211)
66 | label.setStyleSheet("background-color: rgba(255, 255, 255, 0.7);"
67 | "border-width: 5px 5px 5px 5px;"
68 | "border:2px solid %s;"
69 | "border-radius: 15px;"%self.color)
70 |
71 | # Logo
72 | label = QLabel(self)
73 | self.customSetGeometry(label, 140, 365, 35, 35)
74 | label.setPixmap(ui.static.icon.createPixmap(ui.static.icon.APP_LOGO, 30, 30))
75 |
76 | # 标题
77 | label = QLabel(self)
78 | self.customSetGeometry(label, 190, 370, 250, 30)
79 | label.setText("团子翻译器")
80 | label.setStyleSheet("color: %s;"
81 | "background: transparent;"
82 | "font: 20pt %s;"
83 | "font-weight:bold;"%(self.color, self.font_type))
84 |
85 | # 最小化按钮
86 | button = QPushButton(qtawesome.icon("fa.minus", color=self.color), "", self)
87 | self.customSetIconSize(button, 20, 20)
88 | self.customSetGeometry(button, 405, 360, 20, 20)
89 | button.setStyleSheet("background: transparent;")
90 | button.setCursor(ui.static.icon.SELECT_CURSOR)
91 | button.clicked.connect(self.showMinimized)
92 |
93 | # 退出按钮
94 | button = QPushButton(qtawesome.icon("fa.times", color=self.color), "", self)
95 | self.customSetIconSize(button, 20, 20)
96 | self.customSetGeometry(button, 430, 360, 20, 20)
97 | button.setStyleSheet("background: transparent;")
98 | button.setCursor(ui.static.icon.SELECT_CURSOR)
99 | button.clicked.connect(self.quit)
100 |
101 | # 账号输入框
102 | self.user_text = QLineEdit(self)
103 | self.customSetGeometry(self.user_text, 100, 410, 315, 30)
104 | self.user_text.setPlaceholderText("请输入账号:")
105 | self.user_text.setText(self.user)
106 | self.user_text.setCursor(ui.static.icon.EDIT_CURSOR)
107 |
108 | # 密码输入框
109 | self.password_text = QLineEdit(self)
110 | self.customSetGeometry(self.password_text, 100, 455, 315, 30)
111 | self.password_text.setPlaceholderText("请输入密码:")
112 | self.password_text.setEchoMode(QLineEdit.Password)
113 | self.password_text.setText(self.password)
114 | self.password_text.setCursor(ui.static.icon.EDIT_CURSOR)
115 |
116 | # 是否显示密码
117 | self.eye_button = QPushButton(qtawesome.icon("fa.eye-slash", color=self.color), "", self)
118 | self.customSetIconSize(self.eye_button, 25, 25)
119 | self.customSetGeometry(self.eye_button, 390, 455, 30, 30)
120 | self.eye_button.setStyleSheet("background: transparent;")
121 | self.eye_button.setCursor(ui.static.icon.SELECT_CURSOR)
122 | self.eye_button.clicked.connect(self.clickEyeButton)
123 |
124 | # 登录按钮
125 | self.login_button = QPushButton(self)
126 | self.customSetGeometry(self.login_button, 190, 495, 50, 35)
127 | self.login_button.setCursor(ui.static.icon.SELECT_CURSOR)
128 | self.login_button.setText("登录")
129 | self.login_button.setStyleSheet("background: transparent;"
130 | "color: %s;"
131 | "font: 15pt %s;"
132 | % (self.color, self.font_type))
133 |
134 | # 注册按钮
135 | self.register_button = QPushButton(self)
136 | self.customSetGeometry(self.register_button, 280, 495, 50, 35)
137 | self.register_button.setCursor(ui.static.icon.SELECT_CURSOR)
138 | self.register_button.setText("注册")
139 | self.register_button.setStyleSheet("background: transparent;"
140 | "color: %s;"
141 | "font: 15pt %s;"
142 | %(self.color, self.font_type))
143 |
144 | # 忘记密码按钮
145 | self.forget_password_button = QPushButton(self)
146 | self.customSetGeometry(self.forget_password_button, 365, 490, 60, 15)
147 | self.forget_password_button.setCursor(ui.static.icon.SELECT_CURSOR)
148 | self.forget_password_button.setText("忘记密码")
149 | self.forget_password_button.setStyleSheet("background: transparent;"
150 | "color: %s;"
151 | "font: 8pt %s;"
152 | %(self.color, self.font_type))
153 |
154 | # 版本号
155 | label = QLabel(self)
156 | self.customSetGeometry(label, 80, 540, 380, 15)
157 | label.setText("版本号: %s 更新时间: 2023-09-15 By: 胖次团子"%self.object.yaml["version"])
158 | label.setStyleSheet("color: %s;"
159 | "background: transparent;"
160 | "font-weight:bold;"
161 | "font: 10pt %s;"
162 | %(self.color, self.font_type))
163 |
164 | self.setTabOrder(self.user_text, self.password_text)
165 |
166 |
167 | # 初始化配置
168 | def getInitConfig(self) :
169 |
170 | # 界面缩放比例
171 | self.rate = self.object.yaml["screen_scale_rate"]
172 | # 界面字体
173 | self.font_type = "华康方圆体W7"
174 | # 界面字体大小
175 | self.font_size = 13
176 | # 界面颜色
177 | self.color = "#FF79BC"
178 | # 用户名
179 | self.user = str(self.object.yaml["user"])
180 | # 密码
181 | psw = str(self.object.yaml["password"])
182 | if psw.find('%6?u!') != -1:
183 | self.password = utils.enctry.dectry(psw)
184 | else:
185 | self.password = psw
186 |
187 |
188 | # 根据分辨率定义控件位置尺寸
189 | def customSetGeometry(self, object, x, y, w, h):
190 |
191 | object.setGeometry(QRect(int(x * self.rate),
192 | int(y * self.rate), int(w * self.rate),
193 | int(h * self.rate)))
194 |
195 |
196 | # 根据分辨率定义图标位置尺寸
197 | def customSetIconSize(self, object, w, h) :
198 |
199 | object.setIconSize(QSize(int(w*self.rate),
200 | int(h*self.rate)))
201 |
202 |
203 | # 鼠标移动事件
204 | def mouseMoveEvent(self, e: QMouseEvent) :
205 |
206 | try :
207 | self._endPos = e.pos() - self._startPos
208 | self.move(self.pos() + self._endPos)
209 | except :
210 | pass
211 |
212 |
213 | # 鼠标按下事件
214 | def mousePressEvent(self, e: QMouseEvent) :
215 |
216 | try :
217 | if e.button() == Qt.LeftButton :
218 | self._isTracking = True
219 | self._startPos = QPoint(e.x(), e.y())
220 | except :
221 | pass
222 |
223 |
224 | # 鼠标松开事件
225 | def mouseReleaseEvent(self, e: QMouseEvent) :
226 |
227 | try :
228 | if e.button() == Qt.LeftButton :
229 | self._isTracking = False
230 | self._startPos = None
231 | self._endPos = None
232 | except :
233 | pass
234 |
235 |
236 | # 点击眼睛
237 | def clickEyeButton(self) :
238 |
239 | if self.password_text.echoMode() == QLineEdit.Password :
240 | self.eye_button.setIcon(qtawesome.icon('fa.eye', color=self.color))
241 | self.password_text.setEchoMode(QLineEdit.Normal)
242 | else :
243 | self.eye_button.setIcon(qtawesome.icon('fa.eye-slash', color=self.color))
244 | self.password_text.setEchoMode(QLineEdit.Password)
245 |
246 |
247 | # 检查登录参数
248 | def checkLogin(self) :
249 |
250 | self.user = self.user_text.text()
251 | self.password = self.password_text.text()
252 |
253 | # 检查用户名是否合法
254 | if not self.user or re.findall("\s", self.user):
255 | utils.message.MessageBox("登录失败",
256 | "用户名为空或含有不合法字符! ")
257 | return False
258 |
259 | # 检查密码是否合法
260 | if not self.password or re.findall("\s", self.password):
261 | utils.message.MessageBox("登录失败",
262 | "密码为空或含有不合法字符! ")
263 | return False
264 |
265 | return True
266 |
267 |
268 | # 登录请求
269 | def postLogin(self) :
270 |
271 | # 请求服务器
272 | url = self.object.yaml["dict_info"]["dango_login"]
273 | body = {
274 | "User": self.user,
275 | "Password": self.password
276 | }
277 | res = utils.http.post(url=url, body=body, logger=self.logger)
278 | if not res:
279 | url = "https://trans.dango.cloud/DangoTranslate/Login"
280 | res = utils.http.post(url=url, body=body, logger=self.logger)
281 | result = res.get("Result", "")
282 |
283 | if result == "User dose not exist":
284 | utils.message.MessageBox("登录失败",
285 | "用户名不存在, 请先注册! ")
286 | return False
287 |
288 | elif result == "Password error":
289 | utils.message.MessageBox("登录失败",
290 | "密码输错啦! ")
291 | return False
292 |
293 | elif result == "User is black list":
294 | utils.message.MessageBox("登录失败",
295 | "已被纳入黑名单! ")
296 | return False
297 |
298 | elif result == "OK":
299 | # 保存配置
300 | self.object.yaml["user"] = self.user
301 | self.object.yaml["password"] = utils.enctry.enctry(self.password)
302 | utils.config.saveConfig(self.object.yaml, self.logger)
303 | return True
304 |
305 | else:
306 | utils.message.MessageBox("登录失败",
307 | "出现了出乎意料的情况\n请联系团子解决! ")
308 | return False
309 |
310 |
311 | # 登录
312 | def login(self) :
313 |
314 | # 检查登录参数
315 | if not self.checkLogin() :
316 | return False
317 |
318 | # 登录请求
319 | if not self.postLogin() :
320 | return False
321 |
322 | return True
323 |
324 |
325 | # 热键检测
326 | def keyPressEvent(self, event) :
327 |
328 | # 如果按下回车键
329 | if event.key() == 16777220 :
330 | self.object.login()
331 |
332 |
333 | # 退出程序
334 | def quit(self) :
335 |
336 | # 退出
337 | QCoreApplication.instance().quit()
--------------------------------------------------------------------------------
/ui/progress_bar.py:
--------------------------------------------------------------------------------
1 | from PyQt5.QtCore import *
2 | from PyQt5.QtGui import *
3 | from PyQt5.QtWidgets import *
4 | import os
5 | import base64
6 |
7 | import ui.static.icon
8 | import utils.thread
9 | import utils.message
10 |
11 | LOADING_PATH = "./config/icon/loading.gif"
12 |
13 |
14 | # 进度条
15 | class ProgressBar(QWidget) :
16 |
17 | def __init__(self, rate, use_type) :
18 |
19 | super(ProgressBar, self).__init__()
20 | self.rate = rate
21 | self.use_type = use_type
22 | self.getInitConfig()
23 | self.ui()
24 |
25 |
26 | def ui(self) :
27 |
28 | # 窗口置顶
29 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
30 | # 窗口尺寸及不可拉伸
31 | self.resize(self.window_width, self.window_height)
32 | self.setMinimumSize(QSize(self.window_width, self.window_height))
33 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
34 | # 窗口图标
35 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
36 | # 设置字体
37 | self.setStyleSheet("font: %spt '%s'; background-color: rgb(255, 255, 255);"%(self.font_size, self.font_type))
38 |
39 | self.progress_bar = QProgressBar(self)
40 | self.customSetGeometry(self.progress_bar, 20, 10, 260, 10)
41 | self.progress_bar.setTextVisible(False)
42 | self.progress_bar.setStyleSheet("QProgressBar {background-color: rgba(62, 62, 62, 0.2); border-radius: 6px;}"
43 | "QProgressBar::chunk {background-color: %s; border-radius: 5px;}"
44 | %(self.color_2))
45 | self.progress_bar.setValue(0)
46 |
47 | self.progress_label = QLabel(self)
48 | self.customSetGeometry(self.progress_label, 240, 25, 150, 20)
49 | self.progress_label.setStyleSheet("color: %s"%self.color_2)
50 |
51 | self.file_size_label = QLabel(self)
52 | self.customSetGeometry(self.file_size_label, 20, 25, 150, 20)
53 | self.file_size_label.setStyleSheet("color: %s" % self.color_2)
54 |
55 | self.paintProgressBar(0, 0, "0/0")
56 |
57 |
58 | # 根据分辨率定义控件位置尺寸
59 | def customSetGeometry(self, object, x, y, w, h) :
60 |
61 | object.setGeometry(QRect(int(x * self.rate),
62 | int(y * self.rate),
63 | int(w * self.rate),
64 | int(h * self.rate)))
65 |
66 |
67 | # 初始化配置
68 | def getInitConfig(self):
69 |
70 | # 界面字体
71 | self.font_type = "华康方圆体W7"
72 | # 界面字体大小
73 | self.font_size = 10
74 | # 灰色
75 | self.color_1 = "#595959"
76 | # 蓝色
77 | self.color_2 = "#5B8FF9"
78 | # 界面尺寸
79 | self.window_width = int(300 * self.rate)
80 | self.window_height = int(50 * self.rate)
81 | # 结束信号
82 | self.finish_sign = False
83 | # 中止信号
84 | self.stop_sign = False
85 |
86 |
87 | # 绘制进度信息
88 | def paintProgressBar(self, float_val, int_val, str_val) :
89 |
90 | self.progress_label.setText("{:.2f}%".format(float_val))
91 | self.progress_bar.setValue(int_val)
92 | self.file_size_label.setText(str_val)
93 |
94 |
95 | # 修改窗口标题
96 | def modifyTitle(self, title) :
97 |
98 | self.setWindowTitle(title)
99 |
100 |
101 | # 停止任务
102 | def stopProcess(self) :
103 |
104 | self.stop_sign = True
105 |
106 |
107 | # 窗口关闭处理
108 | def closeEvent(self, event) :
109 |
110 | if self.finish_sign :
111 | return
112 | if self.use_type == "offline_ocr" :
113 | utils.message.closeProcessBarMessageBox("停止安装",
114 | "本地OCR安装进行中\n确定要中止操作吗 ",
115 | self)
116 | elif self.use_type == "input_images" :
117 | utils.message.closeProcessBarMessageBox("停止导入",
118 | "图片导入进行中\n确定要中止操作吗 ",
119 | self)
120 | if not self.stop_sign :
121 | event.ignore()
122 |
123 |
124 | # 图片翻译进度条
125 | class MangaProgressBar(QWidget) :
126 |
127 | def __init__(self, rate) :
128 |
129 | super(MangaProgressBar, self).__init__()
130 | self.rate = rate
131 | self.getInitConfig()
132 | self.ui()
133 |
134 |
135 | def ui(self) :
136 |
137 | # 窗口置顶
138 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
139 | # 窗口尺寸及不可拉伸
140 | self.resize(self.window_width, self.window_height)
141 | self.setMaximumSize(QSize(self.window_width, self.window_height))
142 | self.setMinimumSize(QSize(self.window_width, self.window_height))
143 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint)
144 | # 窗口图标
145 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
146 | # 设置字体
147 | self.setStyleSheet("font: %spt '%s'; background-color: rgb(255, 255, 255);"%(self.font_size, self.font_type))
148 |
149 | self.progress_bar = QProgressBar(self)
150 | self.customSetGeometry(self.progress_bar, 20, 10, 260, 10)
151 | self.progress_bar.setTextVisible(False)
152 | self.progress_bar.setStyleSheet("QProgressBar { background-color: rgba(62, 62, 62, 0.2); border-radius: 6px;}"
153 | "QProgressBar::chunk { background-color: %s; border-radius: 5px;}"
154 | %(self.color_2))
155 | self.progress_bar.setValue(0)
156 |
157 | self.progress_label = QLabel(self)
158 | self.customSetGeometry(self.progress_label, 240, 25, 150, 20)
159 | self.progress_label.setStyleSheet("color: %s"%self.color_2)
160 |
161 | self.file_size_label = QLabel(self)
162 | self.customSetGeometry(self.file_size_label, 30, 25, 150, 20)
163 | self.file_size_label.setStyleSheet("color: %s"%self.color_2)
164 |
165 | widget = QWidget(self)
166 | self.customSetGeometry(widget, 15, 40, 300, 200)
167 | layout = QGridLayout(self)
168 | widget.setLayout(layout)
169 |
170 | label = QLabel(self)
171 | label.setStyleSheet("color: %s"%self.color_2)
172 | label.resize(QSize(100*self.rate, 20*self.rate))
173 | label.setText("文字识别")
174 | layout.addWidget(label, 0, 0)
175 |
176 | label = QLabel(self)
177 | label.setStyleSheet("color: %s"%self.color_2)
178 | label.resize(QSize(100*self.rate, 20*self.rate))
179 | label.setText("文字翻译")
180 | layout.addWidget(label, 1, 0)
181 |
182 | label = QLabel(self)
183 | label.setStyleSheet("color: %s"%self.color_2)
184 | label.resize(QSize(100*self.rate, 20*self.rate))
185 | label.setText("文字消除")
186 | layout.addWidget(label, 2, 0)
187 |
188 | label = QLabel(self)
189 | label.setStyleSheet("color: %s"%self.color_2)
190 | label.resize(QSize(100*self.rate, 20*self.rate))
191 | label.setText("文字渲染")
192 | layout.addWidget(label, 3, 0)
193 |
194 | if not os.path.exists(LOADING_PATH) :
195 | with open(LOADING_PATH, "wb") as file :
196 | file.write(base64.b64decode(ui.static.icon.LOADING_GIF))
197 | self.loading_movie = QMovie(LOADING_PATH)
198 | self.loading_movie.setScaledSize(QSize(50*self.rate, 50*self.rate))
199 | self.loading_movie.start()
200 |
201 | self.ocr_icon_label = QLabel(self)
202 | self.ocr_icon_label.resize(QSize(100*self.rate, 20*self.rate))
203 | self.ocr_icon_label.setAlignment(Qt.AlignCenter)
204 | self.ocr_icon_label.setMovie(self.loading_movie)
205 | layout.addWidget(self.ocr_icon_label, 0, 1)
206 |
207 | self.trans_icon_label = QLabel(self)
208 | self.trans_icon_label.resize(QSize(100*self.rate, 20*self.rate))
209 | self.trans_icon_label.setAlignment(Qt.AlignCenter)
210 | self.trans_icon_label.setMovie(self.loading_movie)
211 | layout.addWidget(self.trans_icon_label, 1, 1)
212 |
213 | self.ipt_icon_label = QLabel(self)
214 | self.ipt_icon_label.resize(QSize(100*self.rate, 20*self.rate))
215 | self.ipt_icon_label.setAlignment(Qt.AlignCenter)
216 | self.ipt_icon_label.setMovie(self.loading_movie)
217 | layout.addWidget(self.ipt_icon_label, 2, 1)
218 |
219 | self.rdr_icon_label = QLabel(self)
220 | self.rdr_icon_label.resize(QSize(100*self.rate, 20*self.rate))
221 | self.rdr_icon_label.setAlignment(Qt.AlignCenter)
222 | self.rdr_icon_label.setMovie(self.loading_movie)
223 | layout.addWidget(self.rdr_icon_label, 3, 1)
224 |
225 | self.ocr_time_label = QLabel(self)
226 | self.ocr_time_label.resize(QSize(100*self.rate, 20*self.rate))
227 | self.ocr_time_label.setStyleSheet("color: %s"%self.color_2)
228 | self.ocr_time_label.setText("耗时 - s")
229 | layout.addWidget(self.ocr_time_label, 0, 2, 1, 2)
230 |
231 | self.trans_time_label = QLabel(self)
232 | self.trans_time_label.resize(QSize(100*self.rate, 20*self.rate))
233 | self.trans_time_label.setStyleSheet("color: %s"%self.color_2)
234 | self.trans_time_label.setText("耗时 - s")
235 | layout.addWidget(self.trans_time_label, 1, 2, 1, 2)
236 |
237 | self.ipt_time_label = QLabel(self)
238 | self.ipt_time_label.resize(QSize(100*self.rate, 20*self.rate))
239 | self.ipt_time_label.setStyleSheet("color: %s"%self.color_2)
240 | self.ipt_time_label.setText("耗时 - s")
241 | layout.addWidget(self.ipt_time_label, 2, 2, 1, 2)
242 |
243 | self.rdr_time_label = QLabel(self)
244 | self.rdr_time_label.resize(QSize(100*self.rate, 20*self.rate))
245 | self.rdr_time_label.setStyleSheet("color: %s"%self.color_2)
246 | self.rdr_time_label.setText("耗时 - s")
247 | layout.addWidget(self.rdr_time_label, 3, 2, 1, 2)
248 |
249 | # 翻译进度消息窗口
250 | self.message_text = QTextEdit(self)
251 | self.customSetGeometry(self.message_text, 10, 240, 280, 150)
252 | self.message_text.setWordWrapMode(QTextOption.WrapAnywhere)
253 | self.message_text.setLineWrapMode(QTextEdit.NoWrap)
254 | self.message_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
255 | self.message_text.setReadOnly(True)
256 | self.message_text.setStyleSheet("background-color: rgb(224, 224, 224);"
257 | "font: 9pt '华康方圆体W7';"
258 | "border: 1px solid black;")
259 | self.message_text.setCursor(ui.static.icon.SELECT_CURSOR)
260 |
261 | self.paintProgressBar(0, "0/0")
262 |
263 |
264 | # 根据分辨率定义控件位置尺寸
265 | def customSetGeometry(self, object, x, y, w, h) :
266 |
267 | object.setGeometry(QRect(int(x * self.rate),
268 | int(y * self.rate),
269 | int(w * self.rate),
270 | int(h * self.rate)))
271 |
272 |
273 | # 初始化配置
274 | def getInitConfig(self):
275 |
276 | # 界面字体
277 | self.font_type = "华康方圆体W7"
278 | # 界面字体大小
279 | self.font_size = 10
280 | # 灰色
281 | self.color_1 = "#595959"
282 | # 蓝色
283 | self.color_2 = "#5B8FF9"
284 | # 界面尺寸
285 | self.window_width = int(300 * self.rate)
286 | self.window_height = int(400 * self.rate)
287 | # 结束信号
288 | self.finish_sign = False
289 | # 中止信号
290 | self.stop_sign = False
291 |
292 |
293 | # 绘制进度信息
294 | def paintProgressBar(self, int_val, str_val) :
295 |
296 | self.progress_label.setText("{}%".format(int_val))
297 | self.progress_bar.setValue(int_val)
298 | self.file_size_label.setText(str_val)
299 |
300 | if int_val < 100 :
301 | self.ocr_icon_label.setMovie(self.loading_movie)
302 | self.trans_icon_label.setMovie(self.loading_movie)
303 | self.ipt_icon_label.setMovie(self.loading_movie)
304 | self.rdr_icon_label.setMovie(self.loading_movie)
305 |
306 | self.ocr_time_label.setText("耗时 - s")
307 | self.trans_time_label.setText("耗时 - s")
308 | self.ipt_time_label.setText("耗时 - s")
309 | self.rdr_time_label.setText("耗时 - s")
310 |
311 |
312 | # 修改窗口标题
313 | def modifyTitle(self, title) :
314 |
315 | self.setWindowTitle(title)
316 |
317 |
318 | # 停止任务
319 | def stopProcess(self) :
320 |
321 | self.stop_sign = True
322 |
323 |
324 | # 绘制当前翻译状态
325 | def paintStatus(self, status_type, status_time, success_status) :
326 |
327 | if status_type == "ocr" :
328 | if success_status:
329 | self.ocr_icon_label.setPixmap(ui.static.icon.FINISH_PIXMAP)
330 | else:
331 | self.ocr_icon_label.setPixmap(ui.static.icon.FAIL_PIXMAP)
332 | self.ocr_time_label.setText("耗时 {} s".format(status_time))
333 |
334 | elif status_type == "trans" :
335 | if success_status:
336 | self.trans_icon_label.setPixmap(ui.static.icon.FINISH_PIXMAP)
337 | else:
338 | self.trans_icon_label.setPixmap(ui.static.icon.FAIL_PIXMAP)
339 | self.trans_time_label.setText("耗时 {} s".format(status_time))
340 |
341 | elif status_type == "ipt" :
342 | if success_status:
343 | self.ipt_icon_label.setPixmap(ui.static.icon.FINISH_PIXMAP)
344 | else:
345 | self.ipt_icon_label.setPixmap(ui.static.icon.FAIL_PIXMAP)
346 | self.ipt_time_label.setText("耗时 {} s".format(status_time))
347 |
348 | elif status_type == "rdr" :
349 | if success_status:
350 | self.rdr_icon_label.setPixmap(ui.static.icon.FINISH_PIXMAP)
351 | else :
352 | self.rdr_icon_label.setPixmap(ui.static.icon.FAIL_PIXMAP)
353 | self.rdr_time_label.setText("耗时 {} s".format(status_time))
354 |
355 |
356 | # 设置消息窗文本
357 | def setMessageText(self, text, color) :
358 |
359 | if not text :
360 | self.message_text.clear()
361 | else :
362 | self.message_text.insertHtml("{}".format(color, text))
363 | self.message_text.insertHtml("
")
364 | cursor = self.message_text.textCursor()
365 | cursor.movePosition(QTextCursor.End)
366 | self.message_text.setTextCursor(cursor)
367 |
368 |
369 | # 窗口关闭处理
370 | def closeEvent(self, event) :
371 |
372 | if self.finish_sign :
373 | return
374 | utils.message.closeProcessBarMessageBox("停止翻译",
375 | "图片翻译进行中\n确定要中止操作吗 ",
376 | self)
377 | if not self.stop_sign :
378 | event.ignore()
--------------------------------------------------------------------------------
/ui/static/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/ui/static/__init__.py
--------------------------------------------------------------------------------
/ui/tencent.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtWidgets import *
6 |
7 | import ui.static.icon
8 | import utils.test
9 | import webbrowser
10 |
11 |
12 | # 私人腾讯设置界面
13 | class TencentSetting(QWidget) :
14 |
15 | def __init__(self, object) :
16 |
17 | super(TencentSetting, self).__init__()
18 | self.object = object
19 | self.logger = object.logger
20 | self.getInitConfig()
21 | self.ui()
22 |
23 |
24 | def ui(self) :
25 |
26 | # 窗口尺寸及不可拉伸
27 | self.resize(self.window_width, self.window_height)
28 | self.setMinimumSize(QSize(self.window_width, self.window_height))
29 | self.setMaximumSize(QSize(self.window_width, self.window_height))
30 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
31 |
32 | # 窗口标题
33 | self.setWindowTitle("私人腾讯翻译设置 - 退出会自动保存")
34 | # 窗口图标
35 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
36 | # 鼠标样式
37 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
38 | # 界面样式
39 | self.setStyleSheet("QWidget { font: 9pt '华康方圆体W7';"
40 | "color: %s;"
41 | "background: rgba(255, 255, 255, 1); }"
42 | "QLineEdit { background: transparent;"
43 | "border-width:0;"
44 | "border-style:outset;"
45 | "border-bottom: 2px solid %s; }"
46 | "QLineEdit:focus { border-bottom: 2px "
47 | "dashed %s; }"
48 | "QLabel {color: %s;}"
49 | "QPushButton { background: %s; border-radius: %spx; color: rgb(255, 255, 255); }"
50 | "QPushButton:hover { background-color: #83AAF9; }"
51 | "QPushButton:pressed { background-color: #4480F9; padding-left: 3px; padding-top: 3px; }"
52 | %(self.color, self.color, self.color, self.color, self.color, 6.66*self.rate))
53 |
54 | # secret_id 输入框
55 | label = QLabel(self)
56 | self.customSetGeometry(label, 20, 20, 330, 20)
57 | label.setText("SecretId: ")
58 | self.secret_id_text = QLineEdit(self)
59 | self.customSetGeometry(self.secret_id_text, 20, 40, 330, 25)
60 | self.secret_id_text.setText(self.object.config["tencentAPI"]["Key"])
61 | self.secret_id_text.setCursor(ui.static.icon.EDIT_CURSOR)
62 |
63 | # secret_key 输入框
64 | label = QLabel(self)
65 | self.customSetGeometry(label, 20, 80, 330, 20)
66 | label.setText("SecretKey: ")
67 | self.secret_key_text = QLineEdit(self)
68 | self.customSetGeometry(self.secret_key_text, 20, 100, 330, 25)
69 | self.secret_key_text.setText(self.object.config["tencentAPI"]["Secret"])
70 | self.secret_key_text.setCursor(ui.static.icon.EDIT_CURSOR)
71 |
72 | # 测试按钮
73 | button = QPushButton(self)
74 | self.customSetGeometry(button, 65, 150, 60, 20)
75 | button.setText("测试")
76 | button.clicked.connect(lambda: utils.test.testTencent(
77 | self.object, self.filterNullWord(self.secret_id_text), self.filterNullWord(self.secret_key_text)))
78 | button.setCursor(ui.static.icon.SELECT_CURSOR)
79 |
80 | # 注册按钮
81 | button = QPushButton(self)
82 | self.customSetGeometry(button, 155, 150, 60, 20)
83 | button.setText("注册")
84 | button.clicked.connect(self.openTutorial)
85 | button.setCursor(ui.static.icon.SELECT_CURSOR)
86 |
87 | # 查额度按钮
88 | button = QPushButton(self)
89 | self.customSetGeometry(button, 245, 150, 60, 20)
90 | button.setText("查额度")
91 | button.clicked.connect(self.openQueryQuota)
92 | button.setCursor(ui.static.icon.SELECT_CURSOR)
93 |
94 |
95 | # 初始化配置
96 | def getInitConfig(self) :
97 |
98 | # 界面缩放比例
99 | self.rate = self.object.yaml["screen_scale_rate"]
100 | # 界面尺寸
101 | self.window_width = int(370 * self.rate)
102 | self.window_height = int(200 * self.rate)
103 | # 颜色
104 | self.color = "#5B8FF9"
105 |
106 |
107 | # 根据分辨率定义控件位置尺寸
108 | def customSetGeometry(self, object, x, y, w, h) :
109 |
110 | object.setGeometry(QRect(int(x * self.rate),
111 | int(y * self.rate), int(w * self.rate),
112 | int(h * self.rate)))
113 |
114 |
115 | # 过滤空字符
116 | def filterNullWord(self, obj) :
117 |
118 | text = obj.text().strip()
119 | obj.setText(text)
120 | return text
121 |
122 |
123 | # 打开注册教程
124 | def openTutorial(self) :
125 |
126 | try :
127 | url = self.object.yaml["dict_info"]["tencent_tutorial"]
128 | webbrowser.open(url, new=0, autoraise=True)
129 | except Exception :
130 | self.logger.error(format_exc())
131 | utils.message.MessageBox("私人腾讯注册",
132 | "请尝试手动打开此地址:\n%s " % url)
133 |
134 |
135 | # 打开查询额度地址
136 | def openQueryQuota(self):
137 |
138 | url = "https://console.cloud.tencent.com/tmt"
139 | try:
140 | webbrowser.open(url, new=0, autoraise=True)
141 | except Exception :
142 | self.logger.error(format_exc())
143 | utils.message.MessageBox("私人腾讯额度查询",
144 | "打开地址失败, 请尝试手动打开此网页下载\n%s " % url)
145 |
146 |
147 | # 窗口关闭处理
148 | def closeEvent(self, event) :
149 |
150 | self.object.config["tencentAPI"]["Key"] = self.filterNullWord(self.secret_id_text)
151 | self.object.config["tencentAPI"]["Secret"] = self.filterNullWord(self.secret_key_text)
--------------------------------------------------------------------------------
/ui/trans_history.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import time
3 |
4 | from PyQt5.QtCore import *
5 | from PyQt5.QtGui import *
6 | from PyQt5.QtWidgets import *
7 | import fileinput
8 | import re
9 | import os
10 | import traceback
11 | import pyperclip
12 |
13 | import ui.static.icon
14 | import utils.message
15 | import utils.sqlite
16 | import utils.thread
17 |
18 |
19 | TRANS_FILE = "../翻译历史.txt"
20 |
21 |
22 | # 翻译历史界面
23 | class TransHistory(QWidget) :
24 |
25 | def __init__(self, object) :
26 |
27 | super(TransHistory, self).__init__()
28 | self.object = object
29 | self.logger = object.logger
30 | self.getInitConfig()
31 | self.ui()
32 |
33 |
34 | # 初始化配置
35 | def getInitConfig(self):
36 |
37 | # 界面缩放比例
38 | self.rate = self.object.yaml["screen_scale_rate"]
39 | # 界面字体
40 | self.font_type = "微软雅黑"
41 | # 界面字体大小
42 | self.font_size = 12
43 | # 颜色
44 | self.blue_color = "#5B8FF9"
45 | self.gray_color = "#DCDCDC"
46 | # 界面尺寸
47 | self.window_width = 1000
48 | self.window_height = 790
49 | # 最多显示的记录行数
50 | self.max_rows = 300
51 | # 总表数据
52 | self.trans_data_total = 0
53 | # 当前页码
54 | self.page_now = 1
55 | # 总页码
56 | self.all_page = 1
57 |
58 |
59 | def ui(self) :
60 |
61 | # 窗口尺寸
62 | self.resize(self.window_width*self.rate, self.window_height*self.rate)
63 | # 窗口图标
64 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
65 | # 鼠标样式
66 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
67 | # 设置字体
68 | self.setStyleSheet("QWidget {font: %spt '%s';}"
69 | "QPushButton {font: %spt '华康方圆体W7'; background: %s; border-radius: %spx; color: rgb(255, 255, 255);}"
70 | "QPushButton:hover {background-color: #83AAF9;}"
71 | "QPushButton:pressed {background-color: #4480F9; padding-left: 3px; padding-top: 3px;}"
72 | "QLabel {font: %spt '华康方圆体W7';}"
73 | %(self.font_size, self.font_type, self.font_size, self.blue_color, 6.66*self.rate, self.font_type))
74 |
75 | # 原文搜索框标签
76 | self.src_search_label = QLabel(self)
77 | self.src_search_label.setText("按原文模糊查询:")
78 | # 原文搜索框
79 | self.src_search_text = QLineEdit(self)
80 | self.src_search_text.setPlaceholderText("请输入要查询的原文")
81 | self.src_search_text.textChanged.connect(self.searchTransData)
82 |
83 | # 译文搜索框标签
84 | self.tgt_search_label = QLabel(self)
85 | self.tgt_search_label.setText("按译文模糊查询:")
86 | # 译文搜索框
87 | self.tgt_search_text = QLineEdit(self)
88 | self.tgt_search_text.setPlaceholderText("请输入要查询的译文")
89 | self.tgt_search_text.textChanged.connect(self.searchTransData)
90 |
91 | # 表格
92 | self.table_widget = QTableWidget(self)
93 | self.table_widget.setCursor(ui.static.icon.EDIT_CURSOR)
94 | # 表格列数
95 | self.table_widget.setColumnCount(4)
96 | # 表格无边框
97 | self.table_widget.setFrameShape(QFrame.Box)
98 | # 表头信息
99 | self.table_widget.setHorizontalHeaderLabels(["序号", "翻译源", "原文", "译文"])
100 | # 不显示行号
101 | self.table_widget.verticalHeader().setVisible(False)
102 | # 表头不塌陷
103 | self.table_widget.horizontalHeader().setHighlightSections(False)
104 | # 表头属性
105 | self.table_widget.horizontalHeader().setDefaultSectionSize(100*self.rate)
106 | self.table_widget.horizontalHeader().setStyleSheet("QHeaderView::section {font: 15pt '华康方圆体W7'; background: %s; color: rgb(255, 255, 255);}"%self.blue_color)
107 | # 表格铺满窗口
108 | self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
109 | self.table_widget.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
110 | self.table_widget.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)
111 | # 单元格可复制
112 | self.table_widget.setSelectionMode(QTableWidget.ContiguousSelection)
113 | # 文本修改信号
114 | self.table_widget.cellChanged.connect(self.modifyTransData)
115 | # 右键菜单
116 | self.table_widget.setContextMenuPolicy(Qt.CustomContextMenu)
117 | self.table_widget.customContextMenuRequested.connect(self.showMenu)
118 |
119 | # 上一页按钮
120 | self.last_page_button = QPushButton(self)
121 | self.last_page_button.setText("<上一页")
122 | self.last_page_button.clicked.connect(lambda: self.paging("last"))
123 |
124 | # 页码输入框
125 | self.page_spinbox = QSpinBox(self)
126 | self.page_spinbox.setMinimum(1)
127 | self.page_spinbox.valueChanged.connect(lambda: self.paging(None))
128 |
129 | # 页码标签
130 | self.page_label = QLabel(self)
131 |
132 | # 下一页按钮
133 | self.next_page_button = QPushButton(self)
134 | self.next_page_button.setText("下一页>")
135 | self.next_page_button.clicked.connect(lambda: self.paging("next"))
136 |
137 | # 导出按钮
138 | self.output_button = QPushButton(self)
139 | self.output_button.setText("导出全部")
140 | self.output_button.clicked.connect(self.outputAllTansData)
141 |
142 |
143 | # 根据分辨率定义控件位置尺寸
144 | def customSetGeometry(self, object, x, y, w, h, w_rate=1, h_rate=1) :
145 |
146 | object.setGeometry(QRect(
147 | int(x * w_rate),
148 | int(y * w_rate),
149 | int(w * h_rate),
150 | int(h * h_rate))
151 | )
152 |
153 |
154 | # 刷新表格数据
155 | def refreshTableData(self) :
156 |
157 | # 清空表格
158 | self.table_widget.setRowCount(0)
159 | # 查询数据库
160 | offset = (self.page_now - 1) * self.max_rows
161 | src = self.src_search_text.text()
162 | tgt = self.tgt_search_text.text()
163 | rows = utils.sqlite.selectTranslationDBList(src, tgt, self.max_rows, offset, self.logger)
164 | # 表格行数
165 | self.table_widget.setRowCount(len(rows))
166 | # 数据写入表格
167 | for i, row in enumerate(rows) :
168 | for j, v in enumerate(row) :
169 | if j == 0 :
170 | v = str(v)
171 | elif j == 1 and v in utils.sqlite.TRANS_MAP_INVERSION :
172 | # 翻译源转换中文
173 | v = utils.sqlite.TRANS_MAP_INVERSION[v]
174 | item = QTableWidgetItem(v)
175 | item.setTextAlignment(Qt.AlignVCenter)
176 | self.table_widget.setItem(i, j, item)
177 | # 翻译源和原文不可编辑
178 | if j < 2 :
179 | item.setFlags(item.flags() & ~Qt.ItemIsEditable)
180 | item.setFlags(item.flags() | Qt.ItemIsEnabled)
181 |
182 | # 设置行高自适应内容
183 | for row in range(self.table_widget.rowCount()) :
184 | self.table_widget.resizeRowToContents(row)
185 |
186 |
187 | # 导出所有翻译数据
188 | def outputAllTansData(self) :
189 |
190 | # 选择指定位置
191 | options = QFileDialog.Options()
192 | dialog = QFileDialog()
193 | try :
194 | # 默认桌面
195 | dialog.setDirectory(QStandardPaths.standardLocations(QStandardPaths.DesktopLocation)[0])
196 | except Exception :
197 | # 默认用户根目录
198 | dialog.setDirectory(QDir.homePath())
199 | folder_path = dialog.getExistingDirectory(self, "选择要导出的位置", "", options=options)
200 | if not os.path.exists(folder_path):
201 | return utils.message.MessageBox("导出失败", "无效的目录 ")
202 |
203 | # 查询数据库导出
204 | file_path = os.path.join(folder_path, "团子翻译历史.csv")
205 | err = utils.sqlite.outputTranslationDB(file_path, self.logger)
206 | if err :
207 | if os.path.exists(file_path) :
208 | os.remove(file_path)
209 | return utils.message.MessageBox("导出翻译历史", "导出失败:\n%s"%err, self.rate)
210 |
211 | os.startfile(folder_path)
212 |
213 |
214 | # 窗口显示信号
215 | def showEvent(self, e) :
216 |
217 | # 刷新界面数据
218 | self.refreshTable()
219 |
220 |
221 | # 刷新页码
222 | def refreshPageLabel(self) :
223 |
224 | self.all_page = self.trans_data_total // self.max_rows + 1
225 | self.page_label.setText("/{}页".format(self.all_page))
226 | if self.page_now != self.page_spinbox.value() :
227 | self.page_spinbox.setValue(self.page_now)
228 | self.page_spinbox.setMaximum(self.all_page)
229 |
230 |
231 | # 翻页
232 | def paging(self, sign) :
233 |
234 | if sign == "next" :
235 | self.page_now += 1
236 | elif sign == "last" :
237 | self.page_now -= 1
238 | else :
239 | self.page_now = self.page_spinbox.value()
240 |
241 | if self.page_now > self.all_page :
242 | self.page_now = self.all_page
243 | elif self.page_now < 1 :
244 | self.page_now = 1
245 |
246 | # 刷新界面数据
247 | self.refreshTable()
248 |
249 |
250 | # 根据原文搜索翻译数据
251 | def searchTransData(self) :
252 |
253 | # 刷新页码
254 | self.page_now = 1
255 | # 刷新界面数据
256 | self.refreshTable()
257 |
258 |
259 | # 刷新界面数据
260 | def refreshTable(self) :
261 |
262 | # 刷新数据总量
263 | src = self.src_search_text.text()
264 | tgt = self.tgt_search_text.text()
265 | self.trans_data_total = utils.sqlite.selectTranslationDBTotal(src, tgt, self.logger)
266 | self.setWindowTitle("翻译历史(每页最多显示{}行, 数据总量{}行, 排序方式-按时间倒序, 双击原文和译文可编辑修改, 右键可复制和删除)".format(self.max_rows, self.trans_data_total))
267 |
268 | # 刷新页码
269 | self.refreshPageLabel()
270 | # 刷新数据总数
271 | self.refreshTableData()
272 |
273 |
274 | # 文本编辑信号
275 | def modifyTransData(self, row, col) :
276 |
277 | try :
278 | item = self.table_widget.item(row, col)
279 | if (col == 2 or col == 3) and item in self.table_widget.selectedItems() :
280 | id = int(self.table_widget.item(row, 0).text())
281 | if col == 2 :
282 | # 修改原文
283 | err = utils.sqlite.modifyTranslationDBSrc(id, item.text(), self.logger)
284 | elif col == 3 :
285 | # 修改译文
286 | err = utils.sqlite.modifyTranslationDBTgt(id, item.text(), self.logger)
287 | else :
288 | return
289 | if err :
290 | utils.message.MessageBox("修改翻译历史", "修改失败:\n{}".format(err))
291 | else :
292 | utils.message.MessageBox("修改翻译历史", "修改成功")
293 | except Exception :
294 | err = traceback.format_exc()
295 | self.logger.error(err)
296 | utils.message.MessageBox("修改翻译历史", "修改失败:\n{}".format(err))
297 |
298 |
299 | # 表格右键菜单
300 | def showMenu(self, pos) :
301 |
302 | item = self.table_widget.itemAt(pos)
303 | if item is not None :
304 | menu = QMenu(self)
305 | menu.setStyleSheet("QMenu {color: #5B8FF9; background-color: #FFFFFF; font: 12pt '华康方圆体W7';}"
306 | "QMenu::item:selected:enabled {background: #E5F5FF;}"
307 | "QMenu::item:checked {background: #E5F5FF;}")
308 | # 添加菜单项
309 | copy_action = menu.addAction("复制")
310 | copy_action.triggered.connect(lambda: pyperclip.copy(item.text()))
311 | delete_action = menu.addAction("删除")
312 | delete_action.triggered.connect(lambda: self.deleteTransData(item))
313 | # 显示菜单
314 | cursorPos = QCursor.pos()
315 | menu.exec_(cursorPos)
316 |
317 |
318 | # 删除翻译数据
319 | def deleteTransData(self, item) :
320 |
321 | try :
322 | row = self.table_widget.row(item)
323 | id = int(self.table_widget.item(row, 0).text())
324 | err = utils.sqlite.deleteTranslationDBByID(id, self.logger)
325 | if err :
326 | utils.message.MessageBox("删除翻译历史", "删除失败:\n{}".format(err))
327 | else :
328 | utils.message.MessageBox("删除翻译历史", "删除成功")
329 | except Exception :
330 | err = traceback.format_exc()
331 | self.logger.error(err)
332 | utils.message.MessageBox("删除翻译历史", "删除失败:\n{}".format(err))
333 |
334 | self.refreshTable()
335 |
336 |
337 | # 窗口尺寸变化信号
338 | def resizeEvent(self, event) :
339 |
340 | w = event.size().width()
341 | h = event.size().height()
342 | w_rate = w / self.window_width
343 | h_rate = h / self.window_height
344 |
345 | # 原文搜索框标签
346 | self.customSetGeometry(self.src_search_label, 10, 0, 140, 30, w_rate, h_rate)
347 | # 原文搜索框
348 | self.src_search_text.setGeometry(
349 | self.src_search_label.x() + self.src_search_label.width(), 0,
350 | w-20*w_rate-self.src_search_label.width(), self.src_search_label.height()
351 | )
352 | # 译文搜索框标签
353 | self.tgt_search_label.setGeometry(
354 | self.src_search_label.x(), self.src_search_label.height(),
355 | self.src_search_label.width(), self.src_search_label.height()
356 | )
357 | # 译文搜索框
358 | self.tgt_search_text.setGeometry(
359 | self.src_search_text.x(), self.src_search_text.height(),
360 | self.src_search_text.width(), self.src_search_text.height()
361 | )
362 | # 表格
363 | self.table_widget.setGeometry(
364 | 0, self.tgt_search_text.y() + self.tgt_search_text.height(),
365 | w, h-self.src_search_label.height()*3
366 | )
367 | # 导出按钮
368 | self.output_button.setGeometry(
369 | w-100*w_rate, self.table_widget.y()+self.table_widget.height(),
370 | 100*w_rate, self.src_search_text.height()
371 | )
372 | # 上一页按钮
373 | self.last_page_button.setGeometry(
374 | 325*w_rate, self.output_button.y(),
375 | self.output_button.width(), self.output_button.height()
376 | )
377 | # 页码输入框
378 | self.page_spinbox.setGeometry(
379 | self.last_page_button.x()+self.last_page_button.width()+25*w_rate, self.last_page_button.y(),
380 | self.last_page_button.width()//2, self.last_page_button.height()
381 | )
382 | # 页码标签
383 | self.page_label.setGeometry(
384 | self.page_spinbox.x()+self.page_spinbox.width(), self.page_spinbox.y(),
385 | self.last_page_button.width(), self.last_page_button.height()
386 | )
387 | # 下一页按钮
388 | self.next_page_button.setGeometry(
389 | self.output_button.x()-self.last_page_button.x(), self.last_page_button.y(),
390 | self.last_page_button.width(), self.last_page_button.height()
391 | )
392 |
393 |
394 | # 窗口关闭处理
395 | def closeEvent(self, event) :
396 |
397 | self.close()
398 | self.object.translation_ui.show()
399 | if self.object.range_ui.show_sign == True :
400 | self.object.range_ui.show()
--------------------------------------------------------------------------------
/ui/xiaoniu.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtWidgets import *
6 |
7 | import ui.static.icon
8 | import utils.test
9 | import webbrowser
10 |
11 |
12 | # 私人小牛设置界面
13 | class XiaoniuSetting(QWidget) :
14 |
15 | def __init__(self, object) :
16 |
17 | super(XiaoniuSetting, self).__init__()
18 | self.object = object
19 | self.logger = object.logger
20 | self.getInitConfig()
21 | self.ui()
22 |
23 |
24 | def ui(self) :
25 |
26 | # 窗口尺寸及不可拉伸
27 | self.resize(self.window_width, self.window_height)
28 | self.setMinimumSize(QSize(self.window_width, self.window_height))
29 | self.setMaximumSize(QSize(self.window_width, self.window_height))
30 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
31 |
32 | # 窗口标题
33 | self.setWindowTitle("私人小牛翻译设置 - 退出会自动保存")
34 | # 窗口图标
35 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
36 | # 鼠标样式
37 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
38 | # 界面样式
39 | self.setStyleSheet("QWidget { font: 9pt '华康方圆体W7';"
40 | "color: %s;"
41 | "background: rgba(255, 255, 255, 1); }"
42 | "QLineEdit { background: transparent;"
43 | "border-width:0;"
44 | "border-style:outset;"
45 | "border-bottom: 2px solid %s; }"
46 | "QLineEdit:focus { border-bottom: 2px "
47 | "dashed %s; }"
48 | "QLabel {color: %s;}"
49 | "QPushButton { background: %s; border-radius: %spx; color: rgb(255, 255, 255); }"
50 | "QPushButton:hover { background-color: #83AAF9; }"
51 | "QPushButton:pressed { background-color: #4480F9; padding-left: 3px; padding-top: 3px; }"
52 | %(self.color, self.color, self.color, self.color, self.color, 6.66*self.rate))
53 |
54 | # secret_id 输入框
55 | label = QLabel(self)
56 | self.customSetGeometry(label, 20, 20, 330, 20)
57 | label.setText("API-KEY: ")
58 | self.secret_key_text = QLineEdit(self)
59 | self.customSetGeometry(self.secret_key_text, 20, 40, 330, 25)
60 | self.secret_key_text.setText(self.object.config["xiaoniuAPI"])
61 | self.secret_key_text.setCursor(ui.static.icon.EDIT_CURSOR)
62 |
63 | # 测试按钮
64 | button = QPushButton(self)
65 | self.customSetGeometry(button, 65, 90, 60, 20)
66 | button.setText("测试")
67 | button.clicked.connect(lambda: utils.test.testXiaoniu(
68 | self.object, self.filterNullWord(self.secret_key_text)))
69 | button.setCursor(ui.static.icon.SELECT_CURSOR)
70 |
71 | # 注册按钮
72 | button = QPushButton(self)
73 | self.customSetGeometry(button, 155, 90, 60, 20)
74 | button.setText("注册")
75 | button.clicked.connect(self.openTutorial)
76 | button.setCursor(ui.static.icon.SELECT_CURSOR)
77 |
78 | # 查额度按钮
79 | button = QPushButton(self)
80 | self.customSetGeometry(button, 245, 90, 60, 20)
81 | button.setText("查额度")
82 | button.clicked.connect(self.openQueryQuota)
83 | button.setCursor(ui.static.icon.SELECT_CURSOR)
84 |
85 |
86 | # 初始化配置
87 | def getInitConfig(self) :
88 |
89 | # 界面缩放比例
90 | self.rate = self.object.yaml["screen_scale_rate"]
91 | # 界面尺寸
92 | self.window_width = int(370 * self.rate)
93 | self.window_height = int(140 * self.rate)
94 | # 颜色
95 | self.color = "#5B8FF9"
96 |
97 |
98 | # 根据分辨率定义控件位置尺寸
99 | def customSetGeometry(self, object, x, y, w, h) :
100 |
101 | object.setGeometry(QRect(int(x * self.rate),
102 | int(y * self.rate), int(w * self.rate),
103 | int(h * self.rate)))
104 |
105 |
106 | # 过滤空字符
107 | def filterNullWord(self, obj) :
108 |
109 | text = obj.text().strip()
110 | obj.setText(text)
111 | return text
112 |
113 |
114 | # 打开注册教程
115 | def openTutorial(self) :
116 |
117 | try :
118 | url = self.object.yaml["dict_info"]["xiaoniu_tutorial"]
119 | webbrowser.open(url, new=0, autoraise=True)
120 | except Exception :
121 | self.logger.error(format_exc())
122 | utils.message.MessageBox("私人小牛注册",
123 | "请尝试手动打开此地址:\n%s " % url)
124 |
125 |
126 | # 打开查询额度地址
127 | def openQueryQuota(self):
128 |
129 | url = "https://niutrans.com/cloud/service/flows"
130 | try:
131 | webbrowser.open(url, new=0, autoraise=True)
132 | except Exception :
133 | self.logger.error(format_exc())
134 | utils.message.MessageBox("私人小牛额度查询",
135 | "打开地址失败, 请尝试手动打开此网页下载\n%s " % url)
136 |
137 |
138 | # 窗口关闭处理
139 | def closeEvent(self, event) :
140 |
141 | self.object.config["xiaoniuAPI"] = self.filterNullWord(self.secret_key_text)
--------------------------------------------------------------------------------
/ui/youdao.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from PyQt5.QtCore import *
4 | from PyQt5.QtGui import *
5 | from PyQt5.QtWidgets import *
6 |
7 | import ui.static.icon
8 | import utils.test
9 | import webbrowser
10 |
11 |
12 | # 私人有道设置界面
13 | class YoudaoSetting(QWidget) :
14 |
15 | def __init__(self, object) :
16 |
17 | super(YoudaoSetting, self).__init__()
18 | self.object = object
19 | self.logger = object.logger
20 | self.getInitConfig()
21 | self.ui()
22 |
23 |
24 | def ui(self) :
25 |
26 | # 窗口尺寸及不可拉伸
27 | self.resize(self.window_width, self.window_height)
28 | self.setMinimumSize(QSize(self.window_width, self.window_height))
29 | self.setMaximumSize(QSize(self.window_width, self.window_height))
30 | self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
31 |
32 | # 窗口标题
33 | self.setWindowTitle("私人有道翻译设置 - 退出会自动保存")
34 | # 窗口图标
35 | self.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
36 | # 鼠标样式
37 | self.setCursor(ui.static.icon.PIXMAP_CURSOR)
38 | # 界面样式
39 | self.setStyleSheet("QWidget { font: 9pt '华康方圆体W7';"
40 | "color: %s;"
41 | "background: rgba(255, 255, 255, 1); }"
42 | "QLineEdit { background: transparent;"
43 | "border-width:0;"
44 | "border-style:outset;"
45 | "border-bottom: 2px solid %s; }"
46 | "QLineEdit:focus { border-bottom: 2px "
47 | "dashed %s; }"
48 | "QLabel {color: %s;}"
49 | "QPushButton { background: %s; border-radius: %spx; color: rgb(255, 255, 255); }"
50 | "QPushButton:hover { background-color: #83AAF9; }"
51 | "QPushButton:pressed { background-color: #4480F9; padding-left: 3px; padding-top: 3px; }"
52 | %(self.color, self.color, self.color, self.color, self.color, 6.66*self.rate))
53 |
54 | # secret_id 输入框
55 | label = QLabel(self)
56 | self.customSetGeometry(label, 20, 20, 330, 20)
57 | label.setText("应用ID: ")
58 | self.secret_id_text = QLineEdit(self)
59 | self.customSetGeometry(self.secret_id_text, 20, 40, 330, 25)
60 | self.secret_id_text.setText(self.object.config["youdaoAPI"]["Key"])
61 | self.secret_id_text.setCursor(ui.static.icon.EDIT_CURSOR)
62 |
63 | # secret_key 输入框
64 | label = QLabel(self)
65 | self.customSetGeometry(label, 20, 80, 330, 20)
66 | label.setText("应用秘钥: ")
67 | self.secret_key_text = QLineEdit(self)
68 | self.customSetGeometry(self.secret_key_text, 20, 100, 330, 25)
69 | self.secret_key_text.setText(self.object.config["youdaoAPI"]["Secret"])
70 | self.secret_key_text.setCursor(ui.static.icon.EDIT_CURSOR)
71 |
72 | # 测试按钮
73 | button = QPushButton(self)
74 | self.customSetGeometry(button, 65, 150, 60, 20)
75 | button.setText("测试")
76 | button.clicked.connect(lambda: utils.test.testYoudao(
77 | self.object, self.filterNullWord(self.secret_id_text), self.filterNullWord(self.secret_key_text)))
78 | button.setCursor(ui.static.icon.SELECT_CURSOR)
79 |
80 | # 注册按钮
81 | button = QPushButton(self)
82 | self.customSetGeometry(button, 155, 150, 60, 20)
83 | button.setText("注册")
84 | button.clicked.connect(self.openTutorial)
85 | button.setCursor(ui.static.icon.SELECT_CURSOR)
86 |
87 | # 查额度按钮
88 | button = QPushButton(self)
89 | self.customSetGeometry(button, 245, 150, 60, 20)
90 | button.setText("查额度")
91 | button.clicked.connect(self.openQueryQuota)
92 | button.setCursor(ui.static.icon.SELECT_CURSOR)
93 |
94 |
95 | # 初始化配置
96 | def getInitConfig(self) :
97 |
98 | # 界面缩放比例
99 | self.rate = self.object.yaml["screen_scale_rate"]
100 | # 界面尺寸
101 | self.window_width = int(370 * self.rate)
102 | self.window_height = int(200 * self.rate)
103 | # 颜色
104 | self.color = "#5B8FF9"
105 |
106 |
107 | # 根据分辨率定义控件位置尺寸
108 | def customSetGeometry(self, object, x, y, w, h) :
109 |
110 | object.setGeometry(QRect(int(x * self.rate),
111 | int(y * self.rate), int(w * self.rate),
112 | int(h * self.rate)))
113 |
114 |
115 | # 过滤空字符
116 | def filterNullWord(self, obj) :
117 |
118 | text = obj.text().strip()
119 | obj.setText(text)
120 | return text
121 |
122 |
123 | # 打开注册教程
124 | def openTutorial(self) :
125 |
126 | try :
127 | url = self.object.yaml["dict_info"]["youdao_tutorial"]
128 | webbrowser.open(url, new=0, autoraise=True)
129 | except Exception :
130 | self.logger.error(format_exc())
131 | utils.message.MessageBox("私人有道注册",
132 | "请尝试手动打开此地址:\n%s " % url)
133 |
134 |
135 | # 打开查询额度地址
136 | def openQueryQuota(self):
137 |
138 | url = "https://ai.youdao.com/console/#/service-singleton/text-translation"
139 | try:
140 | webbrowser.open(url, new=0, autoraise=True)
141 | except Exception :
142 | self.logger.error(format_exc())
143 | utils.message.MessageBox("私人有道额度查询",
144 | "打开地址失败, 请尝试手动打开此网页下载\n%s " % url)
145 |
146 |
147 | # 窗口关闭处理
148 | def closeEvent(self, event) :
149 |
150 | self.object.config["youdaoAPI"]["Key"] = self.filterNullWord(self.secret_id_text)
151 | self.object.config["youdaoAPI"]["Secret"] = self.filterNullWord(self.secret_key_text)
--------------------------------------------------------------------------------
/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PantsuDango/Dango-Translator/7c08fd3d3e43204b1fee8169da08838cf1ba6a28/utils/__init__.py
--------------------------------------------------------------------------------
/utils/check_font.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import locale
4 | import tkinter.font
5 | from traceback import format_exc
6 |
7 | import utils.message
8 |
9 |
10 | FONT_PATH = os.path.join(os.getcwd(), "config", "other", "华康方圆体W7.TTC")
11 |
12 |
13 | # 检查字体是否存在
14 | def checkFont(logger) :
15 |
16 | try :
17 | if locale.getdefaultlocale()[1] != "cp936" :
18 | return
19 | except Exception :
20 | logger.error(format_exc())
21 |
22 | tkinter.Tk()
23 | font_list = tkinter.font.families()
24 |
25 | if "华康方圆体W7" not in font_list :
26 | utils.message.checkFontMessageBox("字体文件缺失",
27 | "字体文字缺失,请先安装字体文件\n"
28 | "它会使你的界面更好看ヾ(๑╹◡╹)ノ\" \n"
29 | "安装完毕后需重新打开翻译器!", logger)
30 |
31 |
32 | # 打开字体文件
33 | def openFontFile(logger) :
34 |
35 | try :
36 | os.startfile(FONT_PATH)
37 | except Exception :
38 | logger.error(format_exc())
39 | utils.message.MessageBox("打开字体文件失败",
40 | "由于某种神秘力量,打开字体文件失败了(◢д◣)\n"
41 | "请手动打开安装,字体文件路径如下:\n"
42 | "%s "%FONT_PATH)
43 | sys.exit()
--------------------------------------------------------------------------------
/utils/email.py:
--------------------------------------------------------------------------------
1 | from PyQt5.QtCore import *
2 |
3 | import utils.http
4 | import utils.message
5 |
6 |
7 | # 发送邮件线程
8 | class SendEmail(QThread) :
9 |
10 | signal = pyqtSignal(bool, str)
11 |
12 | def __init__(self, url, user, email, code_key, logger) :
13 |
14 | super(SendEmail, self).__init__()
15 | self.url = url
16 | self.user = user
17 | self.email = email
18 | self.code_key = code_key
19 | self.logger = logger
20 |
21 |
22 | def run(self) :
23 |
24 | body = {
25 | "User": self.user,
26 | "Email": self.email,
27 | "CodeKey": self.code_key
28 | }
29 |
30 | # 请求注册服务器
31 | res = utils.http.post(self.url, body, self.logger)
32 | if not res :
33 | self.url = "https://trans.dango.cloud/DangoTranslate/SendEmail"
34 | res = utils.http.post(self.url, body, self.logger, timeout=10)
35 | result = res.get("Status", "")
36 | error = res.get("Error", "")
37 | if result != "Success" :
38 | self.logger.error(error)
39 | self.signal.emit(False, error)
40 | else :
41 | self.signal.emit(True, error)
42 |
43 |
44 |
45 | # 检查是否绑定邮箱
46 | def bindEmail(object, user="") :
47 |
48 | url = object.yaml["dict_info"]["dango_check_email"]
49 | if user :
50 | body = {"User": user}
51 | else :
52 | body = {"User": object.yaml["user"]}
53 | res = utils.http.post(url, body, object.logger)
54 | if not res:
55 | url = "https://trans.dango.cloud/DangoTranslate/CheckEmail"
56 | res = utils.http.post(url, body, object.logger)
57 | if res.get("Status", "") == "Success" :
58 | return res.get("Result", {}).get("Email", "")
59 | else :
60 | return False
--------------------------------------------------------------------------------
/utils/enctry.py:
--------------------------------------------------------------------------------
1 | # 加蜜,参考https://blog.csdn.net/qq_28905087/article/details/107467795
2 | def enctry(s):
3 | k = 'q%UyQ1.gJTm>|?86*|T]JMfA6?EWmL-~%=]pq!~}'
4 | encry_str = ""
5 | for i, j in zip(s, k):
6 | temp = str(ord(i) + ord(j)) + '%6?u!'
7 | encry_str = encry_str + temp
8 | return encry_str
9 |
10 |
11 | def dectry(p):
12 | k = 'q%UyQ1.gJTm>|?86*|T]JMfA6?EWmL-~%=]pq!~}'
13 | dec_str = ""
14 | for i, j in zip(p.split("%6?u!")[:-1], k):
15 | temp = chr(int(i) - ord(j))
16 | dec_str = dec_str + temp
17 | return dec_str
18 |
--------------------------------------------------------------------------------
/utils/http.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 | import time
4 | from traceback import format_exc
5 | import utils.enctry
6 |
7 |
8 | # 发送http请求
9 | def post(url, body, logger, headers=None, timeout=5) :
10 |
11 | proxies = {
12 | "http": None,
13 | "https": None
14 | }
15 | result = {}
16 |
17 | try :
18 | # 消除https警告
19 | requests.packages.urllib3.disable_warnings()
20 | except Exception :
21 | pass
22 |
23 | response = None
24 | try :
25 | if headers :
26 | response = requests.post(url, headers=headers, data=json.dumps(body), proxies=proxies, verify=False, timeout=timeout)
27 | else :
28 | response = requests.post(url, data=json.dumps(body), proxies=proxies, verify=False, timeout=timeout)
29 | try :
30 | response.encoding = "utf-8"
31 | result = json.loads(response.text)
32 | response.close()
33 | except Exception :
34 | response.encoding = "gb18030"
35 | result = json.loads(response.text)
36 | except json.decoder.JSONDecodeError :
37 | try :
38 | logger.error("post %s error, httpcode is %s, response is %s"%(url, response.status_code, response.text))
39 | except Exception :
40 | pass
41 | except Exception :
42 | logger.error(format_exc())
43 |
44 |
45 | return result
46 |
47 |
48 | # 登录ocr服务器
49 | def loginDangoOCR(object) :
50 |
51 | url = object.yaml["dict_info"]["ocr_login"]
52 |
53 | psw = str(object.yaml["password"])
54 | if psw.find('%6?u!') != -1:
55 | psw = utils.enctry.dectry(psw)
56 | body = {
57 | "User": object.yaml["user"],
58 | "Password": psw,
59 | }
60 |
61 | for x in range(3) :
62 | res = post(url, body, object.logger)
63 | token = res.get("Token", "")
64 | code = res.get("Code", -1)
65 | if token :
66 | object.config["DangoToken"] = token
67 | break
68 | else :
69 | if code != 0 :
70 | object.logger.error(res.get("ErrorMsg", ""))
71 |
72 |
73 | # 下载文件
74 | def downloadFile(url, save_path, logger) :
75 |
76 | try :
77 | res = requests.get(url, stream=True)
78 | content = res.content
79 | with open(save_path, "wb") as file :
80 | file.write(content)
81 | return True
82 | except Exception :
83 | logger.error(format_exc())
84 | return False
85 |
86 |
87 | # 发送get请求
88 | def get(url, logger, timeout=5) :
89 |
90 | try :
91 | res = requests.get(url, stream=True, timeout=timeout)
92 | return res.text
93 | except Exception :
94 | logger.error(format_exc())
95 |
96 |
97 | # 测试在线ocr节点可用性
98 | def getOCR(url) :
99 |
100 | proxies = {"http": None, "https": None}
101 | start = time.time()
102 | try :
103 | res = requests.get(url, proxies=proxies, verify=False, timeout=3)
104 | time_diff = time.time() - start
105 | status_code = res.status_code
106 | except Exception :
107 | return None, 0
108 | if status_code != 200 and status_code != 404 :
109 | return None, 0
110 |
111 | return True, int(time_diff*1000)
112 |
113 |
114 | # 查询在线OCR额度
115 | def onlineOCRQueryQuota(object) :
116 |
117 | url = "%s?Token=%s"%(object.yaml["dict_info"]["ocr_query_quota"], object.config["DangoToken"])
118 | try :
119 | res = post(url, {}, object.logger)
120 | if len(res["Result"]) == 0 :
121 | return "您尚未购买过在线OCR, 请先购买后再查询有效期"
122 | max_end_time = ""
123 | for val in res["Result"] :
124 | if "文字识别" not in val["PackName"] :
125 | continue
126 | if val["EndTime"] > max_end_time :
127 | max_end_time = val["EndTime"]
128 | now_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
129 | if now_time > max_end_time :
130 | return "您的有效期截止至:\n\n%s\n\n在线OCR已不可使用, 若要继续使用请购买\n\n对额度如有任何疑问请联系客服娘"%max_end_time
131 | else :
132 | return "您的有效期截止至:\n\n%s\n\n在线OCR还可以继续使用~\n\n对额度如有任何疑问请联系客服娘" % max_end_time
133 | except Exception :
134 | object.logger.error(format_exc())
135 | return "查询出错, 可联系客服娘, 或直接浏览器登录 https://cloud.stariver.org/auth/login.html 地址查看"
136 |
137 |
138 | # 登录检查
139 | def loginCheck(object) :
140 |
141 | url = object.yaml["dict_info"]["dango_login"]
142 | user = str(object.yaml["user"])
143 | password = str(object.yaml["password"])
144 | if password.find('%6?u!') != -1:
145 | password = utils.enctry.dectry(password)
146 | body = {
147 | "User": user,
148 | "Password": password
149 | }
150 | resp = post(url=url, body=body, logger=object.logger)
151 | if not resp:
152 | url = "https://trans.dango.cloud/DangoTranslate/Login"
153 | resp = post(url=url, body=body, logger=object.logger)
154 | # 如果因为网络原因登录失败
155 | if not resp :
156 | return "网络错误\n具体情况可查阅错误日志\n并通过交流群联系客服娘处理 "
157 |
158 | result = resp.get("Result", "")
159 | if result == "" :
160 | object.logger.error("登录出错, response: %s"%resp)
161 | return "网络错误\n具体情况可查阅错误日志\n并通过交流群联系客服娘处理 "
162 |
163 | if result == "User dose not exist":
164 | return "用户名不存在, 请先注册! "
165 |
166 | elif result == "Password error":
167 | return "用户名和密码不匹配 "
168 |
169 | elif result == "User is black list":
170 | return "账户已被纳入黑名单 "
171 |
172 | elif result == "OK":
173 | # 保存配置
174 | object.yaml["user"] = user
175 | object.yaml["password"] = utils.enctry.enctry(password)
176 | utils.config.saveConfig(object.yaml, object.logger)
177 |
178 | else:
179 | object.logger.error("登录出错, response: %s"%resp)
180 | return "出现了出乎意料的情况\n请联系团子解决! "
181 |
182 |
183 | # 查询在线ocr试用次数
184 | def ocrProbationReadCount(object) :
185 |
186 | url = object.yaml["dict_info"]["ocr_probation_read_count"]
187 | body = {"Username": object.yaml["user"]}
188 | resp = post(url=url, body=body, logger=object.logger)
189 | if not resp :
190 | return
191 | if resp.get("Code", -1) != 0 :
192 | return
193 | count = resp.get("Data", 0)
194 | object.settin_ui.online_ocr_probation_label.setText("试用在线OCR, 剩余%d次"%count)
195 |
196 |
197 | # 查询漫画OCR额度
198 | def mangaOCRQueryQuota(object) :
199 |
200 | url = "%s?Token=%s"%(object.yaml["dict_info"]["ocr_query_quota"], object.config["DangoToken"])
201 | try :
202 | res = post(url, {}, object.logger)
203 | # 未购买
204 | if len(res["Result"]) == 0 :
205 | return False, ""
206 | max_end_time = ""
207 | for val in res["Result"] :
208 | if "漫画" not in val["PackName"] :
209 | continue
210 | if val["EndTime"] > max_end_time :
211 | max_end_time = val["EndTime"]
212 | now_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
213 | # 已过期
214 | if now_time > max_end_time :
215 | return False, max_end_time
216 | else :
217 | # 未过期
218 | return True, max_end_time
219 | except Exception :
220 | object.logger.error(format_exc())
221 | return False, "查询出错"
--------------------------------------------------------------------------------
/utils/hwnd.py:
--------------------------------------------------------------------------------
1 | import time
2 | from win32 import win32api, win32gui, win32print
3 | from win32.lib import win32con
4 | from traceback import format_exc
5 |
6 | import utils.thread
7 |
8 |
9 | # 窗口句柄操作
10 | class WindowHwnd() :
11 |
12 | def __init__(self, object) :
13 |
14 | self.object = object
15 | self.logger = object.logger
16 | self.translation_ui_hwnd = self.object.translation_ui.winId()
17 | self.range_ui_hwnd = self.object.range_ui.winId()
18 |
19 |
20 | # 设置窗口置顶且无焦点
21 | def setTop(self, hwnd) :
22 |
23 | try :
24 | # 校验句柄是否有效
25 | if not win32gui.IsWindow(hwnd) :
26 | return
27 | while True :
28 | time.sleep(0.5)
29 | # 如果置顶开关被关闭则直接结束
30 | if not self.object.settin_ui.set_top_use :
31 | return
32 | # 判断是否有全屏程序
33 | if not self.checkIsFullScreen() :
34 | continue
35 | # 窗口无焦点
36 | win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, win32con.WS_EX_NOACTIVATE)
37 | # 防止黑屏
38 | if hwnd == self.range_ui_hwnd :
39 | self.object.range_ui.drag_label.setStyleSheet("background-color:none")
40 | if hwnd == self.translation_ui_hwnd :
41 | self.object.translation_ui.drag_label.setStyleSheet("{background-color:none}")
42 | while True :
43 | time.sleep(0.5)
44 | # 窗口置顶
45 | win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOSIZE | win32con.SWP_NOMOVE)
46 | # 如果退出了全屏
47 | rect_desk = win32gui.GetWindowRect(win32gui.GetDesktopWindow())
48 | if win32gui.GetWindowRect(self.full_screen_hwnd) != rect_desk :
49 | self.releaseFocus(hwnd)
50 | break
51 | # 如果置顶开关被关闭则直接结束
52 | if not self.object.settin_ui.set_top_use :
53 | self.releaseFocus(hwnd)
54 | return
55 | except Exception :
56 | self.logger.error(format_exc())
57 |
58 |
59 | # 解除窗口焦点
60 | def releaseFocus(self, hwnd) :
61 |
62 | # 范围界面不需要显示焦点
63 | if hwnd == self.range_ui_hwnd :
64 | return
65 | try :
66 | if not win32gui.IsWindow(hwnd) :
67 | return
68 | # 恢复窗口焦点
69 | style = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
70 | win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, style & ~ win32con.WS_EX_NOACTIVATE)
71 | except Exception :
72 | self.logger.error(format_exc())
73 |
74 |
75 | # 判断当前激活窗体是否为全屏
76 | def checkIsFullScreen(self) :
77 |
78 | rect_desk = win32gui.GetWindowRect(win32gui.GetDesktopWindow())
79 | hwnd = win32gui.GetForegroundWindow()
80 | title = win32gui.GetWindowText(hwnd)
81 | if hwnd == win32gui.GetDesktopWindow() or title == "":
82 | return False
83 | if win32gui.GetWindowRect(hwnd) != rect_desk :
84 | return False
85 |
86 | self.full_screen_hwnd = hwnd
87 | return True
88 |
89 |
90 | def run(self) :
91 |
92 | utils.thread.createThread(self.setTop, self.translation_ui_hwnd)
93 | utils.thread.createThread(self.setTop, self.range_ui_hwnd)
94 |
--------------------------------------------------------------------------------
/utils/logger.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import time
4 | import datetime
5 |
6 | LOG_PATH = "../logs/"
7 |
8 | # 设置日志文件
9 | def setLog() :
10 |
11 | logger = logging.getLogger()
12 | logger.setLevel(logging.INFO)
13 |
14 | date = time.strftime("%Y-%m-%d", time.localtime(time.time()))
15 | log_file_name = date + ".log"
16 | logPath = LOG_PATH + log_file_name
17 |
18 | try :
19 | os.makedirs("../logs")
20 | except FileExistsError :
21 | pass
22 |
23 | file_handler = logging.FileHandler(logPath, mode="a+", encoding="utf-8")
24 | file_handler.setLevel(logging.DEBUG)
25 |
26 | formatter = logging.Formatter("[%(asctime)s][%(pathname)s-line:%(lineno)d][%(levelname)s]\n%(message)s")
27 | file_handler.setFormatter(formatter)
28 | logger.addHandler(file_handler)
29 |
30 | return logger
31 |
32 |
33 | # 清理日志
34 | def clearLog() :
35 | log_list = os.listdir(LOG_PATH)
36 | for filename in log_list :
37 | if ".log" not in filename :
38 | continue
39 | log_date = filename.split(".log")[0]
40 | time_point = (datetime.datetime.now() + datetime.timedelta(days=-7)).strftime("%Y-%m-%d")
41 | if log_date < time_point :
42 | os.remove(LOG_PATH+filename)
43 |
--------------------------------------------------------------------------------
/utils/message.py:
--------------------------------------------------------------------------------
1 | from PyQt5.QtCore import *
2 | from PyQt5.QtWidgets import *
3 | import sys
4 | import os
5 | import time
6 | import webbrowser
7 |
8 | import utils.check_font
9 | import utils.offline_ocr
10 | import ui.static.icon
11 |
12 |
13 | # 创建基础消息窗
14 | def createMessageBox(title, text, rate=1) :
15 |
16 | message_box = QMessageBox()
17 | message_box.setTextInteractionFlags(Qt.TextSelectableByMouse)
18 | message_box.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowMaximizeButtonHint | Qt.MSWindowsFixedSizeDialogHint)
19 | message_box.setCursor(ui.static.icon.PIXMAP_CURSOR)
20 | message_box.setWindowIcon(ui.static.icon.APP_LOGO_ICON)
21 | message_box.setWindowTitle(title)
22 | message_box.setText(text)
23 |
24 | return message_box
25 |
26 |
27 | # 消息提示窗口-通用
28 | def MessageBox(title, text, rate=1):
29 |
30 | message_box = createMessageBox(title, text, rate)
31 | message_box.addButton(QPushButton("好滴"), QMessageBox.YesRole)
32 | message_box.exec_()
33 |
34 |
35 | # 检查chrome浏览器提示窗口
36 | def checkChromeMessageBox(title, text, rate=1):
37 |
38 | message_box = createMessageBox(title, text, rate)
39 | button = QPushButton("下载")
40 | message_box.addButton(button, QMessageBox.YesRole)
41 | button.clicked.connect(lambda: webbrowser.open("https://www.google.cn/chrome/index.html", new=0, autoraise=True))
42 | message_box.addButton(QPushButton("取消"), QMessageBox.NoRole)
43 | message_box.exec_()
44 |
45 |
46 | # 错误提示窗口-字体检查提示
47 | def checkFontMessageBox(title, text, logger, rate=1):
48 |
49 | message_box = createMessageBox(title, text, rate)
50 | button = QPushButton("好滴")
51 | button.clicked.connect(lambda: utils.check_font.openFontFile(logger))
52 | message_box.addButton(button, QMessageBox.YesRole)
53 | message_box.addButton(QPushButton("忽略"), QMessageBox.NoRole)
54 | message_box.exec_()
55 |
56 |
57 | # 错误提示窗口-绑定邮箱提示
58 | def checkEmailMessageBox(title, text, object) :
59 |
60 | message_box = createMessageBox(title, text, object.yaml["screen_scale_rate"])
61 | button = QPushButton("好滴")
62 | button.clicked.connect(object.register_ui.clickBindEmail)
63 | message_box.addButton(button, QMessageBox.YesRole)
64 | message_box.addButton(QPushButton("忽略"), QMessageBox.NoRole)
65 | message_box.exec_()
66 |
67 |
68 | # 消息提示窗口-停止进度条
69 | def closeProcessBarMessageBox(title, text, object) :
70 |
71 | message_box = createMessageBox(title, text, object.rate)
72 | button = QPushButton("停止")
73 | button.clicked.connect(object.stopProcess)
74 | message_box.addButton(button, QMessageBox.YesRole)
75 | message_box.addButton(QPushButton("取消"), QMessageBox.NoRole)
76 |
77 | message_box.exec_()
78 |
79 |
80 | # 错误提示窗口-卸载本地OCR
81 | def uninstallOfflineOCRMessageBox(title, text, object) :
82 |
83 | message_box = createMessageBox(title, text, object.yaml["screen_scale_rate"])
84 | button = QPushButton("卸载")
85 | button.clicked.connect(lambda: utils.offline_ocr.uninstall_offline_ocr(object))
86 | message_box.addButton(button, QMessageBox.YesRole)
87 | message_box.addButton(QPushButton("取消"), QMessageBox.NoRole)
88 |
89 | message_box.exec_()
90 |
91 |
92 | # 打开自动更新程序
93 | def updateVersion() :
94 |
95 | try :
96 | os.chdir(os.path.abspath(".."))
97 | os.startfile("自动更新程序.exe")
98 | except Exception as err :
99 | MessageBox("自动更新失败", "打开自动更新程序失败:\n%s"%err)
100 |
101 | sys.exit()
102 |
103 |
104 | # 错误提示窗口-更新版本用
105 | def checkVersionMessageBox(title, text, rate=1) :
106 |
107 | message_box = createMessageBox(title, text, rate)
108 | button = QPushButton("好滴")
109 | button.clicked.connect(updateVersion)
110 | message_box.addButton(button, QMessageBox.YesRole)
111 | message_box.addButton(QPushButton("忽略"), QMessageBox.NoRole)
112 |
113 | message_box.exec_()
114 |
115 |
116 | # 错误提示窗口-关闭程序用
117 | def quitAppMessageBox(title, text, object, rate=1) :
118 |
119 | message_box = createMessageBox(title, text, rate)
120 | button = QPushButton("再见")
121 | button.clicked.connect(object.translation_ui.quit)
122 | message_box.addButton(button, QMessageBox.YesRole)
123 | message_box.addButton(QPushButton("我点错了"), QMessageBox.NoRole)
124 |
125 | message_box.exec_()
126 |
127 |
128 | # 服务连接失败提示
129 | def serverClientFailMessage(object) :
130 |
131 | date = time.strftime("%Y-%m-%d", time.localtime(time.time()))
132 | log_file_name = date + ".log"
133 | utils.message.MessageBox("连接服务器失败",
134 | "无法连接服务器, 请检查你的网络环境是否正常\n"
135 | "并留意是否开启了梯子、加速器等代理软件, 如有可尝试关闭后重试 \n"
136 | "若仍无法解决, 可获取日志文件并通过交流群联系客服处理\n"
137 | "日志文件地址:\n"
138 | "%s"%os.path.join(os.path.abspath("../") , "logs", log_file_name), object.yaml["screen_scale_rate"])
139 | sys.exit()
140 |
141 |
142 | # 检查是否是最新版本
143 | def showCheckVersionMessage(object) :
144 |
145 | message = object.yaml["dict_info"]["update_version_message"]
146 | text = ""
147 | text_list = message.split(r"\n")
148 | for index, val in enumerate(text_list) :
149 | if index+1 == len(text_list) :
150 | text += val
151 | else :
152 | text += val + "\n"
153 | checkVersionMessageBox("检查版本更新",
154 | "%s "%text)
155 |
156 |
157 | # 检查是否为测试版本
158 | def checkIsTestVersion(object) :
159 |
160 | if "Beta" in object.yaml["version"] and object.yaml["dict_info"]["test_version_switch"] != "1" :
161 | MessageBox("此版本已停止服务",
162 | "目前您使用的是测试版本, 此版本已经停止更新 \n"
163 | "请下载正式版本使用, 下载地址:\n%s "
164 | %object.yaml["dict_info"]["dango_home_page"], object.yaml["screen_scale_rate"])
165 | sys.exit()
--------------------------------------------------------------------------------
/utils/offline_ocr.py:
--------------------------------------------------------------------------------
1 | from PyQt5.QtCore import *
2 | from traceback import format_exc
3 | import shutil
4 | import os
5 | import time
6 | import requests
7 | import zipfile
8 |
9 | import utils.message
10 | import utils.thread
11 | import ui.progress_bar
12 |
13 | OCR_INSTALL_PATH = "ocr"
14 | OCR_ZIP_FILE_NAME = "ocr.zip"
15 |
16 |
17 | # 安装本地ocr
18 | def install_offline_ocr(object) :
19 |
20 | # 判断本地ocr是否已安装
21 | if os.path.exists(object.yaml["ocr_cmd_path"]) :
22 | utils.message.MessageBox("安装失败",
23 | "本地OCR已安装, 不需要重复安装! ")
24 | return
25 |
26 | try :
27 | # 杀死本地ocr进程
28 | killOfflineOCR(object.yaml["port"])
29 | # 删除旧文件
30 | shutil.rmtree(OCR_INSTALL_PATH)
31 | except Exception :
32 | object.logger.error(format_exc())
33 |
34 | object.settin_ui.progress_bar.finish_sign = False
35 | object.settin_ui.progress_bar.show()
36 | # 安装本地ocr
37 | thread = InstallThread(object=object,
38 | file_name=OCR_ZIP_FILE_NAME,
39 | unzip_path=OCR_INSTALL_PATH)
40 | thread.progress_bar_sign.connect(object.settin_ui.progress_bar.paintProgressBar)
41 | thread.progress_bar_modify_title_sign.connect(object.settin_ui.progress_bar.modifyTitle)
42 | thread.progress_bar_close_sign.connect(object.settin_ui.closeProcessBar)
43 | thread = utils.thread.runQThread(thread)
44 |
45 |
46 | # 杀死本地ocr进程
47 | def killOfflineOCR(port) :
48 |
49 | try :
50 | popen_content = os.popen("netstat -ano |findstr %s"%port).read()
51 | if popen_content :
52 | pid = popen_content.split(" ")[-1]
53 | os.popen("taskkill /f /t /im %s"%pid)
54 | except Exception :
55 | return format_exc()
56 |
57 |
58 | # 是否卸载本地ocr
59 | def whether_uninstall_offline_ocr(object) :
60 |
61 | # 判断本地ocr是否已安装
62 | if not os.path.exists(OCR_INSTALL_PATH):
63 | utils.message.MessageBox("卸载本地OCR失败",
64 | "本地OCR未安装! ")
65 | return
66 |
67 | utils.message.uninstallOfflineOCRMessageBox("卸载本地OCR",
68 | "卸载后将无法使用本地OCR\n是否真的要执行卸载 ",
69 | object)
70 |
71 |
72 | # 卸载本地ocr
73 | def uninstall_offline_ocr(object) :
74 |
75 | # 杀死本地ocr进程解除占用
76 | killOfflineOCR(object.yaml["port"])
77 |
78 | # 删除本地ocr
79 | try:
80 | shutil.rmtree(OCR_INSTALL_PATH)
81 | object.logger.error(format_exc())
82 | utils.message.MessageBox("卸载本地OCR", "卸载完成! ")
83 | except PermissionError:
84 | utils.message.MessageBox("卸载本地OCR", "卸载失败了, 请先关闭本地ocr后再尝试卸载! ")
85 | except Exception:
86 | object.logger.error(format_exc())
87 | utils.message.MessageBox("卸载本地OCR", "卸载失败了, 原因: %s" % format_exc())
88 |
89 |
90 | # 下载
91 | class InstallThread(QThread) :
92 |
93 | progress_bar_sign = pyqtSignal(float, int, str)
94 | progress_bar_modify_title_sign = pyqtSignal(str)
95 | progress_bar_close_sign = pyqtSignal(str, str)
96 |
97 | def __init__(self, object, file_name, unzip_path) :
98 |
99 | super(InstallThread, self).__init__()
100 | self.object = object
101 | self.logger = object.logger
102 | self.url = object.yaml["dict_info"]["ocr_install_url"]
103 | self.file_name = file_name
104 | self.unzip_path = unzip_path
105 | self.object.settin_ui.progress_bar.stop_sign = False
106 |
107 |
108 | # 下载
109 | def download(self) :
110 |
111 | size = 0
112 | chunk_size = 1024
113 | try :
114 | response = requests.get(self.url, stream=True, timeout=5)
115 | content_size = int(response.headers["content-length"])
116 | if response.status_code != 200 :
117 | return "下载链接失效, 原因:\nhttp.status_code %d"%response.status_code
118 |
119 | # 显示文件大小
120 | file_size = content_size / chunk_size / 1024
121 | file_size_content = "{:.2f} MB".format(content_size / chunk_size / 1024)
122 | with open(self.file_name, "wb") as file :
123 | for data in response.iter_content(chunk_size=chunk_size):
124 | file.write(data)
125 | size += len(data)
126 | # 显示当前进度
127 | now_size_content = "{:.2f} MB".format(size / content_size * file_size)
128 | self.progress_bar_sign.emit(float(size / content_size * 100),
129 | int(size / content_size * 100),
130 | "%s/%s"%(now_size_content, file_size_content))
131 | # 如果收到停止信号
132 | if self.object.settin_ui.progress_bar.stop_sign :
133 | break
134 | response.close()
135 | return None
136 |
137 | except Exception :
138 | self.logger.error(format_exc())
139 | try :
140 | os.remove(file_name)
141 | except Exception :
142 | pass
143 |
144 | return "下载被中断, 原因:\n%s"%format_exc()
145 |
146 |
147 | # 编码转换
148 | def decode(self, str) :
149 |
150 | try :
151 | string = str.encode("cp437").decode("gbk")
152 | except Exception :
153 | string = str.encode("utf-8").decode("utf-8")
154 |
155 | return string
156 |
157 |
158 | # 解压
159 | def unzip(self) :
160 |
161 | try :
162 | zip_file = zipfile.ZipFile(self.file_name)
163 | zip_list = zip_file.infolist()
164 | # 先计算总解压大小
165 | all_size = 0
166 | for f in zip_list :
167 | all_size += f.file_size
168 | all_size_content = "{:.2f} MB".format(all_size / 1024 / 1024)
169 | # 执行解压
170 | now_size = 0
171 | for f in zip_list :
172 | zip_file.extract(f, self.unzip_path)
173 | # 中文乱码重命名
174 | old_name = os.path.join("ocr", f.filename)
175 | new_name = old_name.encode('cp437').decode('gbk')
176 | os.rename(old_name, new_name)
177 | # 显示当前进度
178 | now_size += f.file_size
179 | now_size_content = "{:.2f} MB".format(now_size / 1024 / 1024)
180 | self.progress_bar_sign.emit(float(now_size / all_size * 100),
181 | int(now_size / all_size * 100),
182 | "%s/%s"%(now_size_content, all_size_content))
183 | # 如果收到停止信号
184 | if self.object.settin_ui.progress_bar.stop_sign :
185 | break
186 | zip_file.close()
187 | # 删除压缩包
188 | os.remove(self.file_name)
189 | except Exception :
190 | self.logger.error(format_exc())
191 | return format_exc()
192 |
193 |
194 | def run(self) :
195 |
196 | # 下载
197 | self.progress_bar_modify_title_sign.emit("下载本地OCR -- 下载中请勿关闭此窗口")
198 | err = self.download()
199 | if err :
200 | utils.message.MessageBox("安装本地ocr失败", "原因: %s"%err)
201 | return
202 |
203 | # 如果收到停止信号
204 | if self.object.settin_ui.progress_bar.stop_sign :
205 | os.remove(self.file_name)
206 | return
207 |
208 | # 解压
209 | self.progress_bar_modify_title_sign.emit("解压本地OCR -- 解压中请勿关闭此窗口")
210 | err = self.unzip()
211 | if err :
212 | utils.message.MessageBox("安装本地ocr失败",
213 | "解压%s失败, 原因:\n%s"%(self.file_name, format_exc()))
214 | return
215 |
216 | # 如果收到停止信号
217 | if self.object.settin_ui.progress_bar.stop_sign :
218 | shutil.rmtree(OCR_INSTALL_PATH)
219 | return
220 |
221 | self.object.settin_ui.progress_bar.finish_sign = True
222 | self.progress_bar_close_sign.emit("安装本地ocr完成",
223 | "请点击本地ocr的运行按钮, 待运行完毕后再打开本地ocr的开关, 使用过程中切勿关闭本地ocr的运行小黑窗")
--------------------------------------------------------------------------------
/utils/port.py:
--------------------------------------------------------------------------------
1 | import socket
2 |
3 |
4 | def detectPort(port=6666) :
5 |
6 | s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
7 | try:
8 | s.connect(("127.0.0.1", int(port)))
9 | s.shutdown(2)
10 | sign = True
11 | except Exception :
12 | sign = False
13 | s.close()
14 |
15 | return sign
--------------------------------------------------------------------------------
/utils/range.py:
--------------------------------------------------------------------------------
1 | # 判断矩形是否碰撞
2 | class Rectangular() :
3 |
4 | def __init__(self, x, y, w, h):
5 |
6 | self.x0 = x
7 | self.y0 = y
8 | self.x1 = x + w
9 | self.y1 = y + h
10 | self.w = w
11 | self.h = h
12 |
13 |
14 | def __gt__(self, other) :
15 |
16 | if self.w > other.w and self.h > other.h:
17 | return True
18 | return False
19 |
20 |
21 | def __lt__(self, other) :
22 | if self.w < other.w and self.h < other.h:
23 | return True
24 | return False
25 |
26 |
27 | def collision(self, r2) :
28 |
29 | if self.x0 < r2.x1 and self.y0 < r2.y1 and self.x1 > r2.x0 and self.y1 > r2.y0:
30 | return True
31 | return False
32 |
33 |
34 | # 竖向-创建用于计算碰撞的矩形框对象
35 | def createRectangularMD(val, word_width):
36 | return Rectangular(val["Coordinate"]["UpperLeft"][0],
37 | val["Coordinate"]["UpperLeft"][1],
38 | val["Coordinate"]["UpperRight"][0] - val["Coordinate"]["UpperLeft"][0] + word_width,
39 | val["Coordinate"]["LowerLeft"][1] - val["Coordinate"]["UpperLeft"][1])
40 |
41 |
42 | # 竖向-找出矩形框碰撞对象
43 | def findRectangularMD(rr1, ocr_result, index1, tmp_words_list):
44 | for index2, val in enumerate(ocr_result):
45 | if index2 <= index1:
46 | continue
47 | word_width = (val["Coordinate"]["UpperRight"][0] - val["Coordinate"]["UpperLeft"][0]) // 2
48 | rr2 = createRectangularMD(val, word_width)
49 | if rr2.collision(rr1):
50 | tmp_words_list.append(val)
51 | findRectangularMD(rr2, ocr_result, index2, tmp_words_list)
52 | break
53 |
54 |
55 | # 竖向-找出矩形框碰撞对象
56 | def findRectangular2MD(rr1, ocr_result, index1, tmp_words_list, word_width):
57 | for index2, val in enumerate(ocr_result):
58 | if index2 <= index1:
59 | continue
60 | rr2 = createRectangularMD(val, word_width)
61 | if rr2.collision(rr1):
62 | tmp_words_list.append(val)
63 | findRectangular2MD(rr2, ocr_result, index2, tmp_words_list, word_width)
64 | break
65 |
66 |
67 | # 横向-创建用于计算碰撞的矩形框对象
68 | def createRectangularTD(val, word_height):
69 | return Rectangular(val["Coordinate"]["UpperLeft"][0],
70 | val["Coordinate"]["UpperLeft"][1] ,
71 | val["Coordinate"]["UpperRight"][0] - val["Coordinate"]["UpperLeft"][0],
72 | val["Coordinate"]["LowerLeft"][1] - val["Coordinate"]["UpperLeft"][1] + word_height)
73 |
74 |
75 | # 横向-找出矩形框碰撞对象
76 | def findRectangularTD(rr1, ocr_result, index1, tmp_words_list):
77 | for index2, val in enumerate(ocr_result):
78 | if index2 <= index1:
79 | continue
80 | word_height = (val["Coordinate"]["LowerRight"][1] - val["Coordinate"]["UpperRight"][1]) * 1.5
81 | rr2 = createRectangularTD(val, word_height)
82 | if rr2.collision(rr1):
83 | tmp_words_list.append(val)
84 | findRectangularTD(rr2, ocr_result, index2, tmp_words_list)
85 | break
--------------------------------------------------------------------------------
/utils/screen_rate.py:
--------------------------------------------------------------------------------
1 | from win32 import win32api, win32gui, win32print
2 | from win32.lib import win32con
3 | from win32.win32api import GetSystemMetrics
4 | from traceback import format_exc
5 |
6 |
7 | # 获取真实的分辨率
8 | def getRealResolution() :
9 |
10 | hDC = win32gui.GetDC(0)
11 | # 横向分辨率
12 | w = win32print.GetDeviceCaps(hDC, win32con.DESKTOPHORZRES)
13 | # 纵向分辨率
14 | h = win32print.GetDeviceCaps(hDC, win32con.DESKTOPVERTRES)
15 |
16 | return w, h
17 |
18 |
19 | # 获取缩放后的分辨率
20 | def getScreenSize() :
21 |
22 | w = GetSystemMetrics(0)
23 | h = GetSystemMetrics(1)
24 |
25 | return w, h
26 |
27 |
28 | # 计算屏幕缩放比例
29 | def getScreenRate(logger=None) :
30 |
31 | try :
32 | real_resolution = getRealResolution()
33 | screen_size = getScreenSize()
34 | screen_scale_rate = round(real_resolution[0] / screen_size[0], 2)
35 | except Exception :
36 | if logger :
37 | logger.error(format_exc())
38 | screen_scale_rate = 1.0
39 |
40 |
41 | return screen_scale_rate
--------------------------------------------------------------------------------
/utils/sqlite.py:
--------------------------------------------------------------------------------
1 | import os.path
2 | import sqlite3
3 | import re
4 | import datetime
5 | import time
6 | import traceback
7 | import csv
8 | from difflib import SequenceMatcher
9 |
10 |
11 | DB_PATH = "../db/"
12 | HISTORY_FILE_PATH = "../翻译历史.txt"
13 | TRANSLATION_DB = None
14 | TRANS_MAP = {
15 | "公共有道": "youdao",
16 | "公共彩云": "caiyun",
17 | "公共DeepL": "deepl",
18 | "公共百度": "baidu",
19 | "公共腾讯": "tencent",
20 | "公共Bing": "bing",
21 | "私人团子": "dango_private",
22 | "私人百度": "baidu_private",
23 | "私人腾讯": "tencent_private",
24 | "私人彩云": "caiyun_private",
25 | "私人ChatGPT": "chatgpt_private",
26 | "私人阿里": "aliyun_private",
27 | "私人阿里云": "aliyun_private",
28 | "私人有道": "youdao_private",
29 | "私人小牛": "xiaoniu_private",
30 | "私人火山": "huoshan_private"
31 | }
32 | TRANS_MAP_INVERSION = {
33 | "youdao": "公共有道",
34 | "caiyun": "公共彩云",
35 | "deepl": "公共DeepL",
36 | "baidu": "公共百度",
37 | "tencent": "公共腾讯",
38 | "bing": "公共Bing",
39 | "dango_private": "私人团子",
40 | "baidu_private": "私人百度",
41 | "tencent_private": "私人腾讯",
42 | "caiyun_private": "私人彩云",
43 | "chatgpt_private": "私人ChatGPT",
44 | "aliyun_private": "私人阿里",
45 | "youdao_private": "私人有道",
46 | "xiaoniu_private": "私人小牛",
47 | "huoshan_private": "私人火山"
48 | }
49 |
50 |
51 | # 连接翻译历史数据库
52 | def connectTranslationDB(logger) :
53 |
54 | try :
55 | os.makedirs(DB_PATH)
56 | except FileExistsError :
57 | pass
58 |
59 | try :
60 | # 连接数据库
61 | global TRANSLATION_DB
62 | db_path = os.path.join(DB_PATH, "translation.db")
63 | TRANSLATION_DB = sqlite3.connect(db_path, check_same_thread=False)
64 | # 初始化翻译记录表
65 | sql = '''
66 | CREATE TABLE IF NOT EXISTS translations (
67 | id INTEGER PRIMARY KEY AUTOINCREMENT,
68 | src TEXT NOT NULL,
69 | trans_type TEXT NOT NULL,
70 | tgt TEXT NOT NULL,
71 | create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
72 | UNIQUE (`src`, `trans_type`));
73 | '''
74 | TRANSLATION_DB.execute(sql)
75 | sql = '''
76 | CREATE INDEX IF NOT EXISTS src ON translations (src);
77 | '''
78 | TRANSLATION_DB.execute(sql)
79 | TRANSLATION_DB.commit()
80 | except Exception :
81 | logger.error(traceback.format_exc())
82 |
83 |
84 | # 写入翻译历史数据库
85 | def insertTranslationDB(logger, src, trans_type, tgt, create_time=None) :
86 |
87 | # 原文不能为空
88 | if not tgt :
89 | return
90 | # 校验数据库连接
91 | global TRANSLATION_DB
92 | if not TRANSLATION_DB :
93 | return
94 | # 过滤是否是异常的翻译结果
95 | if trans_type in TRANS_MAP_INVERSION :
96 | trans_type = TRANS_MAP_INVERSION[trans_type]
97 | if re.match("^{}[::]".format(trans_type), tgt) :
98 | return
99 | # 统一翻译类型基于TRANS_MAP
100 | if trans_type in TRANS_MAP :
101 | trans_type = TRANS_MAP[trans_type]
102 | # 使用当前时间
103 | if not create_time :
104 | create_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
105 | try :
106 | sql = '''
107 | INSERT INTO translations (src, trans_type, tgt, create_time)
108 | VALUES (?, ?, ?, ?);
109 | '''
110 | TRANSLATION_DB.execute(sql, (src, trans_type, tgt, create_time))
111 | TRANSLATION_DB.commit()
112 | except sqlite3.IntegrityError :
113 | sql = '''
114 | UPDATE translations SET tgt = ? WHERE src =? AND trans_type = ?;
115 | '''
116 | TRANSLATION_DB.execute(sql, (tgt, src, trans_type))
117 | TRANSLATION_DB.commit()
118 | except Exception :
119 | logger.error(traceback.format_exc())
120 |
121 |
122 | # 查询翻译历史数据库
123 | def selectTranslationDBList(src, tgt, limit, offset, logger) :
124 |
125 | rows = []
126 | global TRANSLATION_DB
127 | if not TRANSLATION_DB :
128 | return rows
129 |
130 | try :
131 | if src :
132 | if tgt :
133 | sql = '''SELECT id, trans_type, src, tgt FROM translations WHERE src LIKE '%{}%' AND tgt LIKE '%{}%' ORDER BY id DESC LIMIT ? OFFSET ?;'''.format(src, tgt)
134 | else :
135 | sql = '''SELECT id, trans_type, src, tgt FROM translations WHERE src LIKE '%{}%' ORDER BY id DESC LIMIT ? OFFSET ?;'''.format(src)
136 | else:
137 | if tgt :
138 | sql = '''SELECT id, trans_type, src, tgt FROM translations WHERE tgt LIKE '%{}%' ORDER BY id DESC LIMIT ? OFFSET ?;'''.format(tgt)
139 | else :
140 | sql = '''SELECT id, trans_type, src, tgt FROM translations ORDER BY id DESC LIMIT ? OFFSET ?;'''
141 | cursor = TRANSLATION_DB.execute(sql, (limit, offset))
142 | rows = cursor.fetchall()
143 | cursor.close()
144 | except Exception :
145 | logger.error(traceback.format_exc())
146 |
147 | return rows
148 |
149 |
150 | # 查询翻译历史数据库
151 | def selectTranslationDBBySrcAndTransType(src, logger) :
152 |
153 | rows = []
154 | trans_map = {}
155 | global TRANSLATION_DB
156 | if not TRANSLATION_DB:
157 | return trans_map
158 |
159 | try :
160 | sql = '''SELECT * FROM translations WHERE src = ?;'''
161 | cursor = TRANSLATION_DB.execute(sql, (src,))
162 | rows = cursor.fetchall()
163 | cursor.close()
164 | except Exception :
165 | logger.error(traceback.format_exc())
166 |
167 | for row in rows :
168 | trans_map[row[2]] = row[3]
169 |
170 | return trans_map
171 |
172 |
173 | # 同步旧翻译历史文件
174 | def SyncTranslationHistory(logger) :
175 |
176 | global TRANSLATION_DB
177 | if not TRANSLATION_DB :
178 | return
179 |
180 | time_pattern = r'''\[原文\]\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]'''
181 | original_pattern = r'''\[原文\]\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]'''
182 | text = ""
183 |
184 | # 读取翻译历史文件
185 | with open(HISTORY_FILE_PATH, "r", encoding="utf-8") as file :
186 | for line in file :
187 | re_result = re.findall(time_pattern, line)
188 | if re_result :
189 | create_time = re_result[0]
190 | trans_map = {}
191 | src = ""
192 | # 提取原文
193 | re_result = re.findall(original_pattern+r"\n(.+?)\n\[", text, re.S)
194 | if re_result :
195 | src = re_result[0]
196 | # 提取译文
197 | for trans_type in TRANS_MAP.keys() :
198 | trans_pattern = r'''\[{}\]'''.format(trans_type)
199 | re_result = re.findall(trans_pattern+r"\n(.+?)\n", text, re.S)
200 | if re_result :
201 | trans_map[trans_type] = re_result[0]
202 | # 写入翻译历史数据库
203 | if src and len(trans_map) >= 1 :
204 | values = []
205 | for trans_type, tgt in trans_map.items() :
206 | insertTranslationDB(logger, src, trans_type, tgt, create_time)
207 |
208 | text = line
209 | else :
210 | text += line
211 |
212 |
213 | # 数据库初始化
214 | def initTranslationDB(object) :
215 |
216 | global TRANSLATION_DB
217 | if not TRANSLATION_DB :
218 | return
219 |
220 | if not object.yaml["sync_db"] :
221 | SyncTranslationHistory(object.logger)
222 | object.yaml["sync_db"] = True
223 |
224 |
225 | # 查询翻译历史总数
226 | def selectTranslationDBTotal(src, tgt, logger) :
227 |
228 | result = 0
229 | global TRANSLATION_DB
230 | if not TRANSLATION_DB :
231 | return result
232 |
233 | try :
234 | if src :
235 | if tgt :
236 | sql = '''SELECT count(*) FROM translations WHERE src LIKE '%{}%' AND tgt LIKE '%{}%';'''.format(src, tgt)
237 | else :
238 | sql = '''SELECT count(*) FROM translations WHERE src LIKE '%{}%';'''.format(src)
239 | else :
240 | if tgt :
241 | sql = '''SELECT count(*) FROM translations WHERE tgt LIKE '%{}%';'''.format(tgt)
242 | else :
243 | sql = '''SELECT count(*) FROM translations;'''
244 | cursor = TRANSLATION_DB.execute(sql)
245 | result = cursor.fetchone()[0]
246 | cursor.close()
247 | except Exception :
248 | logger.error(traceback.format_exc())
249 |
250 | return result
251 |
252 |
253 | # 导出所有数据
254 | def outputTranslationDB(file_path, logger) :
255 |
256 | global TRANSLATION_DB
257 | if not TRANSLATION_DB :
258 | return
259 |
260 | sql = '''SELECT src, trans_type, tgt, create_time FROM translations;'''
261 | try :
262 | cursor = TRANSLATION_DB.execute(sql)
263 | titles = [description[0] for description in cursor.description]
264 |
265 | with open(file_path, "w", encoding="utf-8", newline="") as file :
266 | writer = csv.writer(file)
267 | writer.writerow(titles)
268 | while True:
269 | rows = cursor.fetchmany(10000)
270 | if not rows :
271 | break
272 | writer.writerows(rows)
273 | cursor.close()
274 | except Exception :
275 | logger.error(traceback.format_exc())
276 | return traceback.format_exc()
277 |
278 |
279 | # 修改原文翻译数据
280 | def modifyTranslationDBSrc(id, src, logger) :
281 |
282 | global TRANSLATION_DB
283 | if not TRANSLATION_DB :
284 | return
285 |
286 | sql = '''UPDATE translations SET src = ? WHERE id = ?;'''
287 | try :
288 | TRANSLATION_DB.execute(sql, (src, id,))
289 | TRANSLATION_DB.commit()
290 | except Exception :
291 | logger.error(traceback.format_exc())
292 | return traceback.format_exc()
293 |
294 |
295 | # 修改原文翻译数据
296 | def modifyTranslationDBTgt(id, tgt, logger) :
297 |
298 | global TRANSLATION_DB
299 | if not TRANSLATION_DB :
300 | return
301 |
302 | sql = '''UPDATE translations SET tgt = ? WHERE id = ?;'''
303 | try :
304 | TRANSLATION_DB.execute(sql, (tgt, id,))
305 | TRANSLATION_DB.commit()
306 | except Exception :
307 | logger.error(traceback.format_exc())
308 | return traceback.format_exc()
309 |
310 |
311 | # 删除翻译数据
312 | def deleteTranslationDBByID(id, logger) :
313 |
314 | global TRANSLATION_DB
315 | if not TRANSLATION_DB :
316 | return
317 |
318 | sql = '''DELETE FROM translations WHERE id = ?;'''
319 | try :
320 | TRANSLATION_DB.execute(sql, (id,))
321 | TRANSLATION_DB.commit()
322 | except Exception :
323 | logger.error(traceback.format_exc())
324 | return traceback.format_exc()
325 |
326 |
327 | # 判断字符串相似度
328 | def getEqualRate(str1, str2) :
329 |
330 | score = SequenceMatcher(None, str1, str2).quick_ratio()
331 | score = score * 100
332 | return score
333 |
334 |
335 | # 根据相似度查询数据
336 | def selectTransDataBySimilarity(src, similar_score, logger) :
337 |
338 | new_src = ""
339 | max_score = 0
340 |
341 | global TRANSLATION_DB
342 | if not TRANSLATION_DB :
343 | return new_src
344 |
345 | sql = '''SELECT src FROM translations GROUP BY src;'''
346 | try :
347 | cursor = TRANSLATION_DB.execute(sql)
348 | while True:
349 | rows = cursor.fetchmany(10000)
350 | if not rows :
351 | break
352 | for row in rows :
353 | # 判断相似度
354 | score = getEqualRate(src, row[0])
355 | if score < similar_score :
356 | continue
357 | # 取最高相似度
358 | if score > max_score :
359 | max_score = score
360 | new_src = row[0]
361 | cursor.close()
362 | except Exception :
363 | logger.error(traceback.format_exc())
364 |
365 | return new_src
--------------------------------------------------------------------------------
/utils/test.py:
--------------------------------------------------------------------------------
1 | from PyQt5.QtWidgets import *
2 | from PyQt5.QtCore import *
3 | import time
4 | import os
5 | import re
6 |
7 | import translator.ocr.dango
8 | import translator.ocr.baidu
9 | import translator.api
10 | import utils.http
11 | import utils.thread
12 | import ui.desc
13 | import traceback
14 |
15 |
16 | TEST_IMAGE_PATH = os.path.join(os.getcwd(), "config", "other", "image.jpg")
17 | TEST_ORIGINAL = "もし、今の状況が自分らしくないことの連続で、好きになれないなら、どうすれば、変えられるかを真剣に考えてみよう。そしないと問題はちっとも解決しない。"
18 |
19 |
20 | # 测试本地OCR
21 | def testOfflineOCR(object) :
22 |
23 | desc_ui = object.settin_ui.desc_ui
24 | signal = object.settin_ui.desc_signal
25 | desc_ui.desc_text.clear()
26 | desc_ui.show()
27 |
28 | desc_ui.setWindowTitle("本地OCR测试")
29 | desc_ui.desc_text.append("\n开始测试...")
30 | desc_ui.desc_text.insertHtml('
'.format(TEST_IMAGE_PATH, 245 * object.settin_ui.rate))
31 | QApplication.processEvents()
32 |
33 | def func():
34 | start = time.time()
35 | sign, result = translator.ocr.dango.offlineOCR(object, True)
36 | if sign :
37 | signal.emit("\n识别结果:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time() - start))
38 | else :
39 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
40 |
41 | utils.thread.createThread(func)
42 |
43 |
44 | # 测试私人腾讯
45 | def testTencent(object, secret_id, secret_key) :
46 |
47 | desc_ui = object.settin_ui.desc_ui
48 | signal = object.settin_ui.desc_signal
49 | desc_ui.desc_text.clear()
50 | desc_ui.show()
51 |
52 | desc_ui.setWindowTitle("私人腾讯翻译测试")
53 | signal.emit("\n开始测试...\n\n原文: \n{}".format(TEST_ORIGINAL))
54 | QApplication.processEvents()
55 |
56 | def func() :
57 | start = time.time()
58 | result = translator.api.tencent(TEST_ORIGINAL, secret_id, secret_key, object.logger)
59 | if not re.match("^私人腾讯[::]", result) :
60 | signal.emit("\n译文:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time() - start))
61 | else:
62 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
63 |
64 | utils.thread.createThread(func)
65 |
66 |
67 | # 测试私人百度翻译
68 | def testBaidu(object, secret_id, secret_key) :
69 |
70 | desc_ui = object.settin_ui.desc_ui
71 | signal = object.settin_ui.desc_signal
72 | desc_ui.desc_text.clear()
73 | desc_ui.show()
74 |
75 | desc_ui.setWindowTitle("私人百度翻译测试")
76 | signal.emit("\n开始测试...\n\n原文: \n{}".format(TEST_ORIGINAL))
77 | QApplication.processEvents()
78 |
79 | def func() :
80 | start = time.time()
81 | result = translator.api.baidu(TEST_ORIGINAL, secret_id, secret_key, object.logger)
82 | if not re.match("^私人百度[::]", result):
83 | signal.emit("\n译文:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time() - start))
84 | else:
85 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
86 |
87 | utils.thread.createThread(func)
88 |
89 |
90 | # 测试私人彩云翻译
91 | def testCaiyun(object, secret_key) :
92 |
93 | desc_ui = object.settin_ui.desc_ui
94 | signal = object.settin_ui.desc_signal
95 | desc_ui.desc_text.clear()
96 | desc_ui.show()
97 |
98 | desc_ui.setWindowTitle("私人彩云翻译测试")
99 | signal.emit("\n开始测试...\n\n原文: \n{}".format(TEST_ORIGINAL))
100 | QApplication.processEvents()
101 |
102 | def func() :
103 | start = time.time()
104 | result = translator.api.caiyun(TEST_ORIGINAL, secret_key, object.logger)
105 | if not re.match("^私人彩云[::]", result) :
106 | signal.emit("\n译文:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time() - start))
107 | else:
108 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
109 |
110 | utils.thread.createThread(func)
111 |
112 |
113 | # 测试在线OCR
114 | def testOnlineOCR(object) :
115 |
116 | desc_ui = object.settin_ui.desc_ui
117 | signal = object.settin_ui.desc_signal
118 | desc_ui.desc_text.clear()
119 | desc_ui.show()
120 |
121 | desc_ui.setWindowTitle("团子在线OCR测试")
122 | desc_ui.desc_text.append("\n开始测试...")
123 | desc_ui.desc_text.insertHtml('
'.format(TEST_IMAGE_PATH, 245 * object.settin_ui.rate))
124 | QApplication.processEvents()
125 |
126 | def func() :
127 | start = time.time()
128 | sign, result = translator.ocr.dango.dangoOCR(object, test=True)
129 | if sign :
130 | signal.emit("\n识别结果:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time()-start))
131 | else :
132 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
133 |
134 | utils.thread.createThread(func)
135 |
136 |
137 | # 测试百度OCR
138 | def testBaiduOCR(object) :
139 |
140 | desc_ui = object.settin_ui.desc_ui
141 | signal = object.settin_ui.desc_signal
142 | desc_ui.desc_text.clear()
143 | desc_ui.show()
144 |
145 | desc_ui.setWindowTitle("百度OCR测试")
146 | desc_ui.desc_text.append("\n开始测试...")
147 | desc_ui.desc_text.insertHtml('
'.format(TEST_IMAGE_PATH, 245 * object.settin_ui.rate))
148 | QApplication.processEvents()
149 |
150 | def func() :
151 | start = time.time()
152 | translator.ocr.baidu.getAccessToken(object)
153 | sign, result = translator.ocr.baidu.baiduOCR(object, test=True)
154 | if sign :
155 | signal.emit("\n识别结果:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time()-start))
156 | else :
157 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
158 |
159 | utils.thread.createThread(func)
160 |
161 |
162 | # 测试私人ChatGPT翻译
163 | def testChatGPT(object, api_key, proxy, url, model, prompt) :
164 |
165 | object.config["chatgptAPI"] = api_key
166 | object.config["chatgptProxy"] = proxy
167 | object.config["chatgptApiAddr"] = url
168 | object.config["chatgptModel"] = model
169 | object.config["chatgptPrompt"] = prompt
170 |
171 | desc_ui = object.settin_ui.desc_ui
172 | signal = object.settin_ui.desc_signal
173 | desc_ui.desc_text.clear()
174 | desc_ui.show()
175 |
176 | desc_ui.setWindowTitle("私人ChatGPT翻译测试")
177 | signal.emit("\n开始测试...\n\n原文: \n{}".format(TEST_ORIGINAL))
178 | QApplication.processEvents()
179 |
180 | def func():
181 | start = time.time()
182 | result = translator.api.chatgpt(
183 | object=object,
184 | content=TEST_ORIGINAL,
185 | delay_time=0
186 | )
187 | if not re.match("^私人ChatGPT[::]", result) :
188 | signal.emit("\n译文:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time() - start))
189 | else:
190 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
191 |
192 | utils.thread.createThread(func)
193 |
194 |
195 | # 测试私人团子
196 | def testDango(object) :
197 |
198 | desc_ui = object.settin_ui.desc_ui
199 | signal = object.settin_ui.desc_signal
200 | desc_ui.desc_text.clear()
201 | desc_ui.show()
202 |
203 | desc_ui.setWindowTitle("私人团子翻译测试")
204 | signal.emit("\n开始测试...\n\n原文: \n{}".format(TEST_ORIGINAL))
205 | QApplication.processEvents()
206 |
207 | def func() :
208 | start = time.time()
209 | sign, result = translator.ocr.dango.dangoTrans(
210 | object=object,
211 | sentence=TEST_ORIGINAL,
212 | language="JAP"
213 | )
214 | if sign :
215 | signal.emit("\n译文:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time()-start))
216 | else :
217 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
218 |
219 | utils.thread.createThread(func)
220 |
221 |
222 | # 测试私人阿里云翻译
223 | def testAliyun(object, access_key_id, access_key_secret) :
224 |
225 | desc_ui = object.settin_ui.desc_ui
226 | signal = object.settin_ui.desc_signal
227 | desc_ui.desc_text.clear()
228 | desc_ui.show()
229 |
230 | desc_ui.setWindowTitle("私人阿里云翻译测试")
231 | signal.emit("\n开始测试...\n\n原文: \n{}".format(TEST_ORIGINAL))
232 | QApplication.processEvents()
233 |
234 | def func() :
235 | start = time.time()
236 | sign, result = translator.api.aliyun(
237 | access_key_id=access_key_id,
238 | access_key_secret=access_key_secret,
239 | source_language="JAP",
240 | text_to_translate=TEST_ORIGINAL,
241 | logger=object.logger
242 | )
243 | if sign :
244 | signal.emit("\n译文:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time()-start))
245 | else :
246 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
247 |
248 | utils.thread.createThread(func)
249 |
250 |
251 | # 测试私人有道翻译
252 | def testYoudao(object, app_key, app_secret) :
253 |
254 | desc_ui = object.settin_ui.desc_ui
255 | signal = object.settin_ui.desc_signal
256 | desc_ui.desc_text.clear()
257 | desc_ui.show()
258 |
259 | desc_ui.setWindowTitle("私人有道翻译测试")
260 | signal.emit("\n开始测试...\n\n原文: \n{}".format(TEST_ORIGINAL))
261 | QApplication.processEvents()
262 |
263 | def func():
264 | start = time.time()
265 | sign, result = translator.api.youdao(
266 | text=TEST_ORIGINAL,
267 | app_key=app_key,
268 | app_secret=app_secret,
269 | logger=object.logger
270 | )
271 | if sign :
272 | signal.emit("\n译文:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time()-start))
273 | else :
274 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
275 |
276 | utils.thread.createThread(func)
277 |
278 |
279 | # 测试私人小牛翻译
280 | def testXiaoniu(object, secret_key) :
281 |
282 | desc_ui = object.settin_ui.desc_ui
283 | signal = object.settin_ui.desc_signal
284 | desc_ui.desc_text.clear()
285 | desc_ui.show()
286 |
287 | desc_ui.setWindowTitle("私人小牛翻译测试")
288 | signal.emit("\n开始测试...\n\n原文: \n{}".format(TEST_ORIGINAL))
289 | QApplication.processEvents()
290 |
291 | def func():
292 | start = time.time()
293 | sign, result = translator.api.xiaoniu(secret_key, TEST_ORIGINAL, "JAP", object.logger)
294 | if sign :
295 | signal.emit("\n译文:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time()-start))
296 | else :
297 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
298 |
299 | utils.thread.createThread(func)
300 |
301 |
302 | # 测试私人火山
303 | def testHuoshan(object, secret_id, secret_key) :
304 |
305 | desc_ui = object.settin_ui.desc_ui
306 | signal = object.settin_ui.desc_signal
307 | desc_ui.desc_text.clear()
308 | desc_ui.show()
309 |
310 | desc_ui.setWindowTitle("私人火山翻译测试")
311 | signal.emit("\n开始测试...\n\n原文: \n{}".format(TEST_ORIGINAL))
312 | QApplication.processEvents()
313 |
314 | def func() :
315 | start = time.time()
316 | sign, result = translator.api.huoshan(secret_id, secret_key, TEST_ORIGINAL, object.logger)
317 | if sign :
318 | signal.emit("\n译文:\n{}\n\n耗时: {:.2f}s\n测试成功!".format(result, time.time() - start))
319 | else:
320 | signal.emit("\n测试出错:\n{}\n\n测试失败, 请排查完错误后重试!".format(result))
321 |
322 | utils.thread.createThread(func)
--------------------------------------------------------------------------------
/utils/thread.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from PyQt5.QtCore import *
4 | import threading
5 | import os
6 | import utils.config
7 | import utils.email
8 | import utils.message
9 | import utils.http
10 | import traceback
11 | import translator.sound
12 |
13 |
14 | # 创建线程
15 | def createThread(func, *args) :
16 |
17 | thread = threading.Thread(target=func, args=args)
18 | thread.setDaemon(True)
19 | thread.start()
20 |
21 | return thread
22 |
23 |
24 | # 不守护的线程
25 | def createThreadDaemonFalse(func, *args) :
26 |
27 | thread = threading.Thread(target=func, args=args)
28 | thread.setDaemon(False)
29 | thread.start()
30 |
31 | return thread
32 |
33 |
34 | # 运行QThread
35 | def runQThread(qthread, mode=True) :
36 |
37 | def func() :
38 | qthread.start()
39 | qthread.wait()
40 |
41 | thread = threading.Thread(target=func)
42 | thread.setDaemon(mode)
43 | thread.start()
44 |
45 | return thread
46 |
47 |
48 | # 检查绑定邮箱线程
49 | class createCheckBindEmailQThread(QThread) :
50 |
51 | signal = pyqtSignal(bool)
52 |
53 | def __init__(self, object):
54 | super(createCheckBindEmailQThread, self).__init__()
55 | self.object = object
56 |
57 | def run(self) :
58 | if not utils.email.bindEmail(self.object):
59 | self.signal.emit(False)
60 |
61 |
62 |
63 | # 打印翻译框默认信息线程
64 | class createShowTranslateTextQThread(QThread) :
65 |
66 | signal = pyqtSignal(str)
67 |
68 | def __init__(self, object):
69 | super(createShowTranslateTextQThread, self).__init__()
70 | self.object = object
71 |
72 | def run(self) :
73 | # 获取版本广播信息
74 | result = utils.config.getVersionMessage(self.object)
75 | if result != "" and result != "No" :
76 | self.signal.emit(result)
77 | else :
78 | self.signal.emit("")
79 |
80 |
81 | # 检查版本线程
82 | class createCheckVersionQThread(QThread) :
83 |
84 | signal = pyqtSignal(bool)
85 |
86 | def __init__(self, object):
87 | super(createCheckVersionQThread, self).__init__()
88 | self.object = object
89 |
90 | def run(self) :
91 | if self.object.yaml["version"] != self.object.yaml["dict_info"]["latest_version"] :
92 | self.signal.emit(False)
93 |
94 |
95 | # 检查自动登录线程
96 | class createCheckAutoLoginQThread(QThread) :
97 |
98 | signal = pyqtSignal(str)
99 |
100 | def __init__(self, object):
101 | super(createCheckAutoLoginQThread, self).__init__()
102 | self.object = object
103 |
104 | def run(self) :
105 | message = utils.http.loginCheck(self.object)
106 | if message :
107 | self.signal.emit(message)
108 |
109 |
110 | # 图片翻译进程
111 | class createMangaTransQThread(QThread) :
112 |
113 | signal = pyqtSignal(str, bool)
114 | bar_signal = pyqtSignal(int, str)
115 | add_message_signal = pyqtSignal(str, str)
116 |
117 | def __init__(self, window, image_paths, execute_type="pass", use_sqlite=True):
118 |
119 | super(createMangaTransQThread, self).__init__()
120 | self.window = window
121 | self.logger = self.window.logger
122 | self.image_paths = image_paths
123 | self.execute_type = execute_type
124 | self.use_sqlite = use_sqlite
125 | self.window.trans_process_bar.stop_sign = False
126 | self.window.trans_process_bar.finish_sign = False
127 | self.success_count = 0
128 |
129 | def run(self) :
130 |
131 | # 初始化进度条
132 | self.bar_signal.emit(0, "0/%d"%len(self.image_paths))
133 | self.add_message_signal.emit("", "")
134 | time.sleep(0.1)
135 | try :
136 | for index, image_path in enumerate(self.image_paths) :
137 | # 翻译进程
138 | result = self.window.transProcess(image_path, self.execute_type, self.use_sqlite)
139 | # 如果失败记录日志
140 | image_name = os.path.basename(image_path)
141 | if result :
142 | self.logger.error(result)
143 | self.add_message_signal.emit("{}. {} 翻译失败: {}".format(index+1, image_name, result), "red")
144 | else :
145 | self.success_count += 1
146 | self.add_message_signal.emit("{}. {} 翻译成功".format(index+1, image_name), "green")
147 | # 进度条
148 | self.bar_signal.emit(
149 | int((index + 1) / len(self.image_paths) * 100),
150 | "%d/%d"%(index + 1, len(self.image_paths))
151 | )
152 | time.sleep(0.05)
153 | # 如果停止
154 | if self.window.trans_process_bar.stop_sign :
155 | break
156 | except Exception :
157 | message = traceback.format_exc()
158 | self.logger.error(message)
159 | self.signal.emit(message, False)
160 | self.add_message_signal.emit(" ", "green")
161 | self.add_message_signal.emit(message, "red")
162 | self.add_message_signal.emit("翻译出错, 异常中止", "red")
163 |
164 | # 结束
165 | self.add_message_signal.emit(" ", "green")
166 | self.add_message_signal.emit("成功{}张, 失败{}张, 翻译结束".format(self.success_count, len(self.image_paths) - self.success_count), "green")
167 | self.window.trans_process_bar.finish_sign = True
168 | self.signal.emit("", False)
169 | # 播放系统提示音
170 | if len(self.image_paths) > 1 :
171 | translator.sound.playSystemSound()
172 |
173 |
174 | # 图片翻译导入图片进程
175 | class createInputImagesQThread(QThread) :
176 |
177 | bar_signal = pyqtSignal(float, int, str)
178 | image_widget_signal = pyqtSignal(str, str, bool)
179 |
180 | def __init__(self, window, images):
181 |
182 | super(createInputImagesQThread, self).__init__()
183 | self.window = window
184 | self.images = images
185 | self.window.input_images_progress_bar.stop_sign = False
186 | self.window.input_images_progress_bar.finish_sign = False
187 |
188 | def run(self) :
189 |
190 | # 初始化进度条
191 | self.bar_signal.emit(0, 0, "0/%d"%len(self.images))
192 | time.sleep(0.1)
193 | # 遍历文件列表, 将每个文件路径添加到列表框中
194 | for index, image_path in enumerate(self.images) :
195 | # 判断文件是否已经添加
196 | image_path = os.path.normpath(image_path)
197 | if image_path in self.window.image_path_list :
198 | continue
199 | # 判断图片是否损坏
200 | try :
201 | self.window.repairBadImage(image_path)
202 | except Exception :
203 | continue
204 | # 判断文件大小, 如果超过2MB就缩小
205 | file_size = self.window.getFileSize(image_path)
206 | if file_size > 2 :
207 | self.window.adjustImageSize(image_path, 2*1024*1024)
208 | # 发送添加到列表框信号
209 | self.image_widget_signal.emit(str(index+1), image_path, False)
210 | # 进度条
211 | self.bar_signal.emit(
212 | float((index + 1) / len(self.images) * 100),
213 | int((index + 1) / len(self.images) * 100),
214 | "%d/%d" % (index + 1, len(self.images))
215 | )
216 | time.sleep(0.1)
217 | # 如果停止
218 | if self.window.input_images_progress_bar.stop_sign :
219 | break
220 | self.window.input_images_progress_bar.finish_sign = True
221 | self.image_widget_signal.emit("", "", True)
--------------------------------------------------------------------------------
/utils/update.py:
--------------------------------------------------------------------------------
1 | import re
2 | import os
3 | import sys
4 | from traceback import format_exc
5 | import hashlib
6 |
7 | import utils.http
8 |
9 |
10 | OCR_SRC_FILE_PATH = "./ocr/resources/app.py"
11 | PIL_FILE_PATH = "./PIL/_imagingft.cp38-win32.pyd"
12 | AUTO_UPDATE_FILE_PATH = "../自动更新程序.exe"
13 | MANGA_FONT_PATH = "./config/other/NotoSansSC-Regular.otf"
14 |
15 |
16 | # 更新本地ocr源码文件
17 | def updateOCRSrcFile(url, logger) :
18 |
19 | try:
20 | with open(OCR_SRC_FILE_PATH, "r", encoding="utf-8") as file :
21 | src = file.read()
22 |
23 | regex = "paddleocr.paddleocr.BASE_DIR"
24 | if len(re.findall(regex, src)) > 0 :
25 | utils.http.downloadFile(url, OCR_SRC_FILE_PATH, logger)
26 |
27 | except Exception:
28 | logger.error(format_exc())
29 |
30 |
31 | # 更新贴字翻译所需的文件
32 | def updatePilFile(object) :
33 |
34 | if not os.path.exists(PIL_FILE_PATH) :
35 | url = object.yaml["dict_info"]["pil_file_url"]
36 | if utils.http.downloadFile(url, PIL_FILE_PATH, object.logger) :
37 | sys.exit()
38 |
39 |
40 | # 更新自动更新程序
41 | def updateAutoUpdateFile(object) :
42 |
43 | if os.path.exists(AUTO_UPDATE_FILE_PATH) :
44 | auto_update_file_md5 = object.yaml["dict_info"].get("auto_update_file_md5", "")
45 | if not auto_update_file_md5 :
46 | return
47 | # 计算当前自动更新程序的MD5
48 | hash_md5 = hashlib.md5()
49 | with open(AUTO_UPDATE_FILE_PATH, "rb") as file :
50 | for chunk in iter(lambda: file.read(4096), b"") :
51 | hash_md5.update(chunk)
52 | file_md5 = hash_md5.hexdigest()
53 | # 比较MD5, 判断是否需要更新
54 | if file_md5 == auto_update_file_md5 :
55 | return
56 | # 下载新的自动更新程序
57 | url = object.yaml["dict_info"].get("auto_update_file_url", "")
58 | if not url :
59 | return
60 | utils.http.downloadFile(url, AUTO_UPDATE_FILE_PATH, object.logger)
61 |
62 |
63 | # 下载图片翻译用字体
64 | def updateManFontFile(object) :
65 |
66 | if not os.path.exists(MANGA_FONT_PATH) :
67 | url = object.yaml["dict_info"]["manga_font_file_url"]
68 | utils.http.downloadFile(url, MANGA_FONT_PATH, object.logger)
--------------------------------------------------------------------------------
/utils/zip.py:
--------------------------------------------------------------------------------
1 | import gzip
2 | import shutil
3 | import os
4 | import zipfile
5 |
6 | # 打包指定文件列表
7 | def zipFiles(file_list, output_zip_path) :
8 |
9 | with zipfile.ZipFile(output_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf :
10 | for file in file_list:
11 | # 获取文件的绝对路径
12 | abs_path = os.path.abspath(file)
13 | # 加入压缩包
14 | zipf.write(abs_path, os.path.relpath(abs_path, os.path.dirname(abs_path)))
15 |
16 |
17 | # 打包指定目录
18 | def zipDirectory(directory_path, zip_path) :
19 | # 创建压缩文件
20 | with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
21 | # 遍历目录下的所有文件和子目录
22 | for root, dirs, files in os.walk(directory_path):
23 | for file in files:
24 | # 获取文件的绝对路径
25 | file_path = os.path.join(root, file)
26 | # 将文件添加到压缩文件中
27 | zipf.write(file_path, os.path.relpath(file_path, directory_path))
--------------------------------------------------------------------------------