├── .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://img.shields.io/badge/%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC-Ver6.0.1-ff69b4)](https://github.com/PantsuDango/Dango-Translator) 5 | [![更新时间](https://img.shields.io/badge/%E6%9B%B4%E6%96%B0%E6%97%B6%E9%97%B4-2025--04--07-ff69b4)]() 6 | [![操作系统](https://img.shields.io/badge/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-win7--11-ff69b4)]() 7 | [![GitHubStars](https://img.shields.io/github/stars/PantsuDango/Dango-Translator)]() 8 | [![GitHubForks](https://img.shields.io/github/forks/PantsuDango/Dango-Translator)]() 9 | [![作者](https://img.shields.io/badge/QQ-%E8%83%96%E6%AC%A1%E5%9B%A2%E5%AD%90-ff69b4)](https://github.com/PantsuDango/ImageHub/blob/master/DangoTranslate/public/%E4%BD%9C%E8%80%85.png) 10 | [![群号](https://img.shields.io/badge/%E6%9C%80%E6%96%B0%E4%BA%A4%E6%B5%81%E7%BE%A4-4%E7%BE%A41036831717-ff69b4)](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://img.shields.io/badge/%E6%9C%80%E6%96%B0%E4%BA%A4%E6%B5%81%E7%BE%A4-4%E7%BE%A41036831717-ff69b4)](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 | [![最新版本](https://img.shields.io/badge/%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC-Ver6.0.0-ff69b4)]() 39 | [![更新时间](https://img.shields.io/badge/%E6%9B%B4%E6%96%B0%E6%97%B6%E9%97%B4-2025--03--31-ff69b4)]() 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 | ![原理说明](https://github.com/PantsuDango/ImageHub/blob/master/DangoTranslate/public/%E6%B5%81%E7%A8%8B%E5%9B%BE.png) 70 | ### 图片翻译 71 | ![原理说明](https://github.com/PantsuDango/ImageHub/blob/master/DangoTranslate/Ver6.0.0/manga.png) 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 | ![游戏实时翻译](https://github.com/PantsuDango/ImageHub/blob/master/DangoTranslate/Ver4.3.6/%E4%BD%BF%E7%94%A8%E6%95%88%E6%9E%9C.png) 93 | ![漫画翻译](https://github.com/PantsuDango/ImageHub/blob/master/DangoTranslate/Ver6.0.0/manga3.jpg) 94 | 95 | #### 登录界面 96 | 97 | #### 主界面 98 | 99 | 100 | #### 漫画翻译 101 | ![原图](https://github.com/PantsuDango/ImageHub/blob/master/DangoTranslate/Ver6.0.0/manga1.jpg) 102 | ![编辑](https://github.com/PantsuDango/ImageHub/blob/master/DangoTranslate/Ver6.0.0/manga2.jpg) 103 | ![译图](https://github.com/PantsuDango/ImageHub/blob/master/DangoTranslate/Ver6.0.0/manga3.jpg) 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)) --------------------------------------------------------------------------------