mirror of
https://github.com/HiMeditator/auto-caption.git
synced 2026-02-22 01:14:41 +08:00
feat(engine): 优化字幕引擎通信和控制逻辑,优化窗口信息展示
- 优化错误处理和引擎重启逻辑 - 添加字幕引擎强制终止功能 - 调整通知和错误提示的显示位置 - 优化日志记录精度到毫秒级
This commit is contained in:
@@ -123,9 +123,13 @@
|
|||||||
|
|
||||||
- 新增字幕记录排序功能,可选择字幕记录正序或倒叙显示
|
- 新增字幕记录排序功能,可选择字幕记录正序或倒叙显示
|
||||||
|
|
||||||
|
### 优化体验
|
||||||
|
|
||||||
|
- 交换窗口界面信息和错误提示弹窗的位置,防止提示信息挡住操作
|
||||||
|
|
||||||
### 项目优化
|
### 项目优化
|
||||||
|
|
||||||
- 重构字幕引擎,提示字幕引擎代码的可扩展性和可读性
|
- 重构字幕引擎,提示字幕引擎代码的可扩展性和可读性
|
||||||
- 合并 Gummy 和 Vosk 引擎为单个可执行文件
|
- 合并 Gummy 和 Vosk 引擎为单个可执行文件,减小软件体积
|
||||||
- 字幕引擎和主程序添加 WebScoket 通信,完全避免字幕引擎成为孤儿进程
|
- 字幕引擎和主程序添加 WebScoket 通信,完全避免字幕引擎成为孤儿进程
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,63 @@
|
|||||||
# caption engine api-doc
|
# caption engine api-doc
|
||||||
|
|
||||||
本文档主要 Electron 主进程和字幕引擎进程的通信约定。
|
本文档主要介绍字幕引擎和 Electron 主进程进程的通信约定。
|
||||||
|
|
||||||
## 原理说明
|
## 原理说明
|
||||||
|
|
||||||
本项目的 Python 进程通过标准输出向 Electron 主进程发送数据。
|
本项目的 Python 进程通过标准输出向 Electron 主进程发送数据。Python 进程标准输出 (`sys.stdout`) 的内容一定为一行一行的字符串。且每行字符串均可以解释为一个 JSON 对象。每个 JSON 对象一定有 `command` 参数。
|
||||||
|
|
||||||
Python 进程标准输出 (`sys.stdout`) 的内容一定为一行一行的字符串。且每行字符串均可以解释为一个 JSON 对象。每个 JSON 对象一定有 `command` 参数。
|
Electron 主进程通过 WebSocket 向 Python 进程发送数据。发送的数据均是转化为字符串的对象,对象格式一定为:
|
||||||
|
|
||||||
## 输出约定
|
```js
|
||||||
|
{
|
||||||
|
command: string,
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 标准输出约定
|
||||||
|
|
||||||
|
> 数据传递方向:字幕引擎进程 => Electron 主进程
|
||||||
|
|
||||||
当 JSON 对象的 `command` 参数为下列值时,表示的对应的含义:
|
当 JSON 对象的 `command` 参数为下列值时,表示的对应的含义:
|
||||||
|
|
||||||
|
### `connect`
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
command: "connect",
|
||||||
|
content: ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字幕引擎 WebSocket 服务已经准备好,命令 Electron 主进程连接字幕引擎 WebSocket 服务
|
||||||
|
|
||||||
|
### `kill`
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
command: "connect",
|
||||||
|
content: ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
命令 Electron 主进程强制结束字幕引擎进程。
|
||||||
|
|
||||||
|
### `caption`
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
command: "caption",
|
||||||
|
index: number,
|
||||||
|
time_s: string,
|
||||||
|
time_t: string,
|
||||||
|
text: string,
|
||||||
|
translation: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Python 端监听到的音频流转换为的字幕数据。
|
||||||
|
|
||||||
### `print`
|
### `print`
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@@ -45,18 +91,12 @@ Python 端打印的提示信息,比起 `print`,该信息更希望 Electron
|
|||||||
|
|
||||||
Gummy 字幕引擎结束时打印计费消耗信息。
|
Gummy 字幕引擎结束时打印计费消耗信息。
|
||||||
|
|
||||||
|
## WebSocket
|
||||||
|
|
||||||
### `caption`
|
> 数据传递方向:Electron 主进程 => 字幕引擎进程
|
||||||
|
|
||||||
```js
|
当 JSON 对象的 `command` 参数为下列值时,表示的对应的含义:
|
||||||
{
|
|
||||||
command: "caption",
|
|
||||||
index: number,
|
|
||||||
time_s: string,
|
|
||||||
time_t: string,
|
|
||||||
text: string,
|
|
||||||
translation: string
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Python 端监听到的音频流转换为的字幕数据。
|
### `stop`
|
||||||
|
|
||||||
|
命令当前字幕引擎停止监听并结束任务。
|
||||||
@@ -6,7 +6,7 @@ from dashscope.audio.asr import (
|
|||||||
)
|
)
|
||||||
import dashscope
|
import dashscope
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from utils import stdout_cmd, stdout_obj
|
from utils import stdout_cmd, stdout_obj, stderr
|
||||||
|
|
||||||
|
|
||||||
class Callback(TranslationRecognizerCallback):
|
class Callback(TranslationRecognizerCallback):
|
||||||
@@ -96,4 +96,7 @@ class GummyRecognizer:
|
|||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""停止 Gummy 引擎"""
|
"""停止 Gummy 引擎"""
|
||||||
self.translator.stop()
|
try:
|
||||||
|
self.translator.stop()
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import argparse
|
import argparse
|
||||||
from utils import stdout_cmd
|
from utils import stdout_cmd, stderr
|
||||||
from utils import thread_data, start_server
|
from utils import thread_data, start_server
|
||||||
from utils import merge_chunk_channels, resample_chunk_mono
|
from utils import merge_chunk_channels, resample_chunk_mono
|
||||||
from audio2text import InvalidParameter, GummyRecognizer
|
from audio2text import InvalidParameter, GummyRecognizer
|
||||||
@@ -8,6 +8,7 @@ from sysaudio import AudioStream
|
|||||||
|
|
||||||
|
|
||||||
def main_gummy(s: str, t: str, a: int, c: int, k: str):
|
def main_gummy(s: str, t: str, a: int, c: int, k: str):
|
||||||
|
global thread_data
|
||||||
stream = AudioStream(a, c)
|
stream = AudioStream(a, c)
|
||||||
if t == 'none':
|
if t == 'none':
|
||||||
engine = GummyRecognizer(stream.RATE, s, None, k)
|
engine = GummyRecognizer(stream.RATE, s, None, k)
|
||||||
@@ -17,6 +18,7 @@ def main_gummy(s: str, t: str, a: int, c: int, k: str):
|
|||||||
stream.open_stream()
|
stream.open_stream()
|
||||||
engine.start()
|
engine.start()
|
||||||
|
|
||||||
|
restart_count = 0
|
||||||
while thread_data.status == "running":
|
while thread_data.status == "running":
|
||||||
try:
|
try:
|
||||||
chunk = stream.read_chunk()
|
chunk = stream.read_chunk()
|
||||||
@@ -24,18 +26,22 @@ def main_gummy(s: str, t: str, a: int, c: int, k: str):
|
|||||||
chunk_mono = merge_chunk_channels(chunk, stream.CHANNELS)
|
chunk_mono = merge_chunk_channels(chunk, stream.CHANNELS)
|
||||||
try:
|
try:
|
||||||
engine.send_audio_frame(chunk_mono)
|
engine.send_audio_frame(chunk_mono)
|
||||||
except InvalidParameter:
|
except InvalidParameter as e:
|
||||||
stdout_cmd('info', 'Gummy engine stopped, restart engine')
|
restart_count += 1
|
||||||
engine.start()
|
if restart_count > 8:
|
||||||
engine.send_audio_frame(chunk_mono)
|
stderr(str(e))
|
||||||
|
thread_data.status = "kill"
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
stdout_cmd('info', f'Gummy engine stopped, trying to restart #{restart_count}')
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
break
|
break
|
||||||
|
|
||||||
stream.close_stream()
|
stream.close_stream()
|
||||||
engine.stop()
|
engine.stop()
|
||||||
|
|
||||||
|
|
||||||
def main_vosk(a: int, c: int, m: str):
|
def main_vosk(a: int, c: int, m: str):
|
||||||
|
global thread_data
|
||||||
stream = AudioStream(a, c)
|
stream = AudioStream(a, c)
|
||||||
engine = VoskRecognizer(m)
|
engine = VoskRecognizer(m)
|
||||||
|
|
||||||
@@ -68,9 +74,8 @@ if __name__ == "__main__":
|
|||||||
parser.add_argument('-k', '--api_key', default='', help='API KEY for Gummy model')
|
parser.add_argument('-k', '--api_key', default='', help='API KEY for Gummy model')
|
||||||
# vosk
|
# vosk
|
||||||
parser.add_argument('-m', '--model_path', default='', help='The path to the vosk model.')
|
parser.add_argument('-m', '--model_path', default='', help='The path to the vosk model.')
|
||||||
# for test
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if int(args.port) == 0:
|
if int(args.port) == 0:
|
||||||
thread_data.status = "running"
|
thread_data.status = "running"
|
||||||
else:
|
else:
|
||||||
@@ -91,4 +96,7 @@ if __name__ == "__main__":
|
|||||||
args.model_path
|
args.model_path
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Invalid caption engine specified.')
|
raise ValueError('Invalid caption engine specified.')
|
||||||
|
|
||||||
|
if thread_data.status == "kill":
|
||||||
|
stdout_cmd('kill')
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import samplerate
|
import samplerate
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import numpy.core.multiarray
|
import numpy.core.multiarray # do not remove
|
||||||
|
|
||||||
def merge_chunk_channels(chunk: bytes, channels: int) -> bytes:
|
def merge_chunk_channels(chunk: bytes, channels: int) -> bytes:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from utils import thread_data, stdout_cmd, stderr
|
|||||||
|
|
||||||
def handle_client(client_socket):
|
def handle_client(client_socket):
|
||||||
global thread_data
|
global thread_data
|
||||||
while True:
|
while thread_data.status == 'running':
|
||||||
try:
|
try:
|
||||||
data = client_socket.recv(4096).decode('utf-8')
|
data = client_socket.recv(4096).decode('utf-8')
|
||||||
if not data:
|
if not data:
|
||||||
@@ -14,9 +14,8 @@ def handle_client(client_socket):
|
|||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
|
|
||||||
if data['command'] == 'stop':
|
if data['command'] == 'stop':
|
||||||
if thread_data.status == 'running':
|
thread_data.status = 'stop'
|
||||||
thread_data.status = 'stop'
|
break
|
||||||
break
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
stderr(f'Communication error: {e}')
|
stderr(f'Communication error: {e}')
|
||||||
break
|
break
|
||||||
@@ -29,7 +28,7 @@ def start_server(port: int):
|
|||||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
server.bind(('localhost', port))
|
server.bind(('localhost', port))
|
||||||
server.listen(1)
|
server.listen(1)
|
||||||
stdout_cmd('ready')
|
stdout_cmd('connect')
|
||||||
|
|
||||||
client, addr = server.accept()
|
client, addr = server.accept()
|
||||||
client_handler = threading.Thread(target=handle_client, args=(client,))
|
client_handler = threading.Thread(target=handle_client, args=(client,))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { spawn } from 'child_process'
|
import { exec, spawn } from 'child_process'
|
||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
import { is } from '@electron-toolkit/utils'
|
import { is } from '@electron-toolkit/utils'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
@@ -13,11 +13,11 @@ export class CaptionEngine {
|
|||||||
command: string[] = []
|
command: string[] = []
|
||||||
process: any | undefined
|
process: any | undefined
|
||||||
client: net.Socket | undefined
|
client: net.Socket | undefined
|
||||||
status: 'running' | 'stopping' | 'stopped' = 'stopped'
|
status: 'running' | 'starting' | 'stopping' | 'stopped' = 'stopped'
|
||||||
|
|
||||||
private getApp(): boolean {
|
private getApp(): boolean {
|
||||||
if (allConfig.controls.customized) {
|
if (allConfig.controls.customized) {
|
||||||
Log.info('Using customized engine')
|
Log.info('Using customized caption engine')
|
||||||
this.appPath = allConfig.controls.customizedApp
|
this.appPath = allConfig.controls.customizedApp
|
||||||
this.command = allConfig.controls.customizedCommand.split(' ')
|
this.command = allConfig.controls.customizedCommand.split(' ')
|
||||||
}
|
}
|
||||||
@@ -30,14 +30,14 @@ export class CaptionEngine {
|
|||||||
}
|
}
|
||||||
this.command = []
|
this.command = []
|
||||||
if (is.dev) {
|
if (is.dev) {
|
||||||
// this.appPath = path.join(
|
this.appPath = path.join(
|
||||||
// app.getAppPath(), 'engine',
|
app.getAppPath(), 'engine',
|
||||||
// 'subenv', 'Scripts', 'python.exe'
|
'subenv', 'Scripts', 'python.exe'
|
||||||
// )
|
)
|
||||||
// this.command.push(path.join(
|
this.command.push(path.join(
|
||||||
// app.getAppPath(), 'engine', 'main.py'
|
app.getAppPath(), 'engine', 'main.py'
|
||||||
// ))
|
))
|
||||||
this.appPath = path.join(app.getAppPath(), 'engine', 'dist', 'main.exe')
|
// this.appPath = path.join(app.getAppPath(), 'engine', 'dist', 'main.exe')
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.appPath = path.join(process.resourcesPath, 'engine', 'main.exe')
|
this.appPath = path.join(process.resourcesPath, 'engine', 'main.exe')
|
||||||
@@ -73,6 +73,14 @@ export class CaptionEngine {
|
|||||||
Log.info('Connected to caption engine server');
|
Log.info('Connected to caption engine server');
|
||||||
});
|
});
|
||||||
this.status = 'running'
|
this.status = 'running'
|
||||||
|
allConfig.controls.engineEnabled = true
|
||||||
|
if(controlWindow.window){
|
||||||
|
allConfig.sendControls(controlWindow.window)
|
||||||
|
controlWindow.window.webContents.send(
|
||||||
|
'control.engine.started',
|
||||||
|
this.process.pid
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendCommand(command: string, content: string = "") {
|
public sendCommand(command: string, content: string = "") {
|
||||||
@@ -93,19 +101,11 @@ export class CaptionEngine {
|
|||||||
if(!this.getApp()){ return }
|
if(!this.getApp()){ return }
|
||||||
|
|
||||||
this.process = spawn(this.appPath, this.command)
|
this.process = spawn(this.appPath, this.command)
|
||||||
Log.info('Caption Engine Started, PID:', this.process.pid)
|
this.status = 'starting'
|
||||||
|
Log.info('Caption Engine Starting, PID:', this.process.pid)
|
||||||
allConfig.controls.engineEnabled = true
|
|
||||||
if(controlWindow.window){
|
|
||||||
allConfig.sendControls(controlWindow.window)
|
|
||||||
controlWindow.window.webContents.send(
|
|
||||||
'control.engine.started',
|
|
||||||
this.process.pid
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.process.stdout.on('data', (data: any) => {
|
this.process.stdout.on('data', (data: any) => {
|
||||||
const lines = data.toString().split('\n');
|
const lines = data.toString().split('\n')
|
||||||
lines.forEach((line: string) => {
|
lines.forEach((line: string) => {
|
||||||
if (line.trim()) {
|
if (line.trim()) {
|
||||||
try {
|
try {
|
||||||
@@ -120,13 +120,18 @@ export class CaptionEngine {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.process.stderr.on('data', (data: any) => {
|
this.process.stderr.on('data', (data: any) => {
|
||||||
if(this.status === 'stopping') return
|
const lines = data.toString().split('\n')
|
||||||
controlWindow.sendErrorMessage(i18n('engine.error') + data)
|
lines.forEach((line: string) => {
|
||||||
Log.error(`Engine Error: ${data}`);
|
if(line.trim()){
|
||||||
|
controlWindow.sendErrorMessage(/*i18n('engine.error') +*/ line)
|
||||||
|
console.error(line)
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
this.process.on('close', (code: any) => {
|
this.process.on('close', (code: any) => {
|
||||||
this.process = undefined;
|
this.process = undefined;
|
||||||
|
this.client = undefined
|
||||||
allConfig.controls.engineEnabled = false
|
allConfig.controls.engineEnabled = false
|
||||||
if(controlWindow.window){
|
if(controlWindow.window){
|
||||||
allConfig.sendControls(controlWindow.window)
|
allConfig.sendControls(controlWindow.window)
|
||||||
@@ -150,25 +155,52 @@ export class CaptionEngine {
|
|||||||
this.status = 'stopping'
|
this.status = 'stopping'
|
||||||
Log.info('Caption engine process stopping...')
|
Log.info('Caption engine process stopping...')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public kill(){
|
||||||
|
if(this.status !== 'running'){
|
||||||
|
Log.warn('Engine is not running, current status:', this.status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.process.pid) {
|
||||||
|
Log.warn('Trying to kill engine process, PID:', this.process.pid)
|
||||||
|
if(this.client){
|
||||||
|
this.client.destroy()
|
||||||
|
this.client = undefined
|
||||||
|
}
|
||||||
|
let cmd = `kill ${this.process.pid}`;
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
cmd = `taskkill /pid ${this.process.pid} /t /f`
|
||||||
|
}
|
||||||
|
exec(cmd)
|
||||||
|
}
|
||||||
|
this.status = 'stopping'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEngineData(data: any) {
|
function handleEngineData(data: any) {
|
||||||
if(data.command === 'ready'){
|
if(data.command === 'connect'){
|
||||||
captionEngine.connect()
|
captionEngine.connect()
|
||||||
}
|
}
|
||||||
|
else if(data.command === 'kill') {
|
||||||
|
if(captionEngine.status !== 'stopped') {
|
||||||
|
Log.warn('Error occurred, trying to kill Gummy engine...')
|
||||||
|
captionEngine.kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
else if(data.command === 'caption') {
|
else if(data.command === 'caption') {
|
||||||
allConfig.updateCaptionLog(data);
|
allConfig.updateCaptionLog(data);
|
||||||
}
|
}
|
||||||
else if(data.command === 'print') {
|
else if(data.command === 'print') {
|
||||||
console.log(data.content)
|
Log.info('Engine Print:', data.content)
|
||||||
// Log.info('Engine Print:', data.content)
|
|
||||||
}
|
}
|
||||||
else if(data.command === 'info') {
|
else if(data.command === 'info') {
|
||||||
Log.info('Engine Info:', data.content)
|
Log.info('Engine Info:', data.content)
|
||||||
}
|
}
|
||||||
else if(data.command === 'usage') {
|
else if(data.command === 'usage') {
|
||||||
console.error(data.content)
|
Log.info('Gummy Engine Usage: ', data.content)
|
||||||
// Log.info('Gummy Engine Usage: ', data.content)
|
}
|
||||||
|
else {
|
||||||
|
Log.warn('Unknown command:', data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ function getTimeString() {
|
|||||||
const HH = String(now.getHours()).padStart(2, '0')
|
const HH = String(now.getHours()).padStart(2, '0')
|
||||||
const MM = String(now.getMinutes()).padStart(2, '0')
|
const MM = String(now.getMinutes()).padStart(2, '0')
|
||||||
const SS = String(now.getSeconds()).padStart(2, '0')
|
const SS = String(now.getSeconds()).padStart(2, '0')
|
||||||
return `${HH}:${MM}:${SS}`
|
const MS = String(now.getMilliseconds()).padStart(3, '0')
|
||||||
|
return `${HH}:${MM}:${SS}.${MS}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Log {
|
export class Log {
|
||||||
|
|||||||
@@ -282,7 +282,8 @@ function applyStyle(){
|
|||||||
|
|
||||||
captionStyle.sendStylesChange();
|
captionStyle.sendStylesChange();
|
||||||
|
|
||||||
notification.open({
|
notification.open({
|
||||||
|
placement: 'topLeft',
|
||||||
message: t('noti.styleChange'),
|
message: t('noti.styleChange'),
|
||||||
description: t('noti.styleInfo')
|
description: t('noti.styleInfo')
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ function applyChange(){
|
|||||||
engineControl.sendControlsChange()
|
engineControl.sendControlsChange()
|
||||||
|
|
||||||
notification.open({
|
notification.open({
|
||||||
|
placement: 'topLeft',
|
||||||
message: t('noti.engineChange'),
|
message: t('noti.engineChange'),
|
||||||
description: t('noti.changeInfo')
|
description: t('noti.changeInfo')
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,12 +61,14 @@
|
|||||||
>{{ $t('status.openCaption') }}</a-button>
|
>{{ $t('status.openCaption') }}</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
class="control-button"
|
class="control-button"
|
||||||
:disabled="engineEnabled"
|
:loading="pending && !engineEnabled"
|
||||||
|
:disabled="pending || engineEnabled"
|
||||||
@click="startEngine"
|
@click="startEngine"
|
||||||
>{{ $t('status.startEngine') }}</a-button>
|
>{{ $t('status.startEngine') }}</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
danger class="control-button"
|
danger class="control-button"
|
||||||
:disabled="!engineEnabled"
|
:loading="pending && engineEnabled"
|
||||||
|
:disabled="pending || !engineEnabled"
|
||||||
@click="stopEngine"
|
@click="stopEngine"
|
||||||
>{{ $t('status.stopEngine') }}</a-button>
|
>{{ $t('status.stopEngine') }}</a-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,13 +121,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { EngineInfo } from '@renderer/types'
|
import { EngineInfo } from '@renderer/types'
|
||||||
import { ref } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useCaptionLogStore } from '@renderer/stores/captionLog'
|
import { useCaptionLogStore } from '@renderer/stores/captionLog'
|
||||||
import { useEngineControlStore } from '@renderer/stores/engineControl'
|
import { useEngineControlStore } from '@renderer/stores/engineControl'
|
||||||
import { GithubOutlined, InfoCircleOutlined } from '@ant-design/icons-vue';
|
import { GithubOutlined, InfoCircleOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
const showAbout = ref(false)
|
const showAbout = ref(false)
|
||||||
|
const pending = ref(false)
|
||||||
|
|
||||||
const captionLog = useCaptionLogStore()
|
const captionLog = useCaptionLogStore()
|
||||||
const { captionData } = storeToRefs(captionLog)
|
const { captionData } = storeToRefs(captionLog)
|
||||||
@@ -143,6 +146,7 @@ function openCaptionWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startEngine() {
|
function startEngine() {
|
||||||
|
pending.value = true
|
||||||
if(engineControl.engine === 'vosk' && engineControl.modelPath.trim() === '') {
|
if(engineControl.engine === 'vosk' && engineControl.modelPath.trim() === '') {
|
||||||
engineControl.emptyModelPathErr()
|
engineControl.emptyModelPathErr()
|
||||||
return
|
return
|
||||||
@@ -151,6 +155,7 @@ function startEngine() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function stopEngine() {
|
function stopEngine() {
|
||||||
|
pending.value = true
|
||||||
window.electron.ipcRenderer.send('control.engine.stop')
|
window.electron.ipcRenderer.send('control.engine.stop')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +169,9 @@ function getEngineInfo() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(engineEnabled, () => {
|
||||||
|
pending.value = false
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
|
|||||||
|
|
||||||
function emptyModelPathErr() {
|
function emptyModelPathErr() {
|
||||||
notification.open({
|
notification.open({
|
||||||
|
placement: 'topLeft',
|
||||||
message: t('noti.empty'),
|
message: t('noti.empty'),
|
||||||
description: t('noti.emptyInfo')
|
description: t('noti.emptyInfo')
|
||||||
});
|
});
|
||||||
@@ -80,6 +81,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
|
|||||||
(translation.value ? `${t('noti.tLang')}${targetLang.value}` : '');
|
(translation.value ? `${t('noti.tLang')}${targetLang.value}` : '');
|
||||||
const str1 = `${t('noti.custom')}${customizedApp.value}${t('noti.args')}${customizedCommand.value}`;
|
const str1 = `${t('noti.custom')}${customizedApp.value}${t('noti.args')}${customizedCommand.value}`;
|
||||||
notification.open({
|
notification.open({
|
||||||
|
placement: 'topLeft',
|
||||||
message: t('noti.started'),
|
message: t('noti.started'),
|
||||||
description:
|
description:
|
||||||
(customized.value ? str1 : str0) +
|
(customized.value ? str1 : str0) +
|
||||||
@@ -89,6 +91,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
|
|||||||
|
|
||||||
window.electron.ipcRenderer.on('control.engine.stopped', () => {
|
window.electron.ipcRenderer.on('control.engine.stopped', () => {
|
||||||
notification.open({
|
notification.open({
|
||||||
|
placement: 'topLeft',
|
||||||
message: t('noti.stopped'),
|
message: t('noti.stopped'),
|
||||||
description: t('noti.stoppedInfo')
|
description: t('noti.stoppedInfo')
|
||||||
});
|
});
|
||||||
@@ -99,7 +102,6 @@ export const useEngineControlStore = defineStore('engineControl', () => {
|
|||||||
message: t('noti.error'),
|
message: t('noti.error'),
|
||||||
description: message,
|
description: message,
|
||||||
duration: null,
|
duration: null,
|
||||||
placement: 'topLeft',
|
|
||||||
icon: () => h(ExclamationCircleOutlined, { style: 'color: #ff4d4f' })
|
icon: () => h(ExclamationCircleOutlined, { style: 'color: #ff4d4f' })
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user