feat(engine): 优化字幕引擎、提升程序健壮性

- 优化服务器启动流程,增加异常处理
- 主程序和字幕引擎的 WebSocket 端口号改为随机生成
This commit is contained in:
himeditator mac
2025-07-29 19:37:03 +08:00
parent e4f937e6b6
commit d5d692188e
17 changed files with 182 additions and 856 deletions

View File

@@ -85,12 +85,13 @@ class ControlWindow {
ipcMain.handle('control.engine.info', async () => {
const info: EngineInfo = {
pid: 0, ppid: 0, cpu: 0, mem: 0, elapsed: 0
pid: 0, ppid: 0, port: 0, cpu: 0, mem: 0, elapsed: 0
}
if(captionEngine.processStatus !== 'running') return info
if(captionEngine.status !== 'running') return info
const stats = await pidusage(captionEngine.process.pid)
info.pid = stats.pid
info.ppid = stats.ppid
info.port = captionEngine.port
info.cpu = stats.cpu
info.mem = stats.memory
info.elapsed = stats.elapsed

View File

@@ -58,6 +58,7 @@ export interface FullConfig {
export interface EngineInfo {
pid: number,
ppid: number,
port:number,
cpu: number,
mem: number,
elapsed: number

View File

@@ -121,9 +121,9 @@ class AllConfig {
Log.info('Set Controls:', this.controls)
}
public sendControls(window: BrowserWindow) {
public sendControls(window: BrowserWindow, info = true) {
window.webContents.send('control.controls.set', this.controls)
Log.info(`Send Controls to #${window.id}:`, this.controls)
if(info) Log.info(`Send Controls to #${window.id}:`, this.controls)
}
public updateCaptionLog(log: CaptionItem) {

View File

@@ -13,6 +13,7 @@ export class CaptionEngine {
command: string[] = []
process: any | undefined
client: net.Socket | undefined
port: number = 8080
status: 'running' | 'starting' | 'stopping' | 'stopped' = 'stopped'
private getApp(): boolean {
@@ -20,6 +21,8 @@ export class CaptionEngine {
Log.info('Using customized caption engine')
this.appPath = allConfig.controls.customizedApp
this.command = allConfig.controls.customizedCommand.split(' ')
this.port = Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024
this.command.push('-p', this.port.toString())
}
else {
if(allConfig.controls.engine === 'gummy' &&
@@ -30,19 +33,34 @@ export class CaptionEngine {
}
this.command = []
if (is.dev) {
this.appPath = path.join(
app.getAppPath(), 'engine',
'subenv', 'Scripts', 'python.exe'
)
this.command.push(path.join(
app.getAppPath(), 'engine', 'main.py'
))
// this.appPath = path.join(app.getAppPath(), 'engine', 'dist', 'main.exe')
if(process.platform === "win32") {
this.appPath = path.join(
app.getAppPath(), 'engine',
'subenv', 'Scripts', 'python.exe'
)
this.command.push(path.join(
app.getAppPath(), 'engine', 'main.py'
))
// this.appPath = path.join(app.getAppPath(), 'engine', 'dist', 'main.exe')
}
else {
this.appPath = path.join(
app.getAppPath(), 'engine',
'subenv', 'bin', 'python3'
)
this.command.push(path.join(
app.getAppPath(), 'engine', 'main.py'
))
}
}
else {
this.appPath = path.join(process.resourcesPath, 'engine', 'main.exe')
}
this.command.push('-a', allConfig.controls.audio ? '1' : '0')
this.port = Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024
this.command.push('-p', this.port.toString())
if(allConfig.controls.engine === 'gummy') {
this.command.push('-e', 'gummy')
this.command.push('-s', allConfig.controls.sourceLang)
@@ -50,14 +68,13 @@ export class CaptionEngine {
'-t', allConfig.controls.translation ?
allConfig.controls.targetLang : 'none'
)
this.command.push('-a', allConfig.controls.audio ? '1' : '0')
if(allConfig.controls.API_KEY) {
this.command.push('-k', allConfig.controls.API_KEY)
}
}
else if(allConfig.controls.engine === 'vosk'){
this.command.push('-e', 'vosk')
this.command.push('-a', allConfig.controls.audio ? '1' : '0')
this.command.push('-m', `"${allConfig.controls.modelPath}"`)
}
}
@@ -67,15 +84,15 @@ export class CaptionEngine {
}
public connect() {
Log.info('Connecting to caption engine server...')
if(this.client) { Log.warn('Client already exists, ignoring...') }
Log.info('Connecting to caption engine server...');
this.client = net.createConnection({ port: 7070 }, () => {
this.client = net.createConnection({ port: this.port }, () => {
Log.info('Connected to caption engine server');
});
this.status = 'running'
allConfig.controls.engineEnabled = true
if(controlWindow.window){
allConfig.sendControls(controlWindow.window)
allConfig.sendControls(controlWindow.window, false)
controlWindow.window.webContents.send(
'control.engine.started',
this.process.pid
@@ -95,7 +112,7 @@ export class CaptionEngine {
public start() {
if (this.status !== 'stopped') {
Log.warn('Casption engine is not stopped, current status:', this.status)
Log.warn('Caption engine is not stopped, current status:', this.status)
return
}
if(!this.getApp()){ return }
@@ -134,7 +151,7 @@ export class CaptionEngine {
this.client = undefined
allConfig.controls.engineEnabled = false
if(controlWindow.window){
allConfig.sendControls(controlWindow.window)
allConfig.sendControls(controlWindow.window, false)
controlWindow.window.webContents.send('control.engine.stopped')
}
this.status = 'stopped'
@@ -144,7 +161,7 @@ export class CaptionEngine {
public stop() {
if(this.status !== 'running'){
Log.warn('Engine is not running, current status:', this.status)
Log.warn('Trying to stop engine which is not running, current status:', this.status)
return
}
this.sendCommand('stop')
@@ -158,15 +175,14 @@ export class CaptionEngine {
public kill(){
if(this.status !== 'running'){
Log.warn('Engine is not running, current status:', this.status)
return
Log.warn('Trying to kill engine which is not running, current status:', this.status)
}
Log.warn('Trying to kill engine process, PID:', this.process.pid)
if(this.client){
this.client.destroy()
this.client = undefined
}
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`
@@ -183,7 +199,7 @@ function handleEngineData(data: any) {
}
else if(data.command === 'kill') {
if(captionEngine.status !== 'stopped') {
Log.warn('Error occurred, trying to kill Gummy engine...')
Log.warn('Error occurred, trying to kill caption engine...')
captionEngine.kill()
}
}
@@ -197,7 +213,7 @@ function handleEngineData(data: any) {
Log.info('Engine Info:', data.content)
}
else if(data.command === 'usage') {
Log.info('Gummy Engine Usage: ', data.content)
Log.info('Engine Usage: ', data.content)
}
else {
Log.warn('Unknown command:', data)

View File

@@ -18,6 +18,10 @@
<div class="engine-status-title">ppid</div>
<div>{{ ppid }}</div>
</a-col>
<a-col :flex="1" :title="$t('status.port')" style="cursor:pointer;">
<div class="engine-status-title">port</div>
<div>{{ port }}</div>
</a-col>
<a-col :flex="1" :title="$t('status.cpu')" style="cursor:pointer;">
<div class="engine-status-title">cpu</div>
<div>{{ cpu.toFixed(1) }}%</div>
@@ -79,7 +83,7 @@
<p class="about-desc">{{ $t('status.about.desc') }}</p>
<a-divider />
<div class="about-info">
<p><b>{{ $t('status.about.version') }}</b><a-tag color="green">v0.5.1</a-tag></p>
<p><b>{{ $t('status.about.version') }}</b><a-tag color="green">v0.6.0</a-tag></p>
<p>
<b>{{ $t('status.about.author') }}</b>
<a
@@ -133,10 +137,11 @@ const pending = ref(false)
const captionLog = useCaptionLogStore()
const { captionData } = storeToRefs(captionLog)
const engineControl = useEngineControlStore()
const { engineEnabled, engine, customized } = storeToRefs(engineControl)
const { engineEnabled, engine, customized, errorSignal } = storeToRefs(engineControl)
const pid = ref(0)
const ppid = ref(0)
const port = ref(0)
const cpu = ref(0)
const mem = ref(0)
const elapsed = ref(0)
@@ -163,6 +168,7 @@ function getEngineInfo() {
window.electron.ipcRenderer.invoke('control.engine.info').then((data: EngineInfo) => {
pid.value = data.pid
ppid.value = data.ppid
port.value = data.port
cpu.value = data.cpu
mem.value = data.mem
elapsed.value = data.elapsed
@@ -172,6 +178,11 @@ function getEngineInfo() {
watch(engineEnabled, () => {
pending.value = false
})
watch(errorSignal, () => {
pending.value = false
errorSignal.value = false
})
</script>
<style scoped>

View File

@@ -22,6 +22,8 @@ export default {
"stopped": "Caption Engine Stopped",
"stoppedInfo": "The caption engine has stopped. You can click the 'Start Caption Engine' button to restart it.",
"error": "An error occurred",
"engineError": "The subtitle engine encountered an error and requested a forced exit.",
"wsError": "The WebSocket connection between the main program and the subtitle engine was not successfully established.",
"engineChange": "Cpation Engine Configuration Changed",
"changeInfo": "If the caption engine is already running, you need to restart it for the changes to take effect.",
"styleChange": "Caption Style Changed",
@@ -95,6 +97,7 @@ export default {
"pid": "Process ID",
"ppid": "Parent Process ID",
"cpu": "CPU Usage",
"port": "WebSocket Port Number",
"mem": "Memory Usage",
"elapsed": "Running Time",
"customized": "Customized",
@@ -116,7 +119,7 @@ export default {
"projLink": "Project Link",
"manual": "User Manual",
"engineDoc": "Caption Engine Manual",
"date": "July 17, 2025"
"date": "July 29, 2025"
}
},
log: {

View File

@@ -22,6 +22,8 @@ export default {
"stopped": "字幕エンジンが停止しました",
"stoppedInfo": "字幕エンジンが停止しました。再起動するには「字幕エンジンを開始」ボタンをクリックしてください。",
"error": "エラーが発生しました",
"engineError": "字幕エンジンにエラーが発生し、強制終了が要求されました。",
"wsError": "メインプログラムと字幕エンジン間の WebSocket 接続が確立されませんでした。",
"engineChange": "字幕エンジンの設定が変更されました",
"changeInfo": "字幕エンジンがすでに起動している場合、変更を有効にするには再起動が必要です。",
"styleChange": "字幕のスタイルが変更されました",
@@ -94,6 +96,7 @@ export default {
"engineStatus": "字幕エンジンの状態",
"pid": "プロセス ID",
"ppid": "親プロセス ID",
"port": "WebSocket ポート番号",
"cpu": "CPU 使用率",
"mem": "メモリ使用量",
"elapsed": "稼働時間",
@@ -116,7 +119,7 @@ export default {
"projLink": "プロジェクトリンク",
"manual": "ユーザーマニュアル",
"engineDoc": "字幕エンジンマニュアル",
"date": "2025 年 7 月 17 日"
"date": "2025 年 7 月 29 日"
}
},
log: {

View File

@@ -22,6 +22,8 @@ export default {
"stopped": "字幕引擎停止",
"stoppedInfo": "字幕引擎已经停止,可点击“启动字幕引擎”按钮重新启动",
"error": "发生错误",
"engineError": "字幕引擎发生错误并请求强制退出",
"wsError": "主程序与字幕引擎的 WebSocket 未成功连接",
"engineChange": "字幕引擎配置已更改",
"changeInfo": "如果字幕引擎已经启动,需要重启字幕引擎修改才会生效",
"styleChange": "字幕样式已修改",
@@ -94,6 +96,7 @@ export default {
"engineStatus": "字幕引擎状态",
"pid": "进程ID",
"ppid": "父进程ID",
"port": "WebSocket 端口号",
"cpu": "CPU使用率",
"mem": "内存使用量",
"elapsed": "运行时间",
@@ -116,7 +119,7 @@ export default {
"projLink": "项目链接",
"manual": "用户手册",
"engineDoc": "字幕引擎手册",
"date": "2025 年 7 月 17 日"
"date": "2025 年 7 月 29 日"
}
},
log: {

View File

@@ -29,6 +29,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
const customizedCommand = ref<string>('')
const changeSignal = ref<boolean>(false)
const errorSignal = ref<boolean>(false)
function sendControlsChange() {
const controls: Controls = {
@@ -47,7 +48,22 @@ export const useEngineControlStore = defineStore('engineControl', () => {
window.electron.ipcRenderer.send('control.controls.change', controls)
}
function setControls(controls: Controls) {
function setControls(controls: Controls, set = false) {
if(set && !engineEnabled.value && !controls.engineEnabled) {
errorSignal.value = true
notification.open({
message: t('noti.error'),
description: t("noti.engineError"),
duration: null,
icon: () => h(ExclamationCircleOutlined, { style: 'color: #ff4d4f' })
});
notification.open({
message: t('noti.error'),
description: t("noti.wsError"),
duration: null,
icon: () => h(ExclamationCircleOutlined, { style: 'color: #ff4d4f' })
});
}
sourceLang.value = controls.sourceLang
targetLang.value = controls.targetLang
engine.value = controls.engine
@@ -71,7 +87,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
}
window.electron.ipcRenderer.on('control.controls.set', (_, controls: Controls) => {
setControls(controls)
setControls(controls, true)
})
window.electron.ipcRenderer.on('control.engine.started', (_, args) => {
@@ -125,5 +141,6 @@ export const useEngineControlStore = defineStore('engineControl', () => {
sendControlsChange, // 发送最新控制消息到后端
emptyModelPathErr, // 模型路径为空时显示警告
changeSignal, // 配置改变信号
errorSignal, // 错误信号
}
})

View File

@@ -58,6 +58,7 @@ export interface FullConfig {
export interface EngineInfo {
pid: number,
ppid: number,
port:number,
cpu: number,
mem: number,
elapsed: number

View File

@@ -36,7 +36,6 @@ const { leftBarWidth, antdTheme } = storeToRefs(generalSettingStore)
background-color: var(--control-background);
}
.caption-control {
height: 100vh;
border-right: 1px solid var(--tag-color);