diff --git a/.gitignore b/.gitignore
index f1d6420..579ffe8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,20 @@
-build*
-dist*
-__pycache__*
-run.spec
-alispeech.log
-*.wav
\ No newline at end of file
+*.spec
+__pycache__
+*.log
+*.spec
+*.mp4
+*.mkv
+*.wav
+.DS_Store
+.idea
+*info
+
+*database.db
+*test.py
+*.7z
+*/dist/*
+*/build/*
+*.db
+*.afphoto
+icon*.png
+视频封面.png
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 0000000..5296f66
--- /dev/null
+++ b/Pipfile
@@ -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"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 0000000..847fb09
--- /dev/null
+++ b/Pipfile.lock
@@ -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": {}
+}
diff --git a/README.md b/README.md
index 82006da..a4036c8 100644
--- a/README.md
+++ b/README.md
@@ -1,81 +1,94 @@
-# Caps Writer
+[Gitee](https://gitee.com/haujet/CapsWriter) | [Github](https://github.com/HaujetZhao/CapsWriter)
-### 💡 简介
+#
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 秒,就肯定能识别上。说完后,松开,识别结果立马上屏。
+
+
+
+## ⭐技巧
+
+在设置界面,将 `点击关闭按钮时隐藏到托盘` 选项勾选,就可以将软件隐藏到托盘栏运行:
+
+
+
### 📝 背景
-我真是气抖冷,为什么直到 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 进行语音识别,松开按键后,会将识别结果自动输入。
+万水千山总是情,一块几块都是情。本软件完全开源,用爱发电,如果你愿意,可以以打赏的方式支持我一下:
-### 后话
+
-因为作者就是本着凑合能用就可以了的心态做这个工具的,所以图形界面什么的也没做,整个工具单纯就一个脚本,功能也就一个,按住大写锁定键开始语音识别,松开后输入结果。目前作者本人已经很满意。
-欢迎有想法有能力的人将这个工具加以改进,比如加入讯飞、腾讯、百度的语音识别api,长按0.3秒后开始识别时加一个提示等等等等。
-目前已知改进的方向:
+## 😀 交流
-- 使用 VoiceRecognition 中的 google_recognize 进行识别,使用的是谷歌的免费语音识别 api,优势是不用用户个人申请 api 了,但是在中国大陆不太好使用。在海外的话会非常好用。
-- 使用 Baidu AI 语音识别 api,每个账户有 200 万次的免费额度。
-- 使用 Tencent AI 语音识别 api,每个账户有 5000 次的免费额度。
-- 使用讯飞的语音识别 api,每个账户有 1 年的免费使用时间。
-
-欢迎有兴趣的贡献者对项目进行翻译(国际化),添加 Google、Bing 的 api,让海外用户也可以使用这个便捷的语音输入工具!
\ No newline at end of file
+如果有软件方面的反馈可以提交 issues,或者加入 QQ 群:[1146626791](https://qm.qq.com/cgi-bin/qm/qr?k=DgiFh5cclAElnELH4mOxqWUBxReyEVpm&jump_from=webapi)
\ No newline at end of file
diff --git a/assets/image-20201225053752740.png b/assets/image-20201225053752740.png
new file mode 100644
index 0000000..fdc29e7
Binary files /dev/null and b/assets/image-20201225053752740.png differ
diff --git a/assets/image-20201225140607971.png b/assets/image-20201225140607971.png
new file mode 100644
index 0000000..a49b831
Binary files /dev/null and b/assets/image-20201225140607971.png differ
diff --git a/assets/sponsor.jpg b/assets/sponsor.jpg
new file mode 100644
index 0000000..c34b59b
Binary files /dev/null and b/assets/sponsor.jpg differ
diff --git a/requirements.txt b/requirements.txt
index 71cf397..6a96bb8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,4 +2,4 @@ setuptools
pyaudio
keyboard
aliyunsdkcore
-configparser
\ No newline at end of file
+PySide2
\ No newline at end of file
diff --git a/run.py b/run.py
deleted file mode 100644
index 8cb3a00..0000000
--- a/run.py
+++ /dev/null
@@ -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()
-
-
-
-
-
\ No newline at end of file
diff --git a/src/__init__.pyw b/src/__init__.pyw
new file mode 100644
index 0000000..805b8ab
--- /dev/null
+++ b/src/__init__.pyw
@@ -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()
diff --git a/src/misc/icon.icns b/src/misc/icon.icns
new file mode 100644
index 0000000..05decdf
Binary files /dev/null and b/src/misc/icon.icns differ
diff --git a/src/misc/icon.ico b/src/misc/icon.ico
new file mode 100644
index 0000000..d494cdd
Binary files /dev/null and b/src/misc/icon.ico differ
diff --git a/src/misc/icon_listning.icns b/src/misc/icon_listning.icns
new file mode 100644
index 0000000..e574a21
Binary files /dev/null and b/src/misc/icon_listning.icns differ
diff --git a/src/misc/icon_listning.ico b/src/misc/icon_listning.ico
new file mode 100644
index 0000000..472cc9b
Binary files /dev/null and b/src/misc/icon_listning.ico differ
diff --git a/src/misc/png转ico和icns.bat b/src/misc/png转ico和icns.bat
new file mode 100644
index 0000000..4d43924
--- /dev/null
+++ b/src/misc/png转ico和icns.bat
@@ -0,0 +1,2 @@
+magick convert "%1" -resize 128x128 "%~dp1%~n1.ico"
+magick convert "%1" -resize 128x128 "%~dp1%~n1.icns"
diff --git a/src/misc/requirements.txt b/src/misc/requirements.txt
new file mode 100644
index 0000000..6a96bb8
--- /dev/null
+++ b/src/misc/requirements.txt
@@ -0,0 +1,5 @@
+setuptools
+pyaudio
+keyboard
+aliyunsdkcore
+PySide2
\ No newline at end of file
diff --git a/src/misc/sponsor.jpg b/src/misc/sponsor.jpg
new file mode 100644
index 0000000..5f78c57
Binary files /dev/null and b/src/misc/sponsor.jpg differ
diff --git a/src/misc/style.css b/src/misc/style.css
new file mode 100644
index 0000000..568dd2c
--- /dev/null
+++ b/src/misc/style.css
@@ -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 *
+}*/
\ No newline at end of file
diff --git a/src/moduels/component/Ali_CallBack.py b/src/moduels/component/Ali_CallBack.py
new file mode 100644
index 0000000..edbc310
--- /dev/null
+++ b/src/moduels/component/Ali_CallBack.py
@@ -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
\ No newline at end of file
diff --git a/src/moduels/component/NormalValue.py b/src/moduels/component/NormalValue.py
new file mode 100644
index 0000000..de75ccf
--- /dev/null
+++ b/src/moduels/component/NormalValue.py
@@ -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()
\ No newline at end of file
diff --git a/src/moduels/component/QEditBox_StdoutBox.py b/src/moduels/component/QEditBox_StdoutBox.py
new file mode 100644
index 0000000..2fdb5fc
--- /dev/null
+++ b/src/moduels/component/QEditBox_StdoutBox.py
@@ -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('文本框更新文本失败')
diff --git a/src/moduels/component/SponsorDialog.py b/src/moduels/component/SponsorDialog.py
new file mode 100644
index 0000000..8b9c1c5
--- /dev/null
+++ b/src/moduels/component/SponsorDialog.py
@@ -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)
\ No newline at end of file
diff --git a/src/moduels/component/Stream.py b/src/moduels/component/Stream.py
new file mode 100644
index 0000000..b879f0b
--- /dev/null
+++ b/src/moduels/component/Stream.py
@@ -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
\ No newline at end of file
diff --git a/src/moduels/function/createDB.py b/src/moduels/function/createDB.py
new file mode 100644
index 0000000..a5ad716
--- /dev/null
+++ b/src/moduels/function/createDB.py
@@ -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() # 最后要提交更改
diff --git a/src/moduels/function/getAlibabaRecognizer.py b/src/moduels/function/getAlibabaRecognizer.py
new file mode 100644
index 0000000..52903a5
--- /dev/null
+++ b/src/moduels/function/getAlibabaRecognizer.py
@@ -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)
\ No newline at end of file
diff --git a/src/moduels/function/getAlibabaToken.py b/src/moduels/function/getAlibabaToken.py
new file mode 100644
index 0000000..1a9fdf8
--- /dev/null
+++ b/src/moduels/function/getAlibabaToken.py
@@ -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
\ No newline at end of file
diff --git a/src/moduels/gui/Combo_EngineList.py b/src/moduels/gui/Combo_EngineList.py
new file mode 100644
index 0000000..8a39ef7
--- /dev/null
+++ b/src/moduels/gui/Combo_EngineList.py
@@ -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.列表项)
+
diff --git a/src/moduels/gui/Dialog_AddEngine.py b/src/moduels/gui/Dialog_AddEngine.py
new file mode 100644
index 0000000..6ff416d
--- /dev/null
+++ b/src/moduels/gui/Dialog_AddEngine.py
@@ -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('引擎列表刷新失败')
diff --git a/src/moduels/gui/Group_EditableList.py b/src/moduels/gui/Group_EditableList.py
new file mode 100644
index 0000000..c37bc4b
--- /dev/null
+++ b/src/moduels/gui/Group_EditableList.py
@@ -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.列表.刷新列表()
+
diff --git a/src/moduels/gui/List_List.py b/src/moduels/gui/List_List.py
new file mode 100644
index 0000000..1aee77a
--- /dev/null
+++ b/src/moduels/gui/List_List.py
@@ -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
+
diff --git a/src/moduels/gui/MainWindow.py b/src/moduels/gui/MainWindow.py
new file mode 100644
index 0000000..f85035e
--- /dev/null
+++ b/src/moduels/gui/MainWindow.py
@@ -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)
diff --git a/src/moduels/gui/SystemTray.py b/src/moduels/gui/SystemTray.py
new file mode 100644
index 0000000..5f0cfc4
--- /dev/null
+++ b/src/moduels/gui/SystemTray.py
@@ -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
\ No newline at end of file
diff --git a/src/moduels/gui/Tab_CapsWriter.py b/src/moduels/gui/Tab_CapsWriter.py
new file mode 100644
index 0000000..1413f51
--- /dev/null
+++ b/src/moduels/gui/Tab_CapsWriter.py
@@ -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)
+
diff --git a/src/moduels/gui/Tab_Config.py b/src/moduels/gui/Tab_Config.py
new file mode 100644
index 0000000..bc91e75
--- /dev/null
+++ b/src/moduels/gui/Tab_Config.py
@@ -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()
diff --git a/src/moduels/gui/Tab_Help.py b/src/moduels/gui/Tab_Help.py
new file mode 100644
index 0000000..a98cd0a
--- /dev/null
+++ b/src/moduels/gui/Tab_Help.py
@@ -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('未能打开帮助文档')
diff --git a/src/moduels/thread/Thread_AliEngine.py b/src/moduels/thread/Thread_AliEngine.py
new file mode 100644
index 0000000..0973308
--- /dev/null
+++ b/src/moduels/thread/Thread_AliEngine.py
@@ -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()
+
+
+
+
diff --git a/token.ini b/token.ini
deleted file mode 100644
index bbaa9d5..0000000
--- a/token.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[Token]
-id = 00000000000000000000
-expiretime = 0000000000
-accesskeyid = 00000000000000
-accesskeysecret = 000000000000000000000000000
-appkey = 000000000000000000000
-
diff --git a/安装指南/alibabacloud-nls-python-sdk/dist/alibabacloud-nls-java-sdk-2.0.0.tar.gz b/安装指南/alibabacloud-nls-python-sdk/dist/alibabacloud-nls-java-sdk-2.0.0.tar.gz
new file mode 100644
index 0000000..db98e8c
Binary files /dev/null and b/安装指南/alibabacloud-nls-python-sdk/dist/alibabacloud-nls-java-sdk-2.0.0.tar.gz differ
diff --git a/安装指南/alibabacloud-nls-python-sdk/dist/alibabacloud_nls_java_sdk-2.0.0-py3.7.egg b/安装指南/alibabacloud-nls-python-sdk/dist/alibabacloud_nls_java_sdk-2.0.0-py3.7.egg
deleted file mode 100644
index 8e203ec..0000000
Binary files a/安装指南/alibabacloud-nls-python-sdk/dist/alibabacloud_nls_java_sdk-2.0.0-py3.7.egg and /dev/null differ
diff --git a/安装指南/requirements.txt b/安装指南/requirements.txt
index 71cf397..6a96bb8 100644
--- a/安装指南/requirements.txt
+++ b/安装指南/requirements.txt
@@ -2,4 +2,4 @@ setuptools
pyaudio
keyboard
aliyunsdkcore
-configparser
\ No newline at end of file
+PySide2
\ No newline at end of file
diff --git a/安装指南/安装指南.md b/安装指南/安装指南.md
index 3ca4b61..1532885 100644
--- a/安装指南/安装指南.md
+++ b/安装指南/安装指南.md
@@ -32,17 +32,4 @@ pip install PyAudio‑0.2.11‑cp37‑cp37m‑win_amd64.whl
- 分类:通用
- 场景:中文普通话 或 其它语言(想识别哪个语言就用哪个)
-发布上线,再记下这个项目的 **appkey**
-
-### 填写 API
-
-用文本编辑器打开项目主目录的 `run.py` 编辑,可以看到:
-
-```python
-""" 在这里填写你的 API 设置 """
-accessID = "xxxxxxxxxxxxxxxxxxxxxxxx"
-accessKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-appkey = 'xxxxxxxxxxxxxxxx'
-```
-
-将你在阿里云的 **accessID**、**accessKey**、**appkey** 分别填入,保存。
\ No newline at end of file
+发布上线,再记下这个项目的 **appkey**
diff --git a/打包/Pyinstaller 编译和打包 Win64.bat b/打包/Pyinstaller 编译和打包 Win64.bat
new file mode 100644
index 0000000..cf2180a
--- /dev/null
+++ b/打包/Pyinstaller 编译和打包 Win64.bat
@@ -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
\ No newline at end of file