升级到了图形界面工具
改进了录音开始速度,按住大写锁定键说话就行了,不用再等待。 版本号更新到 2.0
24
.gitignore
vendored
@ -1,6 +1,20 @@
|
|||||||
build*
|
*.spec
|
||||||
dist*
|
__pycache__
|
||||||
__pycache__*
|
*.log
|
||||||
run.spec
|
*.spec
|
||||||
alispeech.log
|
*.mp4
|
||||||
|
*.mkv
|
||||||
*.wav
|
*.wav
|
||||||
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
*info
|
||||||
|
|
||||||
|
*database.db
|
||||||
|
*test.py
|
||||||
|
*.7z
|
||||||
|
*/dist/*
|
||||||
|
*/build/*
|
||||||
|
*.db
|
||||||
|
*.afphoto
|
||||||
|
icon*.png
|
||||||
|
视频封面.png
|
||||||
|
15
Pipfile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[[source]]
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
keyboard = "*"
|
||||||
|
aliyunsdkcore = "*"
|
||||||
|
PyAudio = "*"
|
||||||
|
PySide2 = "*"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.8"
|
81
Pipfile.lock
generated
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "0570a2f54550bb4323c9968752018b201089dc64dd9b956170572797eb8ad0d2"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.8"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"aliyunsdkcore": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1e3a1fbd43e29ee8438c5ca52456d3d69c86929b5e3557c5d6dc0df93b3a1d00"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.0.3"
|
||||||
|
},
|
||||||
|
"keyboard": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:63ed83305955939ca5c9a73755e5cc43e8242263f5ad5fd3bb7e0b032f3d308b",
|
||||||
|
"sha256:8e9c2422f1217e0bd84489b9ecd361027cc78415828f4fe4f88dd4acd587947b"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.13.5"
|
||||||
|
},
|
||||||
|
"pyaudio": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0d92f6a294565260a282f7c9a0b0d309fc8cc988b5ee5b50645634ab9e2da7f7",
|
||||||
|
"sha256:259bb9c1363be895b4f9a97e320a6017dd06bc540728c1a04eb4a7b6fe75035b",
|
||||||
|
"sha256:2a19bdb8ec1445b4f3e4b7b109e0e4cec1fd1f1ce588592aeb6db0b58d4fb3b0",
|
||||||
|
"sha256:51b558d1b28c68437b53218279110db44f69f3f5dd3d81859f569a4a96962bdc",
|
||||||
|
"sha256:589bfad2c615dd4b5d3757e763019c42ab82f06fba5cae64ec02fd7f5ae407ed",
|
||||||
|
"sha256:8f89075b4844ea94dde0c951c2937581c989fabd4df09bfd3f075035f50955df",
|
||||||
|
"sha256:93bfde30e0b64e63a46f2fd77e85c41fd51182a4a3413d9edfaf9ffaa26efb74",
|
||||||
|
"sha256:cf1543ba50bd44ac0d0ab5c035bb9c3127eb76047ff12235149d9adf86f532b6",
|
||||||
|
"sha256:f78d543a98b730e64621ebf7f3e2868a79ade0a373882ef51c0293455ffa8e6e"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.2.11"
|
||||||
|
},
|
||||||
|
"pycrypto": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c"
|
||||||
|
],
|
||||||
|
"version": "==2.6.1"
|
||||||
|
},
|
||||||
|
"pyside2": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0558ced3bcd7f9da638fa8b7709dba5dae82a38728e481aac8b9058ea22fcdd9",
|
||||||
|
"sha256:081d8c8a6c65fb1392856a547814c0c014e25ac04b38b987d9a3483e879e9634",
|
||||||
|
"sha256:087a0b719bb967405ea85fd202757c761f1fc73d0e2397bc3a6a15376782ee75",
|
||||||
|
"sha256:1316aa22dd330df096daf7b0defe9c00297a66e0b4907f057aaa3e88c53d1aff",
|
||||||
|
"sha256:4f17a0161995678110447711d685fcd7b15b762810e8f00f6dc239bffb70a32e",
|
||||||
|
"sha256:976cacf01ef3b397a680f9228af7d3d6273b9254457ad4204731507c1f9e6c3c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==5.15.2"
|
||||||
|
},
|
||||||
|
"shiboken2": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:03f41b0693b91c7f89627f1085a4ecbe8591c03f904118a034854d935e0e766c",
|
||||||
|
"sha256:14a33169cf1bd919e4c4c4408fffbcd424c919a3f702df412b8d72b694e4c1d5",
|
||||||
|
"sha256:4aee1b91e339578f9831e824ce2a1ec3ba3a463f41fda8946b4547c7eb3cba86",
|
||||||
|
"sha256:89c157a0e2271909330e1655892e7039249f7b79a64a443d52c512337065cde0",
|
||||||
|
"sha256:ae8ca41274cfa057106268b6249674ca669c5b21009ec49b16d77665ab9619ed",
|
||||||
|
"sha256:edc12a4df2b5be7ca1e762ab94e331ba9e2fbfe3932c20378d8aa3f73f90e0af"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '3.10'",
|
||||||
|
"version": "==5.15.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
99
README.md
@ -1,81 +1,94 @@
|
|||||||
# Caps Writer
|
[Gitee](https://gitee.com/haujet/CapsWriter) | [Github](https://github.com/HaujetZhao/CapsWriter)
|
||||||
|
|
||||||
### 💡 简介
|
# <img src="src/misc/icon.ico" alt="icon.ico" style="zoom: 50%;" /> Caps Writer
|
||||||
|
|
||||||
一款电脑端语音输入工具,后台运行脚本后,按下按下 `Caps Lock`(也就是大写锁定键)超过 0.3 秒后,开始语音识别,松开按键之后,自动输入识别结果。
|
## 💡 简介
|
||||||
|
|
||||||
目前使用了阿里云的一句话识别 api。(有兴趣的可以自行改成百度、腾讯、讯飞、谷歌的 api )
|
这是一款电脑端语音输入工具。顾名思义,Caps Writer 就是按下大写锁定键来打字的工具。它的具体作用是:当你长按键盘上的大写锁定键后,软件会开始语音识别,当你松开大写锁定键时,识别的结果就可以立马上屏。
|
||||||
|
|
||||||
因为使用了阿里云的 api,所以需要用户自己到阿里云申请,再填到 `token.ini` 中才能正常使用。
|
|
||||||
|
|
||||||
对于聊天时候进行快捷输入、写代码时快速加入中文注释非常的方便。
|
对于聊天时候进行快捷输入、写代码时快速加入中文注释非常的方便。
|
||||||
|
|
||||||
|
目前软件内置了对阿里云一句话识别 API 的支持。如果你要使用,就需要先在阿里云上实名认证,申请语音识别 API,在设置页面添加一个语音识别引擎。
|
||||||
|
|
||||||
|
> 添加其它服务商的引擎也是可以做的,只是目前阿里云的引擎就够用,还没有足够的动力添加其它引擎。
|
||||||
|
|
||||||
|
具体使用效果、申请阿里云 API 的方法,可以参考我这个视频: [ CapsWriter 2.0 使用视频 ](https://www.bilibili.com/video/BV12A411p73r/)
|
||||||
|
|
||||||
|
添加上引擎后,在主页面选择一个引擎,点击启用按钮,就可以进行语音识别了!
|
||||||
|
|
||||||
|
启用后,在实际使用中,只要按下 CapsLock 键,软件就会立刻开始录音:
|
||||||
|
|
||||||
|
* 如果只是单击 CapsLock 后松开,录音数据会立刻被删除;
|
||||||
|
* 如果按下 CapsLock 键时长超过 0.3 秒,就会开始连网进行语音识别,松开 CapsLock 键时,语音识别结果会被立刻输入。
|
||||||
|
|
||||||
|
所以你只需要按下 CapsLock 键,无需等待,就可以开始说话,因为当你按下按下 CapsLock 键的时候,程序就开始录音了,只要你按的时长超过 0.3 秒,就肯定能识别上。说完后,松开,识别结果立马上屏。
|
||||||
|
|
||||||
|
<img src="assets/image-20201225053752740.png" alt="image-20201225053752740" style="zoom: 67%;" />
|
||||||
|
|
||||||
|
## ⭐技巧
|
||||||
|
|
||||||
|
在设置界面,将 `点击关闭按钮时隐藏到托盘` 选项勾选,就可以将软件隐藏到托盘栏运行:
|
||||||
|
|
||||||
|
<img src="assets/image-20201225140607971.png" alt="image-20201225140607971" style="zoom: 67%;" />
|
||||||
|
|
||||||
### 📝 背景
|
### 📝 背景
|
||||||
|
|
||||||
|
|
||||||
我真是气抖冷,为什么直到 0202 年,仍然没有开发者做过一个好用的语音输入工具?
|
对于直到 0202 年,仍然没有开发者做过一个好用的语音输入工具,我又生气又无奈,毕竟这东西不赚钱,自然没有人做。
|
||||||
|
|
||||||
|
|
||||||
有人建议用搜狗输入法、讯飞输入法的语音输入,但这几个方面是真让人受不了:
|
有人建议用搜狗输入法、讯飞输入法的语音输入,但这几个方面是真让人受不了:
|
||||||
|
|
||||||
|
|
||||||
* 广告太多,拒绝安装
|
* 广告太多的软件,拒绝安装
|
||||||
* 我主力五笔,不使用搜狗输入法、讯飞输入法,顶多临时用下微软拼音
|
* 速度慢,讯飞在手机上的语音输入挺快的,但是在 PC 上的语音识别速度超级慢
|
||||||
* 就以搜狗输入法为例,它的语音输入快捷键只能是`Ctrl + Shift + A/B/C……`,有以下槽点:
|
* 就以搜狗输入法为例,它的语音输入快捷键只能是`Ctrl + Shift + A/B/C……`,有以下槽点:
|
||||||
* 这个快捷键会和许多软件的快捷键冲突,且不好记
|
* 这个快捷键会和许多软件的快捷键冲突,且不好记
|
||||||
* 打字时,按这样三个快捷键,手指很别扭,不爽
|
* 打字时,按这样三个快捷键,手指很别扭,不爽
|
||||||
* 它的逻辑是按下快捷键后,启用语音输入,你一停顿一下,要说下一名,语音输入却结束了,不能让用户决定什么时候结束语音输入。
|
* 讯飞语音输入法的快捷键是 F6,只能换成 F 功能键,离手指太远,不好够,同时和许多软件快捷键冲突
|
||||||
|
|
||||||
|
|
||||||
为了在电脑上语音输入,我之前是用的 Quicker 的手机端进行语音识别,输入到电脑上,需要两个设备,非常麻烦。今天终于做好我心目中最好用的电脑端语音输入工具了!
|
|
||||||
|
|
||||||
|
|
||||||
### 📽️ 视频演示
|
|
||||||
|
|
||||||
作者为这个工具录制了使用视频演示、申请 api 的教程视频
|
|
||||||
|
|
||||||
请到 HacPai 帖子中进行查看:[Caps Wirter 发布:按住大写锁定键,进行语音识别输入](https://hacpai.com/article/1594371212477)
|
|
||||||
|
|
||||||
或者到 Bilibili 查看:[ Caps Writer(电脑端语音输入工具)使用教程 ](https://www.bilibili.com/video/BV1qK4y1s7Fb/)
|
|
||||||
|
|
||||||
## 🔮 开箱即用
|
## 🔮 开箱即用
|
||||||
|
|
||||||
小白用户,只需要在 [Release](https://github.com/HaujetZhao/CapsWriter/releases) 界面下载打包好的 exe 文件,运行,会在同级目录生成一个 `token.ini` 文件,在 `token.ini` 中填入你阿里云拥有 **管理智能语音交互(NLS)** 权限的 **RAM访问控制** 用户的 **Accesskey Id**、**Accesskey Secret** 和智能语音交互语音识别项目的 **appkey** ,就可以正常使用了。
|
Windows 小白用户,只需要在 [Gitee Releases](https://gitee.com/haujet/CapsWriter/releases) 或 [Github Releases](https://github.com/HaujetZhao/CapsWriter/releases) 界面下载打包好的压缩文件,解压,执行里面的 exe 文件,就可以运行了,在设置界面新建引擎,填入你在阿里云中申请的:
|
||||||
|
|
||||||
详细申请、填写 API 的步骤请到 [Caps Wirter 发布:按住大写锁定键,进行语音识别输入](https://hacpai.com/article/1594371212477) 或到 [ Caps Writer(电脑端语音输入工具)使用教程 ](https://www.bilibili.com/video/BV1qK4y1s7Fb/) 查看视频教程
|
* 拥有 **管理智能语音交互(NLS)** 权限的 **RAM访问控制** 用户的 **Accesskey Id**、**Accesskey Secret**
|
||||||
|
* 智能语音交互语音识别项目的 **appkey**
|
||||||
|
|
||||||
### 🛠 开发使用
|
就可以正常使用了。
|
||||||
|
|
||||||
本工具是一个python脚本,上面小白下载的 Release 其实是用 pyinstaller 导出的 exe 文件,如果你想在源码基础上使用,就需要安装以下模块:
|
详细申请、填写 API 的步骤请到 [ CapsWriter 2.0 使用视频 ](https://www.bilibili.com/video/BV12A411p73r/) 查看视频教程。
|
||||||
|
|
||||||
- keyboard
|
Mac 和 Linux 用户,你们也可以使用,只是我没有 Mac 和 Linux 的电脑,无法打包。需要你们下载源代码、安装依赖库,再打包或者直接运行。
|
||||||
- pyaudio
|
|
||||||
- configparser
|
### 🛠 源代码使用
|
||||||
- aliyunsdkcore
|
|
||||||
- alibabacloud-nls-python-sdk
|
小白下载的 Release 其实是用 pyinstaller 导出的 exe 文件,如果你需要在源码基础上使用,就需要安装以下模块:
|
||||||
|
|
||||||
|
- keyboard (用于监听键盘输入)
|
||||||
|
- pyaudio (用于接收录音)
|
||||||
|
- PySide2 (图形界面框架)
|
||||||
|
- aliyun-python-sdk-core (阿里云 sdk)
|
||||||
|
- alibabacloud-nls-java-sdk (阿里云智能语音引擎 sdk)
|
||||||
|
|
||||||
其中:
|
其中:
|
||||||
|
|
||||||
- pyaudio 在 windows 上不是太好安装,可以先到 [这个链接](https://www.lfd.uci.edu/~gohlke/pythonlibs) 下载 pyaudio 对应版本的 whl 文件,再用 pip 安装
|
- pyaudio 在 windows 上不是太好安装,可以先到 [这个链接](https://www.lfd.uci.edu/~gohlke/pythonlibs) 下载 pyaudio 对应版本的 whl 文件,再用 pip 安装,Mac 和 Linux 上需要先安装 port audio,才能安装上 pyaudio
|
||||||
- alibabacloud-nls-python-sdk 不是通过 python 安装,而是通过 [阿里云官方文档的方法](https://help.aliyun.com/document_detail/120693.html) 进行安装。
|
- alibabacloud-nls-java-sdk 是指阿里云官方 java sdk 的 python 实现,它不是通过 pip 安装的(官方没有上传到 pypi ),而是通过 [阿里云官方文档的方法](https://www.alibabacloud.com/help/zh/doc-detail/120693.htm) 进行安装。
|
||||||
|
- 其它模块使用 pip 安装即可
|
||||||
|
|
||||||
另外,需要在 `token.ini` 中填入阿里云拥有 **管理智能语音交互(NLS)** 权限的 **RAM访问控制** 用户的 **accessID**、**accessKey** 和智能语音交互语音识别项目的 **appkey** 。
|
本文件夹内有一个 `安装指南` 文件夹,在里面可以找到详细的安装指南,还包括了提前下载的 `alibabacloud-nls-python-sdk` 和 `pyaudio` 的 whl 文件。
|
||||||
|
|
||||||
本文件夹内有一个 `安装指南` 文件夹,在里面可以找到详细的安装指南,还包括了提前下载的 alibabacloud-nls-python-sdk 和 pyaudio 的 whl 文件。
|
## ☕ 打赏
|
||||||
|
|
||||||
用 python 运行 `run.py` 后,按下 `Caps Lock`(也就是大写锁定键)超过 0.3 秒后,就会开始用阿里云的 api 进行语音识别,松开按键后,会将识别结果自动输入。
|
万水千山总是情,一块几块都是情。本软件完全开源,用爱发电,如果你愿意,可以以打赏的方式支持我一下:
|
||||||
|
|
||||||
### 后话
|
<img src="src/misc/sponsor.jpg" alt="sponsor" style="zoom:50%;" />
|
||||||
|
|
||||||
因为作者就是本着凑合能用就可以了的心态做这个工具的,所以图形界面什么的也没做,整个工具单纯就一个脚本,功能也就一个,按住大写锁定键开始语音识别,松开后输入结果。目前作者本人已经很满意。
|
|
||||||
|
|
||||||
欢迎有想法有能力的人将这个工具加以改进,比如加入讯飞、腾讯、百度的语音识别api,长按0.3秒后开始识别时加一个提示等等等等。
|
|
||||||
|
|
||||||
目前已知改进的方向:
|
## 😀 交流
|
||||||
|
|
||||||
- 使用 VoiceRecognition 中的 google_recognize 进行识别,使用的是谷歌的免费语音识别 api,优势是不用用户个人申请 api 了,但是在中国大陆不太好使用。在海外的话会非常好用。
|
如果有软件方面的反馈可以提交 issues,或者加入 QQ 群:[1146626791](https://qm.qq.com/cgi-bin/qm/qr?k=DgiFh5cclAElnELH4mOxqWUBxReyEVpm&jump_from=webapi)
|
||||||
- 使用 Baidu AI 语音识别 api,每个账户有 200 万次的免费额度。
|
|
||||||
- 使用 Tencent AI 语音识别 api,每个账户有 5000 次的免费额度。
|
|
||||||
- 使用讯飞的语音识别 api,每个账户有 1 年的免费使用时间。
|
|
||||||
|
|
||||||
欢迎有兴趣的贡献者对项目进行翻译(国际化),添加 Google、Bing 的 api,让海外用户也可以使用这个便捷的语音输入工具!
|
|
BIN
assets/image-20201225053752740.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
assets/image-20201225140607971.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
assets/sponsor.jpg
Normal file
After Width: | Height: | Size: 166 KiB |
@ -2,4 +2,4 @@ setuptools
|
|||||||
pyaudio
|
pyaudio
|
||||||
keyboard
|
keyboard
|
||||||
aliyunsdkcore
|
aliyunsdkcore
|
||||||
configparser
|
PySide2
|
217
run.py
@ -1,217 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import pyaudio
|
|
||||||
import threading
|
|
||||||
import keyboard
|
|
||||||
|
|
||||||
import time
|
|
||||||
import ali_speech
|
|
||||||
from ali_speech.callbacks import SpeechRecognizerCallback
|
|
||||||
from ali_speech.constant import ASRFormat
|
|
||||||
from ali_speech.constant import ASRSampleRate
|
|
||||||
|
|
||||||
from aliyunsdkcore.client import AcsClient
|
|
||||||
from aliyunsdkcore.request import CommonRequest
|
|
||||||
import configparser
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""pyaudio参数"""
|
|
||||||
CHUNK = 1024 # 数据包或者数据片段
|
|
||||||
FORMAT = pyaudio.paInt16 # pyaudio.paInt16表示我们使用量化位数 16位来进行录音
|
|
||||||
CHANNELS = 1 # 声道,1为单声道,2为双声道
|
|
||||||
RATE = 16000 # 采样率,每秒钟16000次
|
|
||||||
|
|
||||||
count = 1 # 计数
|
|
||||||
pre = True # 是否准备开始录音
|
|
||||||
run = False # 控制录音是否停止
|
|
||||||
|
|
||||||
|
|
||||||
class MyCallback(SpeechRecognizerCallback):
|
|
||||||
"""
|
|
||||||
构造函数的参数没有要求,可根据需要设置添加
|
|
||||||
示例中的name参数可作为待识别的音频文件名,用于在多线程中进行区分
|
|
||||||
"""
|
|
||||||
def __init__(self, name='default'):
|
|
||||||
self._name = name
|
|
||||||
def on_started(self, message):
|
|
||||||
#print('MyCallback.OnRecognitionStarted: %s' % message)
|
|
||||||
pass
|
|
||||||
def on_result_changed(self, message):
|
|
||||||
print('任务信息: task_id: %s, result: %s' % (
|
|
||||||
message['header']['task_id'], message['payload']['result']))
|
|
||||||
def on_completed(self, message):
|
|
||||||
print('结果: %s' % (
|
|
||||||
message['payload']['result']))
|
|
||||||
result = message['payload']['result']
|
|
||||||
try:
|
|
||||||
if result[-1] == '。': # 如果最后一个符号是句号,就去掉。
|
|
||||||
result = result[0:-1]
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
keyboard.write(result) # 输入识别结果
|
|
||||||
keyboard.press_and_release('caps lock') # 再按下大写锁定键,还原大写锁定
|
|
||||||
def on_task_failed(self, message):
|
|
||||||
print('MyCallback.OnRecognitionTaskFailed: %s' % message)
|
|
||||||
def on_channel_closed(self):
|
|
||||||
# print('MyCallback.OnRecognitionChannelClosed')
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_token():
|
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read_file(open('token.ini'))
|
|
||||||
token = config.get("Token","Id")
|
|
||||||
expireTime = config.get("Token","ExpireTime")
|
|
||||||
accessID = config.get("Token","accessKeyId")
|
|
||||||
accessKey = config.get("Token","accessKeySecret")
|
|
||||||
# 要是 token 还有 5 秒过期,那就重新获得一个。
|
|
||||||
if (int(expireTime) - time.time()) < 5 :
|
|
||||||
# 创建AcsClient实例
|
|
||||||
client = AcsClient(
|
|
||||||
accessID, # 填写 AccessID
|
|
||||||
accessKey, # 填写 AccessKey
|
|
||||||
"cn-shanghai"
|
|
||||||
);
|
|
||||||
# 创建request,并设置参数
|
|
||||||
request = CommonRequest()
|
|
||||||
request.set_method('POST')
|
|
||||||
request.set_domain('nls-meta.cn-shanghai.aliyuncs.com')
|
|
||||||
request.set_version('2019-02-28')
|
|
||||||
request.set_action_name('CreateToken')
|
|
||||||
response = json.loads(client.do_action_with_exception(request))
|
|
||||||
token = response['Token']['Id']
|
|
||||||
expireTime = str(response['Token']['ExpireTime'])
|
|
||||||
config.set('Token', 'Id', token)
|
|
||||||
config.set('Token', 'ExpireTime', expireTime)
|
|
||||||
config.write(open("token.ini", "w"))
|
|
||||||
print
|
|
||||||
return token
|
|
||||||
|
|
||||||
def get_recognizer(client, appkey):
|
|
||||||
token = get_token()
|
|
||||||
audio_name = 'none'
|
|
||||||
callback = MyCallback(audio_name)
|
|
||||||
recognizer = client.create_recognizer(callback)
|
|
||||||
recognizer.set_appkey(appkey)
|
|
||||||
recognizer.set_token(token)
|
|
||||||
recognizer.set_format(ASRFormat.PCM)
|
|
||||||
recognizer.set_sample_rate(ASRSampleRate.SAMPLE_RATE_16K)
|
|
||||||
recognizer.set_enable_intermediate_result(False)
|
|
||||||
recognizer.set_enable_punctuation_prediction(True)
|
|
||||||
recognizer.set_enable_inverse_text_normalization(True)
|
|
||||||
return(recognizer)
|
|
||||||
|
|
||||||
# 因为关闭 recognizer 有点慢,就须做成一个函数,用多线程关闭它。
|
|
||||||
def close_recognizer():
|
|
||||||
global recognizer
|
|
||||||
recognizer.close()
|
|
||||||
|
|
||||||
# 处理热键响应
|
|
||||||
def on_hotkey(event):
|
|
||||||
global pre, run
|
|
||||||
if event.event_type == "down":
|
|
||||||
if pre and (not run):
|
|
||||||
pre = False
|
|
||||||
run = True
|
|
||||||
threading.Thread(target=process).start()
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
pre, run = True, False
|
|
||||||
|
|
||||||
# 处理是否开始录音
|
|
||||||
def process():
|
|
||||||
global run
|
|
||||||
# 等待 6 轮 0.05 秒,如果 run 还是 True,就代表还没有松开大写键,是在长按状态,那么就可以开始识别。
|
|
||||||
for i in range(6):
|
|
||||||
if run:
|
|
||||||
time.sleep(0.05)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
global count, recognizer, p, appkey
|
|
||||||
threading.Thread(target=recoder,args=(recognizer, p)).start() # 开始录音识别
|
|
||||||
count += 1
|
|
||||||
recognizer = get_recognizer(client, appkey) # 为下一次监听提前准备好 recognizer
|
|
||||||
|
|
||||||
# 录音识别处理
|
|
||||||
def recoder(recognizer, p):
|
|
||||||
global run
|
|
||||||
try:
|
|
||||||
stream = p.open(channels=CHANNELS,
|
|
||||||
format=FORMAT,
|
|
||||||
rate=RATE,
|
|
||||||
input=True,
|
|
||||||
frames_per_buffer=CHUNK)
|
|
||||||
print('\r{}//:在听了,说完了请松开 CapsLock 键...'.format(count), end=' ')
|
|
||||||
ret = recognizer.start()
|
|
||||||
if ret < 0:
|
|
||||||
return ret
|
|
||||||
while run:
|
|
||||||
data = stream.read(CHUNK)
|
|
||||||
ret = recognizer.send(data)
|
|
||||||
if ret < 0:
|
|
||||||
break
|
|
||||||
recognizer.stop()
|
|
||||||
stream.stop_stream()
|
|
||||||
stream.close()
|
|
||||||
# p.terminate()
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
finally:
|
|
||||||
threading.Thread(target=close_recognizer).start() # 关闭 recognizer
|
|
||||||
print('{}//:按住 CapsLock 键 0.3 秒后开始说话...'.format(count), end=' ')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
print("""\r\nCaps Writer 开始运行
|
|
||||||
|
|
||||||
开源发布地址:https://github.com/HaujetZhao/CapsWriter
|
|
||||||
|
|
||||||
下载地址:https://github.com/HaujetZhao/CapsWriter/releases
|
|
||||||
|
|
||||||
视频教程地址:https://www.bilibili.com/video/BV1qK4y1s7Fb/
|
|
||||||
|
|
||||||
作者:淳帅二代(HaujetZhao)
|
|
||||||
|
|
||||||
软件基于 MIT 协议
|
|
||||||
|
|
||||||
""")
|
|
||||||
|
|
||||||
if not os.path.exists('token.ini'):
|
|
||||||
init_id = """[Token]
|
|
||||||
id = 0000000000000000000
|
|
||||||
expiretime = 0000000000
|
|
||||||
accessKeyId = 000000
|
|
||||||
accessKeySecret = 000000
|
|
||||||
appkey = 00000"""
|
|
||||||
fp = open("token.ini",'w')
|
|
||||||
fp.write(init_id)
|
|
||||||
fp.close()
|
|
||||||
input("""\r\n 检测到没有配置文件,所以刚刚已在同级目录生成了 token.ini 配置文件,\r\n
|
|
||||||
请打开 token.ini 配置文件,\r\n
|
|
||||||
然后填入阿里云的 accesskeyid 和 accesskeysecret, 以及你的语音识别项目的 appkey,\r\n
|
|
||||||
再回到本界面,按任意键后,回车继续\r\n
|
|
||||||
如果下面出错了,那么就很有可能是 accesskeyid 、accesskeysecret 或 appkey 填错了\r\n""")
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read_file(open('token.ini'))
|
|
||||||
appkey = config.get("Token","appkey")
|
|
||||||
|
|
||||||
client = ali_speech.NlsClient()
|
|
||||||
client.set_log_level('WARNING') # 设置 client 输出日志信息的级别:DEBUG、INFO、WARNING、ERROR
|
|
||||||
|
|
||||||
recognizer = get_recognizer(client, appkey)
|
|
||||||
p = pyaudio.PyAudio()
|
|
||||||
|
|
||||||
print("""\r\n初始化完成,现在可以将本工具最小化,在需要输入的界面,按住 CapsLock 键 0.3 秒后开始说话,松开 CapsLock 键后识别结果会自动输入\r\n""")
|
|
||||||
|
|
||||||
keyboard.hook_key('caps lock', on_hotkey)
|
|
||||||
print('{}//:按住 CapsLock 键 0.3 秒后开始说话...'.format(count), end=' ')
|
|
||||||
keyboard.wait()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
40
src/__init__.pyw
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
import os, sys, time
|
||||||
|
try:
|
||||||
|
os.chdir(os.path.dirname(__file__)) # 更改工作目录,指向正确的当前文件夹
|
||||||
|
sys.path.append(os.path.dirname(__file__)) # 将当前目录导入 python 寻找 package 和 moduel 的变量
|
||||||
|
except:
|
||||||
|
print('更改使用路径失败,不过没关系')
|
||||||
|
# os.environ['PATH'] += os.pathsep + os.path.abspath('./bin') # 将可执行文件的目录加入环境变量
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
|
||||||
|
from moduels.function.createDB import createDB # 引入检查和创建创建数据库的函数
|
||||||
|
|
||||||
|
from moduels.gui.MainWindow import MainWindow
|
||||||
|
from moduels.gui.SystemTray import SystemTray
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
|
||||||
|
|
||||||
|
############# 主窗口和托盘 ################
|
||||||
|
|
||||||
|
def 高分屏变量设置(app):
|
||||||
|
os.environ['QT_SCALE_FACTOR'] = '1'
|
||||||
|
app.setAttribute(Qt.AA_EnableHighDpiScaling)
|
||||||
|
QCoreApplication.instance().setAttribute(Qt.AA_UseHighDpiPixmaps)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
高分屏变量设置(app)
|
||||||
|
createDB()
|
||||||
|
mainWindow = MainWindow()
|
||||||
|
tray = SystemTray(mainWindow)
|
||||||
|
常量.托盘 = tray
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
BIN
src/misc/icon.icns
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
src/misc/icon.ico
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
src/misc/icon_listning.icns
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src/misc/icon_listning.ico
Normal file
After Width: | Height: | Size: 66 KiB |
2
src/misc/png转ico和icns.bat
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
magick convert "%1" -resize 128x128 "%~dp1%~n1.ico"
|
||||||
|
magick convert "%1" -resize 128x128 "%~dp1%~n1.icns"
|
5
src/misc/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
setuptools
|
||||||
|
pyaudio
|
||||||
|
keyboard
|
||||||
|
aliyunsdkcore
|
||||||
|
PySide2
|
BIN
src/misc/sponsor.jpg
Normal file
After Width: | Height: | Size: 283 KiB |
42
src/misc/style.css
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*参考这里:https://doc.qt.io/qt-5/stylesheet-reference.html*/
|
||||||
|
|
||||||
|
|
||||||
|
/*切换到分割视频 Tab,里面有几个上面带字的功能框,那些框框就是 QGroupBox */
|
||||||
|
QGroupBox{
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius:6px;
|
||||||
|
margin-top: 2ex;
|
||||||
|
margin-bottom: 0.5ex;
|
||||||
|
padding: 0.3em 0.4em 0.4em 0.3em; /* 上 右 下 左*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 这就是 QGroupBox 上面的标题 */
|
||||||
|
QGroupBox:title {
|
||||||
|
color: #005980;
|
||||||
|
subcontrol-origin: margin;
|
||||||
|
margin-top: 0.5ex;
|
||||||
|
left: 2ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
QPushButton {
|
||||||
|
border: 2px solid #8f8f91;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 1em 0.4em 1em 0.3em; /* 上 右 下 左*
|
||||||
|
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||||
|
stop: 0 #f6f7fa, stop: 1 #dadbde);
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||||
|
stop: 0 #dadbde, stop: 1 #f6f7fa);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton:flat {
|
||||||
|
border: none; /* no border for a flat push button *
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton:default {
|
||||||
|
border-color: navy; /* make the default button prominent *
|
||||||
|
}*/
|
38
src/moduels/component/Ali_CallBack.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pyaudio
|
||||||
|
import threading
|
||||||
|
import keyboard
|
||||||
|
import time
|
||||||
|
|
||||||
|
from ali_speech.callbacks import SpeechRecognizerCallback
|
||||||
|
|
||||||
|
class Ali_Callback(SpeechRecognizerCallback):
|
||||||
|
"""
|
||||||
|
构造函数的参数没有要求,可根据需要设置添加
|
||||||
|
示例中的name参数可作为待识别的音频文件名,用于在多线程中进行区分
|
||||||
|
"""
|
||||||
|
def __init__(self, name='default'):
|
||||||
|
self._name = name
|
||||||
|
def on_started(self, message):
|
||||||
|
#print('MyCallback.OnRecognitionStarted: %s' % message)
|
||||||
|
pass
|
||||||
|
def on_result_changed(self, message):
|
||||||
|
print('任务信息: task_id: %s, result: %s' % (
|
||||||
|
message['header']['task_id'], message['payload']['result']))
|
||||||
|
def on_completed(self, message):
|
||||||
|
print('结果: %s' % (
|
||||||
|
message['payload']['result']))
|
||||||
|
result = message['payload']['result']
|
||||||
|
try:
|
||||||
|
if result[-1] == '。': # 如果最后一个符号是句号,就去掉。
|
||||||
|
result = result[0:-1]
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
keyboard.write(result) # 输入识别结果
|
||||||
|
def on_task_failed(self, message):
|
||||||
|
print('MyCallback.OnRecognitionTaskFailed: %s' % message)
|
||||||
|
def on_channel_closed(self):
|
||||||
|
# print('MyCallback.OnRecognitionChannelClosed')
|
||||||
|
pass
|
39
src/moduels/component/NormalValue.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import sqlite3
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
class NormalValue():
|
||||||
|
样式文件 = 'misc/style.css'
|
||||||
|
软件版本 = '2.0.0'
|
||||||
|
|
||||||
|
主窗口 = None
|
||||||
|
托盘 = None
|
||||||
|
状态栏 = None
|
||||||
|
|
||||||
|
Token配置路径 = 'misc/Token.ini'
|
||||||
|
|
||||||
|
数据库路径 = 'misc/database.db'
|
||||||
|
数据库连接 = sqlite3.connect(数据库路径)
|
||||||
|
|
||||||
|
偏好设置表单名 = '偏好设置'
|
||||||
|
语音引擎表单名 = '语音引擎'
|
||||||
|
|
||||||
|
关闭时隐藏到托盘 = False
|
||||||
|
|
||||||
|
|
||||||
|
系统平台 = platform.system()
|
||||||
|
|
||||||
|
图标路径 = 'misc/icon.icns' if 系统平台 == 'Darwin' else 'misc/icon.ico'
|
||||||
|
聆听图标路径 = 'misc/icon_listning.icns' if 系统平台 == 'Darwin' else 'misc/icon_listning.ico'
|
||||||
|
|
||||||
|
subprocessStartUpInfo = subprocess.STARTUPINFO()
|
||||||
|
if 系统平台 == 'Windows':
|
||||||
|
subprocessStartUpInfo.dwFlags = subprocess.STARTF_USESHOWWINDOW
|
||||||
|
subprocessStartUpInfo.wShowWindow = subprocess.SW_HIDE
|
||||||
|
|
||||||
|
class ThreadValue():
|
||||||
|
pass
|
||||||
|
|
||||||
|
常量 = NormalValue()
|
||||||
|
线程值 = ThreadValue()
|
21
src/moduels/component/QEditBox_StdoutBox.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
|
||||||
|
# 命令输出窗口中的多行文本框
|
||||||
|
class QEditBox_StdoutBox(QTextEdit):
|
||||||
|
# 定义一个 QTextEdit 类,写入 print 方法。用于输出显示。
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(QEditBox_StdoutBox, self).__init__(parent)
|
||||||
|
self.setReadOnly(True)
|
||||||
|
|
||||||
|
def print(self, text):
|
||||||
|
try:
|
||||||
|
cursor = self.textCursor()
|
||||||
|
cursor.movePosition(QTextCursor.End)
|
||||||
|
cursor.insertText(text)
|
||||||
|
self.setTextCursor(cursor)
|
||||||
|
self.ensureCursorVisible()
|
||||||
|
except:
|
||||||
|
print('文本框更新文本失败')
|
24
src/moduels/component/SponsorDialog.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
|
||||||
|
|
||||||
|
# 打赏对话框
|
||||||
|
class SponsorDialog(QDialog):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(SponsorDialog, self).__init__(parent)
|
||||||
|
self.resize(500, 567)
|
||||||
|
图标路径 = 'misc/icon.icns' if 常量.系统平台 == 'Darwin' else 'misc/icon.ico'
|
||||||
|
self.setWindowIcon(QIcon(图标路径))
|
||||||
|
self.setWindowTitle(self.tr('打赏作者'))
|
||||||
|
self.setWindowModality(Qt.NonModal) # 让窗口不要阻挡主窗口
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
painter = QPainter(self)
|
||||||
|
pixmap = QPixmap('misc/sponsor.jpg')
|
||||||
|
painter.drawPixmap(self.rect(), pixmap)
|
19
src/moduels/component/Stream.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
|
||||||
|
|
||||||
|
class Stream(QObject):
|
||||||
|
# 用于将控制台的输出定向到一个槽
|
||||||
|
newText = Signal(str)
|
||||||
|
|
||||||
|
# def __init__(self):
|
||||||
|
# super().__init__()
|
||||||
|
# self.newText = Signal(str)
|
||||||
|
|
||||||
|
def write(self, text):
|
||||||
|
self.newText.emit(str(text))
|
||||||
|
# QApplication.processEvents()
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
pass
|
49
src/moduels/function/createDB.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
|
||||||
|
def createDB():
|
||||||
|
|
||||||
|
数据库连接 = 常量.数据库连接
|
||||||
|
偏好设置表单名 = 常量.偏好设置表单名
|
||||||
|
语音引擎表单名 = 常量.语音引擎表单名
|
||||||
|
# 模板表单名 = 常量.数据库模板表单名
|
||||||
|
# 皮肤表单名 = 常量.数据库皮肤表单名
|
||||||
|
cursor = 数据库连接.cursor()
|
||||||
|
|
||||||
|
result = cursor.execute(f'select * from sqlite_master where name = "{偏好设置表单名}";')
|
||||||
|
if result.fetchone() == None:
|
||||||
|
cursor.execute(f'''create table {偏好设置表单名} (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
item text,
|
||||||
|
value text
|
||||||
|
)''')
|
||||||
|
else:
|
||||||
|
print('偏好设置表单已存在')
|
||||||
|
#
|
||||||
|
result = cursor.execute(f'select * from sqlite_master where name = "{语音引擎表单名}";')
|
||||||
|
if result.fetchone() == None:
|
||||||
|
cursor.execute(f'''create table {语音引擎表单名} (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
引擎名称 text,
|
||||||
|
服务商 text,
|
||||||
|
AppKey text,
|
||||||
|
语言 text,
|
||||||
|
AccessKeyId text,
|
||||||
|
AccessKeySecret text
|
||||||
|
)''')
|
||||||
|
else:
|
||||||
|
print('语音引擎表单名已存在')
|
||||||
|
#
|
||||||
|
# result = cursor.execute(f'select * from sqlite_master where name = "{皮肤表单名}";')
|
||||||
|
# if result.fetchone() == None:
|
||||||
|
# cursor.execute(f'''create table {皮肤表单名} (
|
||||||
|
# id integer primary key autoincrement,
|
||||||
|
# skinName text,
|
||||||
|
# outputFileName text,
|
||||||
|
# sourceFilePath text,
|
||||||
|
# supportDarkMode BOOLEAN)''')
|
||||||
|
# else:
|
||||||
|
# print('皮肤表单已存在')
|
||||||
|
#
|
||||||
|
数据库连接.commit() # 最后要提交更改
|
26
src/moduels/function/getAlibabaRecognizer.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
import configparser, sqlite3, json
|
||||||
|
|
||||||
|
from ali_speech.constant import ASRFormat
|
||||||
|
from ali_speech.constant import ASRSampleRate
|
||||||
|
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
from moduels.component.Ali_CallBack import Ali_Callback
|
||||||
|
from moduels.function.getAlibabaToken import getAlibabaToken
|
||||||
|
|
||||||
|
def getAlibabaRecognizer(client, appkey, accessKeyId, accessKeySecret, tokenId, tokenExpireTime, 线程):
|
||||||
|
tokenId, tokenExpireTime = getAlibabaToken(accessKeyId, accessKeySecret, tokenId, tokenExpireTime)
|
||||||
|
if tokenId == False: return False
|
||||||
|
线程.tokenId = tokenId
|
||||||
|
线程.tokenExpireTime = tokenExpireTime
|
||||||
|
audio_name = 'none'
|
||||||
|
callback = Ali_Callback(audio_name)
|
||||||
|
recognizer = client.create_recognizer(callback)
|
||||||
|
recognizer.set_appkey(appkey)
|
||||||
|
recognizer.set_token(tokenId)
|
||||||
|
recognizer.set_format(ASRFormat.PCM)
|
||||||
|
recognizer.set_sample_rate(ASRSampleRate.SAMPLE_RATE_16K)
|
||||||
|
recognizer.set_enable_intermediate_result(False)
|
||||||
|
recognizer.set_enable_punctuation_prediction(True)
|
||||||
|
recognizer.set_enable_inverse_text_normalization(True)
|
||||||
|
return (recognizer)
|
42
src/moduels/function/getAlibabaToken.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
import configparser, sqlite3, json, time, sys
|
||||||
|
|
||||||
|
|
||||||
|
from aliyunsdkcore.client import AcsClient
|
||||||
|
from aliyunsdkcore.request import CommonRequest
|
||||||
|
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
|
||||||
|
def getAlibabaToken(accessID, accessKey, tokenId, tokenExpireTime):
|
||||||
|
# 要是 token 还有 50 秒过期,那就重新获得一个。
|
||||||
|
if (int(tokenExpireTime) - time.time()) < 50 :
|
||||||
|
# 创建AcsClient实例
|
||||||
|
client = AcsClient(
|
||||||
|
accessID, # 填写 AccessID
|
||||||
|
accessKey, # 填写 AccessKey = 得到AccessKey(引擎名称)
|
||||||
|
"cn-shanghai"
|
||||||
|
);
|
||||||
|
# 创建request,并设置参数
|
||||||
|
request = CommonRequest()
|
||||||
|
request.set_method('POST')
|
||||||
|
request.set_domain('nls-meta.cn-shanghai.aliyuncs.com')
|
||||||
|
request.set_version('2019-02-28')
|
||||||
|
request.set_action_name('CreateToken')
|
||||||
|
try:
|
||||||
|
response = json.loads(client.do_action_with_exception(request))
|
||||||
|
except Exception as e:
|
||||||
|
print(f'''获取 Token 出错了,出错信息如下:\n{e}\n''')
|
||||||
|
return False, False
|
||||||
|
tokenId = response['Token']['Id']
|
||||||
|
tokenExpireTime = str(response['Token']['ExpireTime'])
|
||||||
|
return tokenId, tokenExpireTime
|
||||||
|
|
||||||
|
# def 得到AccessKey(引擎名称):
|
||||||
|
# 数据库连接 = sqlite3.connect(常量.数据库路径)
|
||||||
|
# AccessKeyId, AccessKeySecret = 数据库连接.execute(f'''select AccessKeyId,
|
||||||
|
# AccessKeySecret
|
||||||
|
# from {常量.语音引擎表单名}
|
||||||
|
# where 引擎名称 = :引擎名称''',
|
||||||
|
# {'引擎名称': 引擎名称}).fetchone()
|
||||||
|
# 数据库连接.close()
|
||||||
|
# return AccessKeyId, AccessKeySecret
|
62
src/moduels/gui/Combo_EngineList.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
import os, sqlite3
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
|
||||||
|
# 添加预设对话框
|
||||||
|
class Combo_EngineList(QComboBox):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.initElements() # 先初始化各个控件
|
||||||
|
self.initSlots() # 再将各个控件连接到信号槽
|
||||||
|
self.initLayouts() # 然后布局
|
||||||
|
self.initValues() # 再定义各个控件的值
|
||||||
|
|
||||||
|
def initElements(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initSlots(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initLayouts(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initValues(self):
|
||||||
|
self.初始化列表()
|
||||||
|
|
||||||
|
def mousePressEvent(self, e):
|
||||||
|
self.列表更新()
|
||||||
|
self.showPopup()
|
||||||
|
|
||||||
|
def 初始化列表(self):
|
||||||
|
self.列表项 = []
|
||||||
|
数据库连接 = 常量.数据库连接
|
||||||
|
cursor = 数据库连接.cursor()
|
||||||
|
result = cursor.execute(f'''select 引擎名称 from {常量.语音引擎表单名} order by id;''').fetchall()
|
||||||
|
if len(result) != 0:
|
||||||
|
for item in result:
|
||||||
|
self.列表项.append(item[0])
|
||||||
|
self.addItems(self.列表项)
|
||||||
|
# if not os.path.exists(常量.音效文件路径): os.makedirs(常量.音效文件路径)
|
||||||
|
# with os.scandir(常量.音效文件路径) as 目录条目:
|
||||||
|
# for entry in 目录条目:
|
||||||
|
# if not entry.name.startswith('.') and entry.is_dir():
|
||||||
|
# self.列表项.append(entry.name)
|
||||||
|
|
||||||
|
|
||||||
|
def 列表更新(self):
|
||||||
|
新列表 = []
|
||||||
|
数据库连接 = 常量.数据库连接
|
||||||
|
cursor = 数据库连接.cursor()
|
||||||
|
result = cursor.execute(f'''select 引擎名称 from {常量.语音引擎表单名} order by id;''').fetchall()
|
||||||
|
if len(result) != 0:
|
||||||
|
for item in result:
|
||||||
|
新列表.append(item[0])
|
||||||
|
if self.列表项 == 新列表: return True
|
||||||
|
self.clear()
|
||||||
|
self.列表项 = 新列表
|
||||||
|
self.addItems(self.列表项)
|
||||||
|
|
190
src/moduels/gui/Dialog_AddEngine.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
|
||||||
|
|
||||||
|
class Dialog_AddEngine(QDialog):
|
||||||
|
def __init__(self, 列表, 数据库连接, 表单名字, 显示的列名):
|
||||||
|
super().__init__(常量.主窗口)
|
||||||
|
self.列表 = 列表
|
||||||
|
self.数据库连接 = 数据库连接
|
||||||
|
self.表单名字 = 表单名字
|
||||||
|
self.显示的列名 = 显示的列名
|
||||||
|
self.initElements() # 先初始化各个控件
|
||||||
|
self.initSlots() # 再将各个控件连接到信号槽
|
||||||
|
self.initLayouts() # 然后布局
|
||||||
|
self.initValues() # 再定义各个控件的值
|
||||||
|
|
||||||
|
def initElements(self):
|
||||||
|
self.引擎名称编辑框 = QLineEdit()
|
||||||
|
self.服务商选择框 = QComboBox()
|
||||||
|
self.appKey输入框 = QLineEdit()
|
||||||
|
self.语言Combobox = QComboBox()
|
||||||
|
self.accessKeyId输入框 = QLineEdit()
|
||||||
|
self.AccessKeySecret输入框 = QLineEdit()
|
||||||
|
|
||||||
|
self.确定按钮 = QPushButton(self.tr('确定'))
|
||||||
|
self.取消按钮 = QPushButton(self.tr('取消'))
|
||||||
|
|
||||||
|
self.纵向布局 = QVBoxLayout()
|
||||||
|
self.表格布局 = QFormLayout()
|
||||||
|
self.按钮横向布局 = QHBoxLayout()
|
||||||
|
|
||||||
|
def initSlots(self):
|
||||||
|
self.服务商选择框.currentTextChanged.connect(self.服务商变化)
|
||||||
|
|
||||||
|
self.确定按钮.clicked.connect(self.确认)
|
||||||
|
self.取消按钮.clicked.connect(self.取消)
|
||||||
|
|
||||||
|
def initLayouts(self):
|
||||||
|
self.表格布局.addRow('引擎名称:', self.引擎名称编辑框)
|
||||||
|
self.表格布局.addRow('服务商:', self.服务商选择框)
|
||||||
|
self.表格布局.addRow('AppKey:', self.appKey输入框)
|
||||||
|
self.表格布局.addRow('语言:', self.语言Combobox)
|
||||||
|
self.表格布局.addRow('AccessKeyId:', self.accessKeyId输入框)
|
||||||
|
self.表格布局.addRow('AccessKeySecret:', self.AccessKeySecret输入框)
|
||||||
|
|
||||||
|
self.按钮横向布局.addWidget(self.确定按钮)
|
||||||
|
self.按钮横向布局.addWidget(self.取消按钮)
|
||||||
|
|
||||||
|
self.纵向布局.addLayout(self.表格布局)
|
||||||
|
self.纵向布局.addLayout(self.按钮横向布局)
|
||||||
|
|
||||||
|
self.setLayout(self.纵向布局)
|
||||||
|
|
||||||
|
def initValues(self):
|
||||||
|
self.引擎名称编辑框.setPlaceholderText(self.tr('例如:阿里-中文'))
|
||||||
|
|
||||||
|
self.服务商选择框.addItems(['Alibaba'])
|
||||||
|
self.服务商选择框.setCurrentText('Alibaba')
|
||||||
|
|
||||||
|
self.accessKeyId输入框.setEchoMode(QLineEdit.Password)
|
||||||
|
self.AccessKeySecret输入框.setEchoMode(QLineEdit.Password)
|
||||||
|
|
||||||
|
self.setWindowIcon(QIcon(常量.图标路径))
|
||||||
|
self.setWindowTitle(self.tr('添加或更新 Api'))
|
||||||
|
self.setWindowModality(Qt.NonModal)
|
||||||
|
|
||||||
|
if self.列表.currentItem():
|
||||||
|
已选中的列表项 = self.列表.currentItem().text()
|
||||||
|
填充数据 = self.从数据库得到选中项的数据(已选中的列表项)
|
||||||
|
self.引擎名称编辑框.setText(填充数据[0])
|
||||||
|
self.服务商选择框.setCurrentText(填充数据[1])
|
||||||
|
self.appKey输入框.setText(填充数据[2])
|
||||||
|
self.语言Combobox.setCurrentText(填充数据[3])
|
||||||
|
self.accessKeyId输入框.setText(填充数据[4])
|
||||||
|
self.AccessKeySecret输入框.setText(填充数据[5])
|
||||||
|
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
|
||||||
|
def 服务商变化(self):
|
||||||
|
if self.服务商选择框.currentText() == 'Alibaba':
|
||||||
|
self.语言Combobox.clear()
|
||||||
|
self.语言Combobox.addItem(self.tr('由 Api 的云端配置决定'))
|
||||||
|
self.语言Combobox.setCurrentText(self.tr('由 Api 的云端配置决定'))
|
||||||
|
self.语言Combobox.setEnabled(False)
|
||||||
|
self.appKey输入框.setEnabled(True)
|
||||||
|
# self.accessKeyId标签.setText('AccessKeyId:')
|
||||||
|
# self.AccessKeySecret标签.setText('AccessKeySecret:')
|
||||||
|
elif self.服务商选择框.currentText() == 'Tencent':
|
||||||
|
self.语言Combobox.clear()
|
||||||
|
self.语言Combobox.addItems(['中文普通话', '英语', '粤语'])
|
||||||
|
self.语言Combobox.setCurrentText('中文普通话')
|
||||||
|
self.语言Combobox.setEnabled(True)
|
||||||
|
self.appKey输入框.setEnabled(False)
|
||||||
|
# self.accessKeyId标签.setText('AccessSecretId:')
|
||||||
|
# self.AccessKeySecret标签.setText('AccessSecretKey:')
|
||||||
|
|
||||||
|
|
||||||
|
def 确认(self):
|
||||||
|
self.引擎名称 = self.引擎名称编辑框.text() # str
|
||||||
|
self.服务商 = self.服务商选择框.currentText() # str
|
||||||
|
self.AppKey = self.appKey输入框.text() # str
|
||||||
|
self.语言 = self.语言Combobox.currentText() # str
|
||||||
|
self.AccessKeyId = self.accessKeyId输入框.text() # str
|
||||||
|
self.AccessKeySecret = self.AccessKeySecret输入框.text() # str
|
||||||
|
self.有重名项 = self.检查数据库是否有重名项()
|
||||||
|
if self.引擎名称 == '':
|
||||||
|
return False
|
||||||
|
if self.有重名项:
|
||||||
|
是否覆盖 = QMessageBox.warning(self, '覆盖确认', '已存在相同名字的引擎,是否覆盖?', QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel)
|
||||||
|
if 是否覆盖 != QMessageBox.Yes:
|
||||||
|
return False
|
||||||
|
self.更新数据库()
|
||||||
|
else:
|
||||||
|
self.插入数据库()
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def 取消(self):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def 从数据库得到选中项的数据(self, 已选中的列表项):
|
||||||
|
数据库连接 = self.数据库连接
|
||||||
|
cursor = 数据库连接.cursor()
|
||||||
|
result = cursor.execute(f'''select 引擎名称,
|
||||||
|
服务商,
|
||||||
|
AppKey,
|
||||||
|
语言,
|
||||||
|
AccessKeyId,
|
||||||
|
AccessKeySecret
|
||||||
|
from {self.表单名字} where {self.显示的列名} = :引擎名称;''',
|
||||||
|
{'引擎名称': 已选中的列表项})
|
||||||
|
return result.fetchone()
|
||||||
|
#
|
||||||
|
def 检查数据库是否有重名项(self):
|
||||||
|
数据库连接 = self.数据库连接
|
||||||
|
cursor = 数据库连接.cursor()
|
||||||
|
result = cursor.execute(f'''select * from {self.表单名字} where {self.显示的列名} = :引擎名称;''', {'引擎名称': self.引擎名称})
|
||||||
|
if result.fetchone() == None: return False # 没有重名项,返回 False
|
||||||
|
return True
|
||||||
|
#
|
||||||
|
def 更新数据库(self):
|
||||||
|
数据库连接 = self.数据库连接
|
||||||
|
cursor = 数据库连接.cursor()
|
||||||
|
cursor.execute(f'''update {self.表单名字} set 服务商 = :服务商,
|
||||||
|
AppKey = :AppKey,
|
||||||
|
语言 = :语言,
|
||||||
|
AccessKeyId = :AccessKeyId,
|
||||||
|
AccessKeySecret = :AccessKeySecret
|
||||||
|
where {self.显示的列名} = :引擎名称 ''',
|
||||||
|
{'服务商': self.服务商,
|
||||||
|
'AppKey': self.AppKey,
|
||||||
|
'语言': self.语言,
|
||||||
|
'AccessKeyId': self.AccessKeyId,
|
||||||
|
'AccessKeySecret': self.AccessKeySecret,
|
||||||
|
'引擎名称': self.引擎名称})
|
||||||
|
数据库连接.commit()
|
||||||
|
#
|
||||||
|
def 插入数据库(self):
|
||||||
|
数据库连接 = self.数据库连接
|
||||||
|
cursor = 数据库连接.cursor()
|
||||||
|
cursor.execute(f'''insert into {self.表单名字} (引擎名称,
|
||||||
|
服务商,
|
||||||
|
AppKey,
|
||||||
|
语言,
|
||||||
|
AccessKeyId,
|
||||||
|
AccessKeySecret)
|
||||||
|
values (:引擎名称,
|
||||||
|
:服务商,
|
||||||
|
:AppKey,
|
||||||
|
:语言,
|
||||||
|
:AccessKeyId,
|
||||||
|
:AccessKeySecret)''',
|
||||||
|
{'引擎名称': self.引擎名称,
|
||||||
|
'服务商': self.服务商,
|
||||||
|
'AppKey': self.AppKey,
|
||||||
|
'语言': self.语言,
|
||||||
|
'AccessKeyId': self.AccessKeyId,
|
||||||
|
'AccessKeySecret': self.AccessKeySecret})
|
||||||
|
数据库连接.commit()
|
||||||
|
|
||||||
|
# 根据刚开始预设名字是否为空,设置确定键可否使用
|
||||||
|
def closeEvent(self, a0: QCloseEvent) -> None:
|
||||||
|
try:
|
||||||
|
self.列表.刷新列表()
|
||||||
|
except:
|
||||||
|
print('引擎列表刷新失败')
|
108
src/moduels/gui/Group_EditableList.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from moduels.gui.List_List import List_List
|
||||||
|
|
||||||
|
# 添加预设对话框
|
||||||
|
class Group_EditableList(QGroupBox):
|
||||||
|
def __init__(self, 组名, 对话框类, 数据库连接, 表单名字, 显示的列名):
|
||||||
|
super().__init__(组名)
|
||||||
|
self.对话框类 = 对话框类
|
||||||
|
self.数据库连接 = 数据库连接
|
||||||
|
self.表单名字 = 表单名字
|
||||||
|
self.显示的列名 = 显示的列名
|
||||||
|
self.initElements() # 先初始化各个控件
|
||||||
|
self.initSlots() # 再将各个控件连接到信号槽
|
||||||
|
self.initLayouts() # 然后布局
|
||||||
|
self.initValues() # 再定义各个控件的值
|
||||||
|
|
||||||
|
def initElements(self):
|
||||||
|
self.筛选文字输入框 = QLineEdit()
|
||||||
|
self.列表 = List_List(self.数据库连接, self.表单名字, self.显示的列名)
|
||||||
|
self.添加按钮 = QPushButton('+')
|
||||||
|
self.删除按钮 = QPushButton('-')
|
||||||
|
self.上移按钮 = QPushButton('↑')
|
||||||
|
self.下移按钮 = QPushButton('↓')
|
||||||
|
self.部件布局 = QGridLayout()
|
||||||
|
|
||||||
|
def initSlots(self):
|
||||||
|
self.筛选文字输入框.textChanged.connect(self.筛选)
|
||||||
|
self.添加按钮.clicked.connect(self.添加或修改)
|
||||||
|
self.删除按钮.clicked.connect(self.删除)
|
||||||
|
self.上移按钮.clicked.connect(self.上移)
|
||||||
|
self.下移按钮.clicked.connect(self.下移)
|
||||||
|
|
||||||
|
def initLayouts(self):
|
||||||
|
self.部件布局.addWidget(self.筛选文字输入框, 0, 0, 1, 2)
|
||||||
|
self.部件布局.addWidget(self.列表, 1, 0, 1, 2)
|
||||||
|
self.部件布局.addWidget(self.添加按钮, 2, 0, 1, 1)
|
||||||
|
self.部件布局.addWidget(self.删除按钮, 2, 1, 1, 1)
|
||||||
|
self.部件布局.addWidget(self.上移按钮, 3, 0, 1, 1)
|
||||||
|
self.部件布局.addWidget(self.下移按钮, 3, 1, 1, 1)
|
||||||
|
self.setLayout(self.部件布局)
|
||||||
|
|
||||||
|
def initValues(self):
|
||||||
|
self.筛选文字输入框.setPlaceholderText('筛选')
|
||||||
|
self.列表.刷新列表()
|
||||||
|
|
||||||
|
|
||||||
|
def 添加或修改(self):
|
||||||
|
'''
|
||||||
|
打开对话框,添加或修改条目
|
||||||
|
'''
|
||||||
|
对话框 = self.对话框类(self.列表, self.数据库连接, self.表单名字, self.显示的列名)
|
||||||
|
|
||||||
|
def 删除(self):
|
||||||
|
if not self.列表.currentItem(): return False
|
||||||
|
当前排 = self.列表.currentRow()
|
||||||
|
已选中的列表项 = self.列表.currentItem().text()
|
||||||
|
answer = QMessageBox.question(self, self.tr('删除预设'), self.tr(f'将要删除“{已选中的列表项}”项,是否确认?'))
|
||||||
|
if answer != QMessageBox.Yes: return False
|
||||||
|
id = self.数据库连接.cursor().execute(
|
||||||
|
f'''select id from {self.表单名字} where {self.显示的列名} = :已选中的列表项''', {'已选中的列表项': 已选中的列表项}).fetchone()[0]
|
||||||
|
self.数据库连接.cursor().execute(f'''delete from {self.表单名字} where id = :id''', {'id': id})
|
||||||
|
self.数据库连接.cursor().execute(f'''update {self.表单名字} set id=id-1 where id > :id''', {'id': id})
|
||||||
|
self.数据库连接.commit()
|
||||||
|
self.列表.刷新列表()
|
||||||
|
if self.列表.count() >= 当前排:
|
||||||
|
self.列表.setCurrentRow(当前排)
|
||||||
|
|
||||||
|
def 上移(self):
|
||||||
|
当前排 = self.列表.currentRow()
|
||||||
|
if 当前排 > 0:
|
||||||
|
已选中的列表项 = self.列表.currentItem().text()
|
||||||
|
id = self.数据库连接.cursor().execute(
|
||||||
|
f'''select id from {self.表单名字} where {self.显示的列名} = :已选中的列表项 ''', {'已选中的列表项': 已选中的列表项}).fetchone()[0]
|
||||||
|
self.数据库连接.cursor().execute(f'''update {self.表单名字} set id = 100000 where id = :id - 1 ''', {'id': id})
|
||||||
|
self.数据库连接.cursor().execute(f'''update {self.表单名字} set id = id - 1 where {self.显示的列名} = :已选中的列表项''', {'已选中的列表项': 已选中的列表项})
|
||||||
|
self.数据库连接.cursor().execute(f'''update {self.表单名字} set id = :id where id=100000 ''', {'id': id})
|
||||||
|
self.数据库连接.commit()
|
||||||
|
self.列表.刷新列表()
|
||||||
|
if self.列表.筛选文字 == '':
|
||||||
|
self.列表.setCurrentRow(当前排 - 1)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 向下移动预设
|
||||||
|
def 下移(self):
|
||||||
|
当前排 = self.列表.currentRow()
|
||||||
|
总行数 = self.列表.count()
|
||||||
|
if 当前排 > -1 and 当前排 < 总行数 - 1:
|
||||||
|
已选中的列表项 = self.列表.currentItem().text()
|
||||||
|
id = self.数据库连接.cursor().execute(
|
||||||
|
f'''select id from {self.表单名字} where {self.显示的列名} = :已选中的列表项''', {'已选中的列表项': 已选中的列表项}).fetchone()[0]
|
||||||
|
self.数据库连接.cursor().execute(f'''update {self.表单名字} set id = 100000 where id = :id + 1 ''', {'id': id})
|
||||||
|
self.数据库连接.cursor().execute(f'''update {self.表单名字} set id = id + 1 where {self.显示的列名} = :已选中的列表项''', {'已选中的列表项': 已选中的列表项})
|
||||||
|
self.数据库连接.cursor().execute(f'''update {self.表单名字} set id = :id where id=100000 ''', {'id': id})
|
||||||
|
self.数据库连接.commit()
|
||||||
|
self.列表.刷新列表()
|
||||||
|
if self.列表.筛选文字 == '':
|
||||||
|
if 当前排 < 总行数:
|
||||||
|
self.列表.setCurrentRow(当前排 + 1)
|
||||||
|
else:
|
||||||
|
self.列表.setCurrentRow(当前排)
|
||||||
|
return
|
||||||
|
|
||||||
|
def 筛选(self):
|
||||||
|
self.列表.筛选文字 = self.筛选文字输入框.text()
|
||||||
|
self.列表.刷新列表()
|
||||||
|
|
57
src/moduels/gui/List_List.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
|
||||||
|
# 添加预设对话框
|
||||||
|
class List_List(QListWidget):
|
||||||
|
|
||||||
|
选中文字 = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, 数据库连接, 表单名字, 显示的列名):
|
||||||
|
super().__init__()
|
||||||
|
self.筛选文字 = ''
|
||||||
|
self.数据库连接 = 数据库连接
|
||||||
|
self.表单名字 = 表单名字
|
||||||
|
self.显示的列名 = 显示的列名
|
||||||
|
self.initElements() # 先初始化各个控件
|
||||||
|
self.initSlots() # 再将各个控件连接到信号槽
|
||||||
|
self.initLayouts() # 然后布局
|
||||||
|
self.initValues() # 再定义各个控件的值
|
||||||
|
|
||||||
|
def initElements(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initSlots(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initLayouts(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initValues(self):
|
||||||
|
self.刷新列表()
|
||||||
|
|
||||||
|
def currentChanged(self, current, previous):
|
||||||
|
if current.row() > -1:
|
||||||
|
self.选中文字.emit(current.data())
|
||||||
|
|
||||||
|
def 刷新列表(self):
|
||||||
|
cursor = self.数据库连接.cursor()
|
||||||
|
if self.筛选文字 == '':
|
||||||
|
显示项 = cursor.execute(
|
||||||
|
f'''select id, {self.显示的列名} from {self.表单名字} order by id''')
|
||||||
|
self.clear()
|
||||||
|
for i in 显示项:
|
||||||
|
self.addItem(i[1])
|
||||||
|
else:
|
||||||
|
显示项 = cursor.execute(
|
||||||
|
f'''select id, {self.显示的列名}, * from {self.表单名字} order by id''')
|
||||||
|
self.clear()
|
||||||
|
for i in 显示项:
|
||||||
|
for j in i[2:]:
|
||||||
|
if self.筛选文字 in str(j):
|
||||||
|
self.addItem(i[1])
|
||||||
|
break
|
||||||
|
|
134
src/moduels/gui/MainWindow.py
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
from moduels.component.Stream import Stream
|
||||||
|
from moduels.gui.Tab_CapsWriter import Tab_CapsWriter
|
||||||
|
# from moduels.gui.Tab_Stdout import Tab_Stdout
|
||||||
|
from moduels.gui.Tab_Config import Tab_Config
|
||||||
|
from moduels.gui.Tab_Help import Tab_Help
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
class MainWindow(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
常量.主窗口 = self
|
||||||
|
self.loadStyleSheet()
|
||||||
|
self.initElements() # 先初始化各个控件
|
||||||
|
self.initSlots() # 再将各个控件连接到信号槽
|
||||||
|
self.initLayouts() # 然后布局
|
||||||
|
self.initValues() # 再定义各个控件的值
|
||||||
|
|
||||||
|
|
||||||
|
# self.setWindowState(Qt.WindowMaximized)
|
||||||
|
# sys.stdout = Stream(newText=self.onUpdateText)
|
||||||
|
|
||||||
|
def initElements(self):
|
||||||
|
self.状态栏 = self.statusBar()
|
||||||
|
# 定义中心控件为多 tab 页面
|
||||||
|
self.tabs = QTabWidget()
|
||||||
|
|
||||||
|
# 定义多个不同功能的 tab
|
||||||
|
self.设置标签页 = Tab_Config() # 设置页要在前排加载,以确保一些设置加载成功
|
||||||
|
self.CapsWriter标签页 = Tab_CapsWriter() # 主要功能的 tab
|
||||||
|
# self.打印输出标签页 = Tab_Stdout()
|
||||||
|
self.帮助标签页 = Tab_Help()
|
||||||
|
|
||||||
|
self.标准输出流 = Stream()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def initLayouts(self):
|
||||||
|
|
||||||
|
self.tabs.addTab(self.CapsWriter标签页, 'CapsWriter')
|
||||||
|
self.tabs.addTab(self.设置标签页, '设置')
|
||||||
|
self.tabs.addTab(self.帮助标签页, '帮助')
|
||||||
|
self.setCentralWidget(self.tabs)
|
||||||
|
|
||||||
|
def initSlots(self):
|
||||||
|
self.CapsWriter标签页.状态栏消息.connect(lambda 消息, 时间: self.状态栏.showMessage(消息, 时间))
|
||||||
|
# self.打印输出标签页.状态栏消息.connect(lambda 消息, 时间: self.状态栏.showMessage(消息, 时间))
|
||||||
|
# self.设置标签页.状态栏消息.connect(lambda 消息, 时间: self.状态栏.showMessage(消息, 时间))
|
||||||
|
self.帮助标签页.状态栏消息.connect(lambda 消息, 时间: self.状态栏.showMessage(消息, 时间))
|
||||||
|
|
||||||
|
self.标准输出流.newText.connect(self.CapsWriter标签页.更新控制台输出)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initValues(self):
|
||||||
|
# self.adjustSize()
|
||||||
|
# self.setGeometry(QStyle(Qt.LeftToRight, Qt.AlignCenter, self.size(), QApplication.desktop().availableGeometry()))
|
||||||
|
常量.状态栏 = self.状态栏
|
||||||
|
sys.stdout = self.标准输出流
|
||||||
|
self.setWindowIcon(QIcon(常量.图标路径))
|
||||||
|
self.setWindowTitle('CapsWriter 语音输入工具')
|
||||||
|
self.setWindowFlag(Qt.WindowStaysOnTopHint) # 始终在前台
|
||||||
|
print("""\n软件介绍:
|
||||||
|
|
||||||
|
CapsWriter,顾名思义,就是按下大写锁定键来打字的工具。它的具体作用是:当你按下键盘上的大写锁定键后,软件开始语音识别,当你松开大写锁定键时,识别的结果就可以立马上屏。
|
||||||
|
|
||||||
|
目前软件内置了对阿里云一句话识别 API 的支持。如果你要使用,就需要先在阿里云上实名认证,申请语音识别 API,在设置页面添加一个语音识别引擎。
|
||||||
|
|
||||||
|
具体申请阿里云 API 的方法,可以参考我这个视频:https://www.bilibili.com/video/BV1qK4y1s7Fb/
|
||||||
|
|
||||||
|
添加上引擎后,在当前页面选择一个引擎,点击启用按钮,就可以进行语音识别了!嗯
|
||||||
|
|
||||||
|
启用后,在实际使用中,只要按下 CapsLock 键,软件就会立刻开始录音:
|
||||||
|
|
||||||
|
如果只是单击 CapsLock 后松开,录音数据会立刻被删除;
|
||||||
|
如果按下 CapsLock 键时长超过 0.3 秒,就会开始连网进行语音识别,松开 CapsLock 键时,语音识别结果会被立刻输入。
|
||||||
|
|
||||||
|
所以你只需要按下 CapsLock 键,无需等待,就可以开始说话,因为当你按下按下 CapsLock 键的时候,程序就开始录音了。说完后,松开,识别结果立马上屏。\r\n""")
|
||||||
|
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def 移动到屏幕中央(self):
|
||||||
|
rectangle = self.frameGeometry()
|
||||||
|
center = QApplication.desktop().availableGeometry().center()
|
||||||
|
rectangle.moveCenter(center)
|
||||||
|
self.move(rectangle.topLeft())
|
||||||
|
|
||||||
|
def 更新控制台输出(self, text):
|
||||||
|
self.打印输出标签页.print(text)
|
||||||
|
|
||||||
|
def loadStyleSheet(self):
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
with open(常量.样式文件, 'r', encoding='utf-8') as style:
|
||||||
|
self.setStyleSheet(style.read())
|
||||||
|
except:
|
||||||
|
with open(常量.样式文件, 'r', encoding='gbk') as style:
|
||||||
|
self.setStyleSheet(style.read())
|
||||||
|
except:
|
||||||
|
QMessageBox.warning(self, self.tr('主题载入错误'), self.tr('未能成功载入主题,请确保软件 misc 目录有 "style.css" 文件存在。'))
|
||||||
|
|
||||||
|
def keyPressEvent(self, event) -> None:
|
||||||
|
# 在按下 F5 的时候重载 style.css 主题
|
||||||
|
if (event.key() == Qt.Key_F5):
|
||||||
|
self.loadStyleSheet()
|
||||||
|
self.status.showMessage('已成功更新主题', 800)
|
||||||
|
|
||||||
|
def onUpdateText(self, text):
|
||||||
|
"""Write console output to text widget."""
|
||||||
|
|
||||||
|
cursor = self.consoleTab.consoleEditBox.textCursor()
|
||||||
|
cursor.movePosition(QTextCursor.End)
|
||||||
|
cursor.insertText(text)
|
||||||
|
self.consoleTab.consoleEditBox.setTextCursor(cursor)
|
||||||
|
self.consoleTab.consoleEditBox.ensureCursorVisible()
|
||||||
|
|
||||||
|
def 状态栏提示(self, 提示文字:str, 时间:int):
|
||||||
|
self.状态栏.showMessage(提示文字, 时间)
|
||||||
|
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
"""Shuts down application on close."""
|
||||||
|
# Return stdout to defaults.
|
||||||
|
if 常量.关闭时隐藏到托盘:
|
||||||
|
event.ignore()
|
||||||
|
self.hide()
|
||||||
|
else:
|
||||||
|
sys.stdout = sys.__stdout__
|
||||||
|
super().closeEvent(event)
|
68
src/moduels/gui/SystemTray.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
|
||||||
|
class SystemTray(QSystemTrayIcon):
|
||||||
|
def __init__(self, 主窗口):
|
||||||
|
super(SystemTray, self).__init__()
|
||||||
|
self.主窗口 = 主窗口
|
||||||
|
self.initElements() # 先初始化各个控件
|
||||||
|
self.initSlots() # 再将各个控件连接到信号槽
|
||||||
|
self.initLayouts() # 然后布局
|
||||||
|
self.initValues() # 再定义各个控件的值
|
||||||
|
|
||||||
|
# self.RestoreAction = QAction(u'还原 ', self, triggered=self.showWindow) # 添加一级菜单动作选项(还原主窗口)
|
||||||
|
# self.StyleAction = QAction(self.tr('更新主题'), self, triggered=mainWindow.loadStyleSheet) # 添加一级菜单动作选项(更新 QSS)
|
||||||
|
# self.tray_menu.addAction(self.RestoreAction) # 为菜单添加动作
|
||||||
|
# self.tray_menu.addAction(self.StyleAction)
|
||||||
|
|
||||||
|
def initElements(self):
|
||||||
|
self.托盘菜单 = QMenu(QApplication.desktop()) # 创建菜单
|
||||||
|
self.QuitAction = QAction(self.tr('退出'), self, triggered=self.退出) # 添加一级菜单动作选项(退出程序)
|
||||||
|
|
||||||
|
def initSlots(self):
|
||||||
|
self.activated.connect(self.trayEvent) # 设置托盘点击事件处理函数
|
||||||
|
|
||||||
|
def initLayouts(self):
|
||||||
|
self.托盘菜单.addAction(self.QuitAction)
|
||||||
|
|
||||||
|
def initValues(self):
|
||||||
|
self.setIcon(QIcon(常量.图标路径))
|
||||||
|
self.setParent(self.主窗口)
|
||||||
|
self.setContextMenu(self.托盘菜单) # 设置系统托盘菜单
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def 显示主窗口(self):
|
||||||
|
self.主窗口.showNormal()
|
||||||
|
self.主窗口.activateWindow()
|
||||||
|
self.主窗口.setWindowFlag(Qt.Window, Qt.WindowStaysOnTopHint) # 始终在前台
|
||||||
|
self.主窗口.show()
|
||||||
|
|
||||||
|
def 退出(self):
|
||||||
|
sys.stdout = sys.__stdout__
|
||||||
|
self.hide()
|
||||||
|
QApplication.quit()
|
||||||
|
|
||||||
|
def 切换聆听中的图标(self):
|
||||||
|
self.setIcon(QIcon(常量.聆听图标路径))
|
||||||
|
|
||||||
|
def 切换正常图标(self):
|
||||||
|
self.setIcon(QIcon(常量.图标路径))
|
||||||
|
|
||||||
|
def trayEvent(self, reason):
|
||||||
|
# 鼠标点击icon传递的信号会带有一个整形的值,1是表示单击右键,2是双击,3是单击左键,4是用鼠标中键点击
|
||||||
|
if reason == 2 or reason == 3:
|
||||||
|
if 常量.主窗口.isMinimized() or not 常量.主窗口.isVisible():
|
||||||
|
# 若是最小化或者最小化到托盘,则先正常显示窗口,再变为活动窗口(暂时显示在最前面)
|
||||||
|
self.显示主窗口()
|
||||||
|
else:
|
||||||
|
# 若不是最小化,则最小化
|
||||||
|
# self.window.showMinimized()
|
||||||
|
self.主窗口.hide()
|
||||||
|
pass
|
189
src/moduels/gui/Tab_CapsWriter.py
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
import os, re, subprocess, time
|
||||||
|
|
||||||
|
import pyaudio
|
||||||
|
|
||||||
|
# from moduels.component.QLEdit_FilePathQLineEdit import QLEdit_FilePathQLineEdit
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
from moduels.component.QEditBox_StdoutBox import QEditBox_StdoutBox
|
||||||
|
# from moduels.component.SpaceLine import QHLine, QVLine
|
||||||
|
from moduels.thread.Thread_AliEngine import Thread_AliEngine
|
||||||
|
# from moduels.thread.Thread_GenerateSkins import Thread_GenerateSkins
|
||||||
|
# from moduels.thread.Thread_ExtractAllSkin import Thread_ExtractAllSkin
|
||||||
|
|
||||||
|
# from moduels.function.applyTemplate import applyTemplate
|
||||||
|
# from moduels.function.openSkinSourcePath import openSkinSourcePath
|
||||||
|
#
|
||||||
|
# from moduels.gui.Dialog_AddSkin import Dialog_AddSkin
|
||||||
|
# from moduels.gui.Dialog_DecompressSkin import Dialog_DecompressSkin
|
||||||
|
# from moduels.gui.Dialog_RestoreSkin import Dialog_RestoreSkin
|
||||||
|
# from moduels.gui.Group_EditableList import Group_EditableList
|
||||||
|
# from moduels.gui.VBox_RBtnContainer import VBox_RBtnContainer
|
||||||
|
from moduels.gui.Combo_EngineList import Combo_EngineList
|
||||||
|
|
||||||
|
|
||||||
|
class Tab_CapsWriter(QWidget):
|
||||||
|
状态栏消息 = Signal(str, int)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.initElements() # 先初始化各个控件
|
||||||
|
self.initSlots() # 再将各个控件连接到信号槽
|
||||||
|
self.initLayouts() # 然后布局
|
||||||
|
self.initValues() # 再定义各个控件的值
|
||||||
|
|
||||||
|
|
||||||
|
def initElements(self):
|
||||||
|
self.页面总布局 = QVBoxLayout()
|
||||||
|
|
||||||
|
self.引擎选择Box = QGroupBox('引擎选择')
|
||||||
|
self.引擎选择下拉框 = Combo_EngineList()
|
||||||
|
self.引擎选择Box布局 = QVBoxLayout()
|
||||||
|
|
||||||
|
self.控制台输出Box = QGroupBox('提示消息')
|
||||||
|
self.控制台输出框 = QEditBox_StdoutBox()
|
||||||
|
self.控制台输出Box布局 = QVBoxLayout()
|
||||||
|
|
||||||
|
self.启停按钮Box = QGroupBox('开关')
|
||||||
|
self.启动按钮 = QPushButton('启用 CapsWriter')
|
||||||
|
self.停止按钮 = QPushButton('停止 CapsWriter')
|
||||||
|
self.启停按钮Box布局 = QHBoxLayout()
|
||||||
|
|
||||||
|
def initLayouts(self):
|
||||||
|
self.引擎选择Box布局.addWidget(self.引擎选择下拉框)
|
||||||
|
|
||||||
|
self.控制台输出Box布局.addWidget(self.控制台输出框)
|
||||||
|
|
||||||
|
self.启停按钮Box布局.addWidget(self.启动按钮)
|
||||||
|
self.启停按钮Box布局.addWidget(self.停止按钮)
|
||||||
|
|
||||||
|
self.引擎选择Box.setLayout(self.引擎选择Box布局)
|
||||||
|
self.控制台输出Box.setLayout(self.控制台输出Box布局)
|
||||||
|
self.启停按钮Box.setLayout(self.启停按钮Box布局)
|
||||||
|
|
||||||
|
self.页面总布局.addWidget(self.引擎选择Box)
|
||||||
|
self.页面总布局.addWidget(self.控制台输出Box)
|
||||||
|
self.页面总布局.addWidget(self.启停按钮Box)
|
||||||
|
|
||||||
|
self.setLayout(self.页面总布局)
|
||||||
|
|
||||||
|
def initSlots(self):
|
||||||
|
self.启动按钮.clicked.connect(self.启动引擎)
|
||||||
|
self.停止按钮.clicked.connect(self.停止引擎)
|
||||||
|
|
||||||
|
def 更新控制台输出(self, 文本):
|
||||||
|
self.控制台输出框.print(文本)
|
||||||
|
|
||||||
|
|
||||||
|
def initValues(self):
|
||||||
|
self.引擎线程 = None
|
||||||
|
# self.aliClient = ali_speech.NlsClient()
|
||||||
|
# self.aliClient.set_log_level('WARNING') # 设置 client 输出日志信息的级别:DEBUG、INFO、WARNING、ERROR
|
||||||
|
self.停止按钮.setDisabled(True)
|
||||||
|
|
||||||
|
def 启动引擎(self):
|
||||||
|
if self.引擎线程 != None: return
|
||||||
|
引擎名称 = self.引擎选择下拉框.currentText()
|
||||||
|
if 引擎名称 == '': return
|
||||||
|
self.启动按钮.setDisabled(True)
|
||||||
|
self.停止按钮.setEnabled(True)
|
||||||
|
result = 常量.数据库连接.execute(f'''select * from {常量.语音引擎表单名} where 引擎名称 = :引擎名称''',
|
||||||
|
{'引擎名称': 引擎名称}).fetchone()
|
||||||
|
if result == None: return
|
||||||
|
self.引擎线程 = Thread_AliEngine(引擎名称, self)
|
||||||
|
self.引擎线程.识别中的信号.connect(常量.托盘.切换聆听中的图标)
|
||||||
|
self.引擎线程.结束识别的信号.connect(常量.托盘.切换正常图标)
|
||||||
|
self.引擎线程.引擎出错信号.connect(self.停止引擎)
|
||||||
|
self.引擎线程.start()
|
||||||
|
|
||||||
|
|
||||||
|
def 停止引擎(self):
|
||||||
|
if self.引擎线程 != None:
|
||||||
|
self.引擎线程.停止引擎()
|
||||||
|
# print(self.引擎线程.isRunning())
|
||||||
|
self.引擎线程 = None
|
||||||
|
self.启动按钮.setEnabled(True)
|
||||||
|
self.停止按钮.setDisabled(True)
|
||||||
|
|
||||||
|
|
||||||
|
# self.压缩图片_按钮纵向布局.通过id勾选单选按钮(常量.输出选项['图片压缩'])
|
||||||
|
# self.输出格式_按钮纵向布局.通过id勾选单选按钮(常量.输出选项['输出格式'])
|
||||||
|
# self.其它_安装到手机选框.setChecked(常量.输出选项['adb发送至手机'])
|
||||||
|
# self.其它_清理注释选框.setChecked(常量.输出选项['清理注释'])
|
||||||
|
#
|
||||||
|
# def 无线adb(self):
|
||||||
|
# self.无线adb线程.start()
|
||||||
|
#
|
||||||
|
# def 提取皮肤(self):
|
||||||
|
# self.提取皮肤线程.start()
|
||||||
|
#
|
||||||
|
# def 解压皮肤(self):
|
||||||
|
# 解压皮肤对话框 = Dialog_DecompressSkin()
|
||||||
|
#
|
||||||
|
# def 发送皮肤(self):
|
||||||
|
# 获得的皮肤路径 = QFileDialog.getOpenFileName(self, caption='选择皮肤', dir=常量.皮肤输出路径, filter='皮肤文件 (*.bds)')[0]
|
||||||
|
# if 获得的皮肤路径 == '': return True
|
||||||
|
# 皮肤文件名 = os.path.basename(获得的皮肤路径)
|
||||||
|
# 手机皮肤路径 = '/sdcard/baidu/ime/skins/' + 皮肤文件名
|
||||||
|
# 发送皮肤命令 = f'''adb push "{获得的皮肤路径}" "{手机皮肤路径}"'''
|
||||||
|
# subprocess.run(发送皮肤命令, startupinfo=常量.subprocessStartUpInfo)
|
||||||
|
# 安装皮肤命令 = f'''adb shell am start -a android.intent.action.VIEW -c android.intent.category.DEFAULT -n com.baidu.input/com.baidu.input.ImeUpdateActivity -d '{手机皮肤路径}' '''
|
||||||
|
# subprocess.run(安装皮肤命令, startupinfo=常量.subprocessStartUpInfo)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def 备份选中皮肤(self):
|
||||||
|
# if self.皮肤列表Box.列表.currentRow() < 0: return
|
||||||
|
# 已选中的列表项 = self.皮肤列表Box.列表.currentItem().text()
|
||||||
|
# 输出文件名, 皮肤源文件目录 = 常量.数据库连接.cursor().execute(
|
||||||
|
# f'select outputFileName, sourceFilePath from {常量.数据库皮肤表单名} where skinName = :皮肤名字;',
|
||||||
|
# {'皮肤名字': 已选中的列表项}).fetchone()
|
||||||
|
# 备份时间 = time.localtime()
|
||||||
|
# 备份压缩文件名 = f'{输出文件名}_备份_{备份时间.tm_year}年{备份时间.tm_mon}月{备份时间.tm_mday}日{备份时间.tm_hour}时{备份时间.tm_min}分{备份时间.tm_sec}秒.bds'
|
||||||
|
# 备份文件完整路径 = os.path.join(常量.皮肤输出路径, '皮肤备份文件', 备份压缩文件名)
|
||||||
|
# 备份命令 = f'''winrar a -afzip -ibck -r -ep1 "{备份文件完整路径}" "{皮肤源文件目录}/*"'''
|
||||||
|
# if not os.path.exists(os.path.dirname(备份文件完整路径)): os.makedirs(os.path.dirname(备份文件完整路径))
|
||||||
|
# subprocess.run(备份命令, startupinfo=常量.subprocessStartUpInfo)
|
||||||
|
# os.startfile(os.path.dirname(备份文件完整路径))
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def 还原选中皮肤(self):
|
||||||
|
# if self.皮肤列表Box.列表.currentRow() < 0: return
|
||||||
|
# 已选中的列表项 = self.皮肤列表Box.列表.currentItem().text()
|
||||||
|
# 输出文件名, 皮肤源文件目录 = 常量.数据库连接.cursor().execute(
|
||||||
|
# f'select outputFileName, sourceFilePath from {常量.数据库皮肤表单名} where skinName = :皮肤名字;',
|
||||||
|
# {'皮肤名字': 已选中的列表项}).fetchone()
|
||||||
|
# 备份文件夹路径 = os.path.join(常量.皮肤输出路径, '皮肤备份文件')
|
||||||
|
# Dialog_RestoreSkin(备份文件夹路径, 输出文件名, 皮肤源文件目录)
|
||||||
|
#
|
||||||
|
# def 打开皮肤输出文件夹(self):
|
||||||
|
# if not os.path.exists(常量.皮肤输出路径): os.makedirs(常量.皮肤输出路径)
|
||||||
|
# os.startfile(常量.皮肤输出路径)
|
||||||
|
#
|
||||||
|
# def 打包选中皮肤(self):
|
||||||
|
# if self.皮肤列表Box.列表.currentRow() < 0: return True
|
||||||
|
# self.备份选中皮肤_按钮.setDisabled(True)
|
||||||
|
# self.还原选中皮肤_按钮.setDisabled(True)
|
||||||
|
# self.打包选中皮肤_按钮.setDisabled(True)
|
||||||
|
# self.打包所有皮肤_按钮.setDisabled(True)
|
||||||
|
# self.生成皮肤线程.是否要全部生成 = False
|
||||||
|
# self.生成皮肤线程.start()
|
||||||
|
#
|
||||||
|
# def 打包所有皮肤(self):
|
||||||
|
# self.备份选中皮肤_按钮.setDisabled(True)
|
||||||
|
# self.还原选中皮肤_按钮.setDisabled(True)
|
||||||
|
# self.打包选中皮肤_按钮.setDisabled(True)
|
||||||
|
# self.打包所有皮肤_按钮.setDisabled(True)
|
||||||
|
# self.生成皮肤线程.是否要全部生成 = True
|
||||||
|
# self.生成皮肤线程.start()
|
||||||
|
#
|
||||||
|
# def 生成皮肤线程完成(self):
|
||||||
|
# self.备份选中皮肤_按钮.setEnabled(True)
|
||||||
|
# self.还原选中皮肤_按钮.setEnabled(True)
|
||||||
|
# self.打包选中皮肤_按钮.setEnabled(True)
|
||||||
|
# self.打包所有皮肤_按钮.setEnabled(True)
|
||||||
|
# 常量.状态栏.showMessage('打包任务完成!', 5000)
|
||||||
|
|
134
src/moduels/gui/Tab_Config.py
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import webbrowser
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
from PySide2.QtSql import *
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
from moduels.gui.Group_EditableList import Group_EditableList
|
||||||
|
from moduels.gui.Dialog_AddEngine import Dialog_AddEngine
|
||||||
|
# from moduels.gui.Group_PathSetting import Group_PathSetting
|
||||||
|
|
||||||
|
|
||||||
|
class Tab_Config(QWidget):
|
||||||
|
状态栏消息 = Signal(str, int)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(Tab_Config, self).__init__(parent)
|
||||||
|
self.initElements() # 先初始化各个控件
|
||||||
|
self.initSlots() # 再将各个控件连接到信号槽
|
||||||
|
self.initLayouts() # 然后布局
|
||||||
|
self.initValues() # 再定义各个控件的值
|
||||||
|
|
||||||
|
def initElements(self):
|
||||||
|
self.程序设置Box = QGroupBox(self.tr('程序设置'))
|
||||||
|
self.开关_关闭窗口时隐藏到托盘 = QCheckBox(self.tr('点击关闭按钮时隐藏到托盘'))
|
||||||
|
self.程序设置横向布局 = QHBoxLayout()
|
||||||
|
|
||||||
|
self.引擎列表 = Group_EditableList('语音引擎', Dialog_AddEngine, 常量.数据库连接, 常量.语音引擎表单名, '引擎名称')
|
||||||
|
|
||||||
|
self.常用网址Box = QGroupBox('网页控制台')
|
||||||
|
self.常用网址Box布局 = QGridLayout()
|
||||||
|
self.智能语音交互控制台按钮 = QPushButton('智能语音交互')
|
||||||
|
self.RAM访问控制控制台按钮 = QPushButton('RAM访问控制')
|
||||||
|
|
||||||
|
self.页面布局 = QVBoxLayout()
|
||||||
|
|
||||||
|
|
||||||
|
def initSlots(self):
|
||||||
|
self.开关_关闭窗口时隐藏到托盘.stateChanged.connect(self.设置_隐藏到状态栏)
|
||||||
|
self.智能语音交互控制台按钮.clicked.connect(lambda: webbrowser.open(r'https://nls-portal.console.aliyun.com/'))
|
||||||
|
self.RAM访问控制控制台按钮.clicked.connect(lambda: webbrowser.open(r'https://ram.console.aliyun.com/'))
|
||||||
|
# self.路径设置Box.皮肤输出路径输入框.textChanged.connect(self.设置_皮肤输出路径)
|
||||||
|
# self.路径设置Box.音效文件路径输入框.textChanged.connect(self.设置_音效文件路径)
|
||||||
|
|
||||||
|
def initLayouts(self):
|
||||||
|
self.程序设置横向布局.addWidget(self.开关_关闭窗口时隐藏到托盘)
|
||||||
|
self.程序设置Box.setLayout(self.程序设置横向布局)
|
||||||
|
|
||||||
|
self.常用网址Box布局.addWidget(self.智能语音交互控制台按钮, 0, 0)
|
||||||
|
self.常用网址Box布局.addWidget(self.RAM访问控制控制台按钮, 0, 1)
|
||||||
|
self.常用网址Box.setLayout(self.常用网址Box布局)
|
||||||
|
|
||||||
|
self.页面布局.addWidget(self.程序设置Box)
|
||||||
|
self.页面布局.addWidget(self.引擎列表)
|
||||||
|
self.页面布局.addWidget(self.常用网址Box)
|
||||||
|
self.页面布局.addStretch(1)
|
||||||
|
|
||||||
|
self.setLayout(self.页面布局)
|
||||||
|
|
||||||
|
def initValues(self):
|
||||||
|
self.检查数据库()
|
||||||
|
|
||||||
|
|
||||||
|
def 检查数据库(self):
|
||||||
|
数据库连接 = 常量.数据库连接
|
||||||
|
self.检查数据库_关闭时最小化(数据库连接)
|
||||||
|
|
||||||
|
def 检查数据库_关闭时最小化(self, 数据库连接):
|
||||||
|
result = 数据库连接.cursor().execute(f'''select value from {常量.偏好设置表单名} where item = :item''',
|
||||||
|
{'item': 'hideToTrayWhenHitCloseButton'}).fetchone()
|
||||||
|
if result == None: # 如果关闭窗口最小化到状态栏这个选项还没有在数据库创建,那就创建一个
|
||||||
|
初始值 = 'False'
|
||||||
|
数据库连接.cursor().execute(f'''insert into {常量.偏好设置表单名} (item, value) values (:item, :value) ''',
|
||||||
|
{'item': 'hideToTrayWhenHitCloseButton',
|
||||||
|
'value':初始值})
|
||||||
|
数据库连接.commit()
|
||||||
|
self.开关_关闭窗口时隐藏到托盘.setChecked(初始值 == 'True')
|
||||||
|
else:
|
||||||
|
self.开关_关闭窗口时隐藏到托盘.setChecked(result[0] == 'True')
|
||||||
|
#
|
||||||
|
# def 检查数据库_皮肤输出路径(self, 数据库连接):
|
||||||
|
# result = 数据库连接.cursor().execute(f'''select value from {常量.偏好设置表单名} where item = :item''',
|
||||||
|
# {'item': 'skinOutputPath'}).fetchone()
|
||||||
|
# if result == None: # 如果关闭窗口最小化到状态栏这个选项还没有在数据库创建,那就创建一个
|
||||||
|
# 初始值 = 'output'
|
||||||
|
# 数据库连接.cursor().execute(f'''insert into {常量.偏好设置表单名} (item, value) values (:item, :value) ''',
|
||||||
|
# {'item': 'skinOutputPath',
|
||||||
|
# 'value': 初始值})
|
||||||
|
# 数据库连接.commit()
|
||||||
|
# self.路径设置Box.皮肤输出路径输入框.setText(初始值)
|
||||||
|
# else:
|
||||||
|
# self.路径设置Box.皮肤输出路径输入框.setText(result[0])
|
||||||
|
#
|
||||||
|
# def 检查数据库_音效文件路径(self, 数据库连接):
|
||||||
|
# result = 数据库连接.cursor().execute(f'''select value from {常量.偏好设置表单名} where item = :item''',
|
||||||
|
# {'item': 'soundFilePath'}).fetchone()
|
||||||
|
# if result == None: # 如果关闭窗口最小化到状态栏这个选项还没有在数据库创建,那就创建一个
|
||||||
|
# 初始值 = 'sound'
|
||||||
|
# 数据库连接.cursor().execute(f'''insert into {常量.偏好设置表单名} (item, value) values (:item, :value) ''',
|
||||||
|
# {'item': 'soundFilePath',
|
||||||
|
# 'value': 初始值})
|
||||||
|
# 数据库连接.commit()
|
||||||
|
# self.路径设置Box.音效文件路径输入框.setText(初始值)
|
||||||
|
# else:
|
||||||
|
# self.路径设置Box.音效文件路径输入框.setText(result[0])
|
||||||
|
|
||||||
|
def 设置_隐藏到状态栏(self):
|
||||||
|
数据库连接 = 常量.数据库连接
|
||||||
|
数据库连接.cursor().execute(f'''update {常量.偏好设置表单名} set value = :value where item = :item''',
|
||||||
|
{'item': 'hideToTrayWhenHitCloseButton',
|
||||||
|
'value': str(self.开关_关闭窗口时隐藏到托盘.isChecked())})
|
||||||
|
数据库连接.commit()
|
||||||
|
常量.关闭时隐藏到托盘 = self.开关_关闭窗口时隐藏到托盘.isChecked()
|
||||||
|
|
||||||
|
# def 设置_皮肤输出路径(self):
|
||||||
|
# 数据库连接 = 常量.数据库连接
|
||||||
|
# 数据库连接.cursor().execute(f'''update {常量.数据库偏好设置表单名} set value = :value where item = :item''',
|
||||||
|
# {'item': 'skinOutputPath',
|
||||||
|
# 'value': self.路径设置Box.皮肤输出路径输入框.text()})
|
||||||
|
# 数据库连接.commit()
|
||||||
|
# 常量.皮肤输出路径 = self.路径设置Box.皮肤输出路径输入框.text()
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def 设置_音效文件路径(self):
|
||||||
|
# 数据库连接 = 常量.数据库连接
|
||||||
|
# 数据库连接.cursor().execute(f'''update {常量.数据库偏好设置表单名} set value = :value where item = :item''',
|
||||||
|
# {'item': 'soundFilePath',
|
||||||
|
# 'value': self.路径设置Box.音效文件路径输入框.text()})
|
||||||
|
# 数据库连接.commit()
|
||||||
|
# 常量.音效文件路径 = self.路径设置Box.音效文件路径输入框.text()
|
||||||
|
|
||||||
|
def 隐藏到状态栏开关被点击(self):
|
||||||
|
cursor = 常量.数据库连接.cursor()
|
||||||
|
cursor.execute(f'''update {常量.数据库偏好设置表单名} set value='{str(self.开关_关闭窗口时隐藏到托盘.isChecked())}' where item = '{'hideToTrayWhenHitCloseButton'}';''')
|
||||||
|
常量.数据库连接.commit()
|
69
src/moduels/gui/Tab_Help.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtCore import Signal
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
from moduels.component.SponsorDialog import SponsorDialog
|
||||||
|
|
||||||
|
import os, webbrowser
|
||||||
|
|
||||||
|
|
||||||
|
class Tab_Help(QWidget):
|
||||||
|
状态栏消息 = Signal(str, int)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.initElement() # 先初始化各个控件
|
||||||
|
self.initSlots() # 再将各个控件连接到信号槽
|
||||||
|
self.initLayout() # 然后布局
|
||||||
|
self.initValue() # 再定义各个控件的值
|
||||||
|
|
||||||
|
def initElement(self):
|
||||||
|
self.打开帮助按钮 = QPushButton(self.tr('打开帮助文档'))
|
||||||
|
self.ffmpegMannualNoteButton = QPushButton(self.tr('查看作者的 FFmpeg 笔记'))
|
||||||
|
self.openVideoHelpButtone = QPushButton(self.tr('查看视频教程'))
|
||||||
|
self.openGiteePage = QPushButton(self.tr(f'当前版本是 v{常量.软件版本},到 Gitee 检查新版本'))
|
||||||
|
self.openGithubPage = QPushButton(self.tr(f'当前版本是 v{常量.软件版本},到 Github 检查新版本'))
|
||||||
|
self.linkToDiscussPage = QPushButton(self.tr('加入 QQ 群'))
|
||||||
|
self.tipButton = QPushButton(self.tr('打赏作者'))
|
||||||
|
|
||||||
|
self.masterLayout = QVBoxLayout()
|
||||||
|
|
||||||
|
def initSlots(self):
|
||||||
|
self.打开帮助按钮.clicked.connect(self.openHelpDocument)
|
||||||
|
self.ffmpegMannualNoteButton.clicked.connect(lambda: webbrowser.open(self.tr(r'https://hacpai.com/article/1595480295489')))
|
||||||
|
self.openVideoHelpButtone.clicked.connect(lambda: webbrowser.open(self.tr(r'https://www.bilibili.com/video/BV12A411p73r/')))
|
||||||
|
self.openGiteePage.clicked.connect(lambda: webbrowser.open(self.tr(r'https://gitee.com/haujet/CapsWriter/releases')))
|
||||||
|
self.openGithubPage.clicked.connect(lambda: webbrowser.open(self.tr(r'https://github.com/HaujetZhao/CapsWriter/releases')))
|
||||||
|
self.linkToDiscussPage.clicked.connect(lambda: webbrowser.open(
|
||||||
|
self.tr(r'https://qm.qq.com/cgi-bin/qm/qr?k=DgiFh5cclAElnELH4mOxqWUBxReyEVpm&jump_from=webapi')))
|
||||||
|
self.tipButton.clicked.connect(lambda: SponsorDialog(self))
|
||||||
|
|
||||||
|
def initLayout(self):
|
||||||
|
self.setLayout(self.masterLayout)
|
||||||
|
# self.masterLayout.addWidget(self.打开帮助按钮)
|
||||||
|
# self.masterLayout.addWidget(self.ffmpegMannualNoteButton)
|
||||||
|
self.masterLayout.addWidget(self.openVideoHelpButtone)
|
||||||
|
self.masterLayout.addWidget(self.openGiteePage)
|
||||||
|
self.masterLayout.addWidget(self.openGithubPage)
|
||||||
|
self.masterLayout.addWidget(self.linkToDiscussPage)
|
||||||
|
self.masterLayout.addWidget(self.tipButton)
|
||||||
|
|
||||||
|
def initValue(self):
|
||||||
|
self.打开帮助按钮.setMaximumHeight(100)
|
||||||
|
self.ffmpegMannualNoteButton.setMaximumHeight(100)
|
||||||
|
self.openVideoHelpButtone.setMaximumHeight(100)
|
||||||
|
self.openGiteePage.setMaximumHeight(100)
|
||||||
|
self.openGithubPage.setMaximumHeight(100)
|
||||||
|
self.linkToDiscussPage.setMaximumHeight(100)
|
||||||
|
self.tipButton.setMaximumHeight(100)
|
||||||
|
|
||||||
|
def openHelpDocument(self):
|
||||||
|
try:
|
||||||
|
if 常量.系统平台 == 'Darwin':
|
||||||
|
import shlex
|
||||||
|
os.system("open " + shlex.quote(self.tr("./misc/Docs/README_zh.html")))
|
||||||
|
elif 常量.系统平台 == 'Windows':
|
||||||
|
os.startfile(os.path.realpath(self.tr('./misc/Docs/README_zh.html')))
|
||||||
|
except:
|
||||||
|
print('未能打开帮助文档')
|
262
src/moduels/thread/Thread_AliEngine.py
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pyaudio
|
||||||
|
import threading
|
||||||
|
import keyboard
|
||||||
|
import sqlite3
|
||||||
|
import time
|
||||||
|
|
||||||
|
import ali_speech
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import *
|
||||||
|
from PySide2.QtGui import *
|
||||||
|
from PySide2.QtCore import *
|
||||||
|
|
||||||
|
from moduels.component.NormalValue import 常量
|
||||||
|
from moduels.function.getAlibabaRecognizer import getAlibabaRecognizer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Thread_AliEngine(QThread):
|
||||||
|
状态栏消息 = Signal(str, int)
|
||||||
|
引擎出错信号 = Signal()
|
||||||
|
|
||||||
|
CHUNK = 1024 # 数据包或者数据片段
|
||||||
|
FORMAT = pyaudio.paInt16 # pyaudio.paInt16表示我们使用量化位数 16位来进行录音
|
||||||
|
CHANNELS = 1 # 声道,1为单声道,2为双声道
|
||||||
|
RATE = 16000 # 采样率,每秒钟16000次
|
||||||
|
总共写入音频片段数 = 0
|
||||||
|
|
||||||
|
count = 1 # 计数
|
||||||
|
待命中 = True # 是否准备开始录音
|
||||||
|
识别中 = False # 控制录音是否停止
|
||||||
|
|
||||||
|
识别中的信号 = Signal()
|
||||||
|
结束识别的信号 = Signal()
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, 引擎名称, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.正在运行 = 0
|
||||||
|
self.引擎名称 = 引擎名称
|
||||||
|
self.得到引擎信息()
|
||||||
|
self.tokenId = 0
|
||||||
|
self.tokenExpireTime = 0
|
||||||
|
self.构建按键发送器()
|
||||||
|
QApplication.instance().aboutToQuit.connect(self.要退出了)
|
||||||
|
|
||||||
|
def 要退出了(self):
|
||||||
|
self.terminate()
|
||||||
|
|
||||||
|
def 构建按键发送器(self):
|
||||||
|
if 常量.系统平台 == 'Windows':
|
||||||
|
import win32com.client as comclt
|
||||||
|
self.按键发送器 = comclt.Dispatch("WScript.Shell")
|
||||||
|
|
||||||
|
def 发送大写锁定键(self):
|
||||||
|
if 常量.系统平台 == 'Windows':
|
||||||
|
self.按键发送器.SendKeys("{CAPSLOCK}")
|
||||||
|
else:
|
||||||
|
self.取消监听大写锁定键()
|
||||||
|
keyboard.press_and_release('caps lock')
|
||||||
|
self.开始监听大写锁定键()
|
||||||
|
|
||||||
|
def 开始监听大写锁定键(self):
|
||||||
|
keyboard.hook_key('caps lock', self.大写锁定键被触发)
|
||||||
|
|
||||||
|
def 取消监听大写锁定键(self):
|
||||||
|
try:
|
||||||
|
keyboard.unhook('caps lock')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def 停止引擎(self):
|
||||||
|
self.取消监听大写锁定键()
|
||||||
|
keyboard.unhook_all()
|
||||||
|
self.setTerminationEnabled(True)
|
||||||
|
self.terminate()
|
||||||
|
print('引擎已停止\n\n')
|
||||||
|
self.正在运行 = 0
|
||||||
|
|
||||||
|
def 得到引擎信息(self):
|
||||||
|
数据库连接 = sqlite3.connect(常量.数据库路径)
|
||||||
|
self.appKey, self.accessKeyId, self.accessKeySecret = 数据库连接.execute(f'''select AppKey,
|
||||||
|
AccessKeyId,
|
||||||
|
AccessKeySecret
|
||||||
|
from {常量.语音引擎表单名}
|
||||||
|
where 引擎名称 = :引擎名称''',
|
||||||
|
{'引擎名称': self.引擎名称}).fetchone()
|
||||||
|
数据库连接.close()
|
||||||
|
|
||||||
|
def 大写锁定键被触发(self, event):
|
||||||
|
if event.event_type == "down":
|
||||||
|
if self.识别中:
|
||||||
|
return
|
||||||
|
self.识别中 = True
|
||||||
|
try:
|
||||||
|
self.data = []
|
||||||
|
threading.Thread(target=self.录音线程, args=[self.p]).start() # 开始录音
|
||||||
|
threading.Thread(target=self.识别线程).start() # 开始识别
|
||||||
|
|
||||||
|
except:
|
||||||
|
print('process 启动失败')
|
||||||
|
elif event.event_type == "up":
|
||||||
|
# self.访问录音数据的线程锁.acquire()
|
||||||
|
self.识别中 = False
|
||||||
|
# self.访问录音数据的线程锁.release()
|
||||||
|
else:
|
||||||
|
# print(event.event_type)
|
||||||
|
pass
|
||||||
|
def 为下一次输入准备识别器(self):
|
||||||
|
self.识别器 = getAlibabaRecognizer(self.client,
|
||||||
|
self.appKey,
|
||||||
|
self.accessKeyId,
|
||||||
|
self.accessKeySecret,
|
||||||
|
self.tokenId,
|
||||||
|
self.tokenExpireTime,
|
||||||
|
线程=self)
|
||||||
|
if self.识别器 == False:
|
||||||
|
print('获取云端识别器出错\n')
|
||||||
|
self.引擎出错信号.emit()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def 录音线程(self, p):
|
||||||
|
self.录音(p)
|
||||||
|
|
||||||
|
def 识别线程(self):
|
||||||
|
self.识别中的信号.emit()
|
||||||
|
if not self.识别():
|
||||||
|
self.count += 1
|
||||||
|
self.总共写入音频片段数 = 0
|
||||||
|
self.结束识别的信号.emit()
|
||||||
|
|
||||||
|
def 录音(self, p):
|
||||||
|
# print('准备录制')
|
||||||
|
stream = p.open(channels=self.CHANNELS,
|
||||||
|
format=self.FORMAT,
|
||||||
|
rate=self.RATE,
|
||||||
|
input=True,
|
||||||
|
frames_per_buffer=self.CHUNK)
|
||||||
|
|
||||||
|
# print('录制器准备完毕')
|
||||||
|
# 录音写入序号 = 1
|
||||||
|
for i in range(5):
|
||||||
|
# self.访问录音数据的线程锁.acquire()
|
||||||
|
if not self.识别中:
|
||||||
|
self.data = []
|
||||||
|
# self.访问录音数据的线程锁.release()
|
||||||
|
return
|
||||||
|
# print(f'录音{录音写入序号},开始写入,时间 {time.time()}')
|
||||||
|
self.data.append(stream.read(self.CHUNK))
|
||||||
|
# print(f'录音{录音写入序号},写入结束,时间 {time.time()}')
|
||||||
|
# 录音写入序号 += 1
|
||||||
|
# self.访问录音数据的线程锁.release()
|
||||||
|
# 在这里录下5个小片段,大约录制了0.32秒,如果这个时候松开了大写锁定键,就不打开连接。如果还继续按着,那就开始识别。
|
||||||
|
|
||||||
|
while self.识别中:
|
||||||
|
# self.访问录音数据的线程锁.acquire()
|
||||||
|
# print(f'录音{录音写入序号},开始写入,时间 {time.time()}')
|
||||||
|
self.data.append(stream.read(self.CHUNK))
|
||||||
|
# print(f'录音{录音写入序号},写入结束,时间 {time.time()}\n')
|
||||||
|
# 录音写入序号 += 1
|
||||||
|
# self.访问录音数据的线程锁.release()
|
||||||
|
# self.访问录音数据的线程锁.acquire()
|
||||||
|
time.sleep(0.0)
|
||||||
|
self.总共写入音频片段数 = len(self.data)
|
||||||
|
# self.访问录音数据的线程锁.release()
|
||||||
|
self.发送大写锁定键() # 再按下大写锁定键,还原大写锁定
|
||||||
|
stream.stop_stream()# print('停止录制流')
|
||||||
|
stream.close()
|
||||||
|
|
||||||
|
|
||||||
|
# 这边开始上传识别
|
||||||
|
def 识别(self):
|
||||||
|
# print('识别器开始等待')
|
||||||
|
for i in range(5):
|
||||||
|
time.sleep(0.06)
|
||||||
|
if not self.识别中:
|
||||||
|
return # 如果这个时候大写锁定键松开了 那就返回
|
||||||
|
# print('识别器等待完闭')
|
||||||
|
# try:
|
||||||
|
print(self.tr('\n{}:在识别了,说完后请松开 CapsLock 键...').format(self.count))
|
||||||
|
识别器 = self.识别器
|
||||||
|
self.识别器 = None
|
||||||
|
threading.Thread(target=self.为下一次输入准备识别器).start() # 用新线程为下一次识别准备识别器
|
||||||
|
# print('准备新的识别器')
|
||||||
|
try:
|
||||||
|
ret = 识别器.start() # 识别器开始识别
|
||||||
|
except:
|
||||||
|
print('识别器开启失败')
|
||||||
|
return False
|
||||||
|
if ret < 0:
|
||||||
|
return False # 如果开始识别出错了,那就返回
|
||||||
|
已发送音频片段数 = 0 # 对音频片段记数
|
||||||
|
# j = 1
|
||||||
|
当前进程测得数据片段总数 = len(self.data)
|
||||||
|
while self.识别中 or 已发送音频片段数 < 当前进程测得数据片段总数 or 已发送音频片段数 < self.总共写入音频片段数:
|
||||||
|
# self.访问录音数据的线程锁.acquire()
|
||||||
|
当前进程测得数据片段总数 = len(self.data)
|
||||||
|
# self.访问录音数据的线程锁.release()
|
||||||
|
# print(f' 已发送音频片段数: {已发送音频片段数}, 当前进程测得数据片段总数: {当前进程测得数据片段总数}')
|
||||||
|
if 已发送音频片段数 > 当前进程测得数据片段总数:
|
||||||
|
return True
|
||||||
|
elif 已发送音频片段数 == 当前进程测得数据片段总数:
|
||||||
|
time.sleep(0.05)
|
||||||
|
if 已发送音频片段数 < 当前进程测得数据片段总数:
|
||||||
|
# self.访问录音数据的线程锁.acquire()
|
||||||
|
要发送的音频数据 = self.data[已发送音频片段数]
|
||||||
|
# self.访问录音数据的线程锁.release()
|
||||||
|
try:
|
||||||
|
# print(f' 发送器{j},开始发送,时间 {time.time()}')
|
||||||
|
ret = 识别器.send(要发送的音频数据) # 发送音频数据
|
||||||
|
# print(f' 发送器{j},发送结束,时间 {time.time()}\n')
|
||||||
|
# j += 1
|
||||||
|
except:
|
||||||
|
print('识别器发送失败')
|
||||||
|
return False
|
||||||
|
已发送音频片段数 += 1
|
||||||
|
# print(self.tr('\n{}:按住 CapsLock 键后开始说话...').format(self.count + 1))
|
||||||
|
self.总共写入音频片段数 = 0
|
||||||
|
self.结束识别的信号.emit()
|
||||||
|
self.count += 1
|
||||||
|
识别器.stop()
|
||||||
|
识别器.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if self.正在运行 == 1: return False
|
||||||
|
self.正在运行 = 1
|
||||||
|
|
||||||
|
self.client = ali_speech.NlsClient()
|
||||||
|
self.client.set_log_level('WARNING') # 设置 client 输出日志信息的级别:DEBUG、INFO、WARNING、ERROR
|
||||||
|
|
||||||
|
self.tokenId = 0
|
||||||
|
self.tokenExpireTime = 0
|
||||||
|
# try:
|
||||||
|
self.识别器 = getAlibabaRecognizer(self.client,
|
||||||
|
self.appKey,
|
||||||
|
self.accessKeyId,
|
||||||
|
self.accessKeySecret,
|
||||||
|
self.tokenId,
|
||||||
|
self.tokenExpireTime,
|
||||||
|
线程=self)
|
||||||
|
if self.识别器 == False:
|
||||||
|
print('获取云端识别器出错\n')
|
||||||
|
self.引擎出错信号.emit()
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.p = pyaudio.PyAudio() # 在 QThread 中引入 PyAudio 会使得 PySide2 图形界面阻塞
|
||||||
|
|
||||||
|
self.开始监听大写锁定键()
|
||||||
|
|
||||||
|
print("""引擎初始化完成\n""")
|
||||||
|
print('按住 CapsLock 键后开始说话...'.format(self.count))
|
||||||
|
keyboard.wait()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
|||||||
[Token]
|
|
||||||
id = 00000000000000000000
|
|
||||||
expiretime = 0000000000
|
|
||||||
accesskeyid = 00000000000000
|
|
||||||
accesskeysecret = 000000000000000000000000000
|
|
||||||
appkey = 000000000000000000000
|
|
||||||
|
|
BIN
安装指南/alibabacloud-nls-python-sdk/dist/alibabacloud-nls-java-sdk-2.0.0.tar.gz
vendored
Normal file
@ -2,4 +2,4 @@ setuptools
|
|||||||
pyaudio
|
pyaudio
|
||||||
keyboard
|
keyboard
|
||||||
aliyunsdkcore
|
aliyunsdkcore
|
||||||
configparser
|
PySide2
|
13
安装指南/安装指南.md
@ -33,16 +33,3 @@ pip install PyAudio‑0.2.11‑cp37‑cp37m‑win_amd64.whl
|
|||||||
- 场景:中文普通话 或 其它语言(想识别哪个语言就用哪个)
|
- 场景:中文普通话 或 其它语言(想识别哪个语言就用哪个)
|
||||||
|
|
||||||
发布上线,再记下这个项目的 **appkey**
|
发布上线,再记下这个项目的 **appkey**
|
||||||
|
|
||||||
### 填写 API
|
|
||||||
|
|
||||||
用文本编辑器打开项目主目录的 `run.py` 编辑,可以看到:
|
|
||||||
|
|
||||||
```python
|
|
||||||
""" 在这里填写你的 API 设置 """
|
|
||||||
accessID = "xxxxxxxxxxxxxxxxxxxxxxxx"
|
|
||||||
accessKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
||||||
appkey = 'xxxxxxxxxxxxxxxx'
|
|
||||||
```
|
|
||||||
|
|
||||||
将你在阿里云的 **accessID**、**accessKey**、**appkey** 分别填入,保存。
|
|
17
打包/Pyinstaller 编译和打包 Win64.bat
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
rmdir /s /q .\dist\CapsWriter
|
||||||
|
|
||||||
|
pyinstaller --hidden-import sqlite3 --noconfirm -i "../src/misc/icon.ico" "../src/__init__.pyw"
|
||||||
|
|
||||||
|
::pyinstaller --hidden-import sqlite3 --hidden-import PySide2.QtSql --noconfirm -i "../src/misc/icon.ico" "../src/__init__.py"
|
||||||
|
|
||||||
|
echo d | xcopy /y /s .\dist\rely .\dist\__init__
|
||||||
|
|
||||||
|
ren .\dist\__init__\__init__.exe "_CapsWriter语音输入工具.exe"
|
||||||
|
|
||||||
|
move .\dist\__init__ .\dist\CapsWriter
|
||||||
|
|
||||||
|
del /F /Q CapsWriter_Win64.7z
|
||||||
|
|
||||||
|
7z a -t7z CapsWriter_Win64.7z .\dist\CapsWriter -mx=9 -ms=200m -mf -mhc -mhcf -mmt -r
|
||||||
|
|
||||||
|
pause
|