升级到了图形界面工具

改进了录音开始速度,按住大写锁定键说话就行了,不用再等待。
版本号更新到 2.0
This commit is contained in:
Haujet 2020-12-25 15:09:59 +08:00
parent f4ff3bb1a9
commit f4b90c2979
42 changed files with 1812 additions and 289 deletions

24
.gitignore vendored
View File

@ -1,6 +1,20 @@
build*
dist*
__pycache__*
run.spec
alispeech.log
*.spec
__pycache__
*.log
*.spec
*.mp4
*.mkv
*.wav
.DS_Store
.idea
*info
*database.db
*test.py
*.7z
*/dist/*
*/build/*
*.db
*.afphoto
icon*.png
视频封面.png

15
Pipfile Normal file
View 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
View 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": {}
}

View File

@ -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
因为使用了阿里云的 api所以需要用户自己到阿里云申请再填到 `token.ini` 中才能正常使用。
这是一款电脑端语音输入工具。顾名思义Caps Writer 就是按下大写锁定键来打字的工具。它的具体作用是:当你长按键盘上的大写锁定键后,软件会开始语音识别,当你松开大写锁定键时,识别的结果就可以立马上屏。
对于聊天时候进行快捷输入、写代码时快速加入中文注释非常的方便。
目前软件内置了对阿里云一句话识别 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……`,有以下槽点:
* 这个快捷键会和许多软件的快捷键冲突,且不好记
* 打字时,按这样三个快捷键,手指很别扭,不爽
* 它的逻辑是按下快捷键后,启用语音输入,你一停顿一下,要说下一名,语音输入却结束了,不能让用户决定什么时候结束语音输入。
* 讯飞语音输入法的快捷键是 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
- pyaudio
- configparser
- aliyunsdkcore
- alibabacloud-nls-python-sdk
Mac 和 Linux 用户,你们也可以使用,只是我没有 Mac 和 Linux 的电脑,无法打包。需要你们下载源代码、安装依赖库,再打包或者直接运行。
### 🛠 源代码使用
小白下载的 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 安装
- alibabacloud-nls-python-sdk 不是通过 python 安装,而是通过 [阿里云官方文档的方法](https://help.aliyun.com/document_detail/120693.html) 进行安装。
- pyaudio 在 windows 上不是太好安装,可以先到 [这个链接](https://www.lfd.uci.edu/~gohlke/pythonlibs) 下载 pyaudio 对应版本的 whl 文件,再用 pip 安装Mac 和 Linux 上需要先安装 port audio才能安装上 pyaudio
- 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 了,但是在中国大陆不太好使用。在海外的话会非常好用。
- 使用 Baidu AI 语音识别 api每个账户有 200 万次的免费额度。
- 使用 Tencent AI 语音识别 api每个账户有 5000 次的免费额度。
- 使用讯飞的语音识别 api每个账户有 1 年的免费使用时间。
欢迎有兴趣的贡献者对项目进行翻译(国际化),添加 Google、Bing 的 api让海外用户也可以使用这个便捷的语音输入工具
如果有软件方面的反馈可以提交 issues或者加入 QQ 群:[1146626791](https://qm.qq.com/cgi-bin/qm/qr?k=DgiFh5cclAElnELH4mOxqWUBxReyEVpm&jump_from=webapi)

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
assets/sponsor.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

View File

@ -2,4 +2,4 @@ setuptools
pyaudio
keyboard
aliyunsdkcore
configparser
PySide2

217
run.py
View File

@ -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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
src/misc/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
src/misc/icon_listning.icns Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
src/misc/icon_listning.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -0,0 +1,2 @@
magick convert "%1" -resize 128x128 "%~dp1%~n1.ico"
magick convert "%1" -resize 128x128 "%~dp1%~n1.icns"

View File

@ -0,0 +1,5 @@
setuptools
pyaudio
keyboard
aliyunsdkcore
PySide2

BIN
src/misc/sponsor.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

42
src/misc/style.css Normal file
View 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 *
}*/

View 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

View 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()

View 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('文本框更新文本失败')

View 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)

View 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

View 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() # 最后要提交更改

View 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)

View 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

View 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.列表项)

View 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('引擎列表刷新失败')

View 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.列表.刷新列表()

View 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

View 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)

View 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

View 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)

View 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()

View 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('未能打开帮助文档')

View 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()

View File

@ -1,7 +0,0 @@
[Token]
id = 00000000000000000000
expiretime = 0000000000
accesskeyid = 00000000000000
accesskeysecret = 000000000000000000000000000
appkey = 000000000000000000000

View File

@ -2,4 +2,4 @@ setuptools
pyaudio
keyboard
aliyunsdkcore
configparser
PySide2

View File

@ -33,16 +33,3 @@ pip install PyAudio0.2.11cp37cp37mwin_amd64.whl
- 场景:中文普通话 或 其它语言(想识别哪个语言就用哪个)
发布上线,再记下这个项目的 **appkey**
### 填写 API
用文本编辑器打开项目主目录的 `run.py` 编辑,可以看到:
```python
""" 在这里填写你的 API 设置 """
accessID = "xxxxxxxxxxxxxxxxxxxxxxxx"
accessKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
appkey = 'xxxxxxxxxxxxxxxx'
```
将你在阿里云的 **accessID**、**accessKey**、**appkey** 分别填入,保存。

View 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