refactor(main): 重构字幕引擎启动和错误处理逻辑

- 修改了字幕引擎的启动条件,增加了对环境变量和自定义应用的检查
- 优化了错误处理机制,通过控制窗口发送错误消息
- 在前端增加了错误通知功能
This commit is contained in:
himeditator
2025-06-26 18:59:53 +08:00
parent c086725d98
commit 147e328d8c
3 changed files with 133 additions and 91 deletions

View File

@@ -90,8 +90,17 @@ class ControlWindow {
this.window?.webContents.send('control.engine.already')
}
else {
captionEngine.start()
this.window?.webContents.send('control.engine.started')
if(
process.env.DASHSCOPE_API_KEY ||
(controls.customized && controls.customizedApp)
) {
if(this.window){
captionEngine.start(this.window)
}
}
else {
this.sendErrorMessage('没有检测到 DASHSCOPE_API_KEY 环境变量,如果要使用 gummy 引擎,需要在阿里云百炼平台获取 API Key 并添加到本机环境变量')
}
}
})
// 停止字幕引擎
@@ -104,6 +113,10 @@ class ControlWindow {
captionLog.splice(0)
})
}
public sendErrorMessage(message: string) {
this.window?.webContents.send('control.error.send', message)
}
}
export const controlWindow = new ControlWindow()

View File

@@ -1,103 +1,119 @@
import { spawn, exec } from 'child_process'
import { app } from 'electron'
import { app, BrowserWindow } from 'electron'
import { is } from '@electron-toolkit/utils'
import path from 'path'
import { addCaptionLog, controls } from './config'
import { addCaptionLog, controls, sendControls } from './config'
import { controlWindow } from '../control'
export class CaptionEngine {
appPath: string = ''
command: string[] = []
process: any | undefined
export class CaptionEngine {
appPath: string = ''
command: string[] = []
process: any | undefined
private getApp() {
if(controls.customized && controls.customizedApp){
this.appPath = controls.customizedApp
this.command = [ controls.customizedCommand ]
}
else if(controls.engine === 'gummy'){
let gummyName = ''
if(process.platform === 'win32'){
gummyName = 'main-gummy.exe'
}
else if(process.platform === 'linux'){
gummyName = 'main-gummy'
}
else{
throw new Error('Unsupported platform')
}
if(is.dev){
this.appPath = path.join(
app.getAppPath(),
'python-subprocess', 'dist', gummyName
)
}
else{
this.appPath = path.join(
process.resourcesPath,
'python-subprocess', 'dist', gummyName
)
}
this.command = []
this.command.push('-s', controls.sourceLang)
this.command.push('-t', controls.translation ? controls.targetLang : 'none')
this.command.push('-a', controls.audio ? '1' : '0')
private getApp() {
if (controls.customized && controls.customizedApp) {
this.appPath = controls.customizedApp
this.command = [controls.customizedCommand]
}
else if (controls.engine === 'gummy') {
controls.customized = false
let gummyName = ''
if (process.platform === 'win32') {
gummyName = 'main-gummy.exe'
}
else if (process.platform === 'linux') {
gummyName = 'main-gummy'
}
else {
controlWindow.sendErrorMessage('不支持的操作系统平台:' + process.platform)
throw new Error('Unsupported platform')
}
if (is.dev) {
this.appPath = path.join(
app.getAppPath(),
'python-subprocess', 'dist', gummyName
)
}
else {
this.appPath = path.join(
process.resourcesPath,
'python-subprocess', 'dist', gummyName
)
}
this.command = []
this.command.push('-s', controls.sourceLang)
this.command.push('-t', controls.translation ? controls.targetLang : 'none')
this.command.push('-a', controls.audio ? '1' : '0')
console.log('[INFO] engine', this.appPath)
console.log('[INFO] engine command',this.command)
}
console.log('[INFO] Engine Path:', this.appPath)
console.log('[INFO] Engine Command:', this.command)
}
}
public start(window: BrowserWindow) {
if (this.process) {
this.stop();
}
this.getApp()
try {
this.process = spawn(this.appPath, this.command)
}
catch (e) {
controlWindow.sendErrorMessage('字幕引擎启动失败:' + e)
console.error('[ERROR] Error starting subprocess:', e)
return
}
public start() {
if (this.process) {
this.stop();
console.log('[INFO] Caption Engine Started: ', {
appPath: this.appPath,
command: this.command
})
controls.engineEnabled = true
sendControls(window)
window.webContents.send('control.engine.started')
this.process.stdout.on('data', (data) => {
const lines = data.toString().split('\n');
lines.forEach((line: string) => {
if (line.trim()) {
try {
const caption = JSON.parse(line);
addCaptionLog(caption);
} catch (e) {
controlWindow.sendErrorMessage('字幕引擎输出内容无法解析为 JSON 对象:' + e)
console.error('[ERROR] Error parsing JSON:', e);
}
}
this.getApp()
this.process = spawn(this.appPath, this.command)
controls.engineEnabled = true
});
});
console.log('[INFO] Caption Engine Started: ', {
appPath: this.appPath,
command: this.command
})
this.process.stderr.on('data', (data) => {
controlWindow.sendErrorMessage('字幕引擎错误:' + data)
console.error(`[ERROR] Subprocess Error: ${data}`);
});
this.process.stdout.on('data', (data) => {
const lines = data.toString().split('\n');
lines.forEach( (line: string) => {
if (line.trim()) {
try {
const caption = JSON.parse(line);
addCaptionLog(caption);
} catch (e) {
console.error('Error parsing JSON:', e);
}
}
});
});
this.process.stderr.on('data', (data) => {
console.error(`Python Error: ${data}`);
});
this.process.on('close', (code: any) => {
console.log(`Python process exited with code ${code}`);
this.process = undefined;
this.process.on('close', (code: any) => {
console.log(`[INFO] Subprocess exited with code ${code}`);
this.process = undefined;
});
}
public stop() {
if (this.process) {
if (process.platform === "win32" && this.process.pid) {
exec(`taskkill /pid ${this.process.pid} /t /f`, (error) => {
if (error) {
controlWindow.sendErrorMessage('字幕引擎进程关闭失败:' + error)
console.error(`[ERROR] Failed to kill process: ${error}`);
}
});
} else {
this.process.kill('SIGKILL');
}
this.process = undefined;
controls.engineEnabled = false;
console.log('[INFO] Caption engine process stopped');
}
public stop() {
if (this.process) {
if (process.platform === "win32" && this.process.pid) {
exec(`taskkill /pid ${this.process.pid} /t /f`, (error) => {
if (error) {
console.error(`Failed to kill process: ${error}`);
}
});
} else {
this.process.kill('SIGKILL');
}
this.process = undefined;
controls.engineEnabled = false;
console.log('[INFO] Caption engine process stopped');
}
}
}
}

View File

@@ -1,6 +1,9 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { notification } from 'ant-design-vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
import { h } from 'vue'
export const useCaptionControlStore = defineStore('captionControl', () => {
const captionEngine = ref([
@@ -109,6 +112,16 @@ export const useCaptionControlStore = defineStore('captionControl', () => {
});
})
window.electron.ipcRenderer.on('control.error.send', (_, message) => {
notification.open({
message: '发生错误',
description: message,
duration: null,
placement: 'topLeft',
icon: () => h(ExclamationCircleOutlined, { style: 'color: #ff4d4f' })
});
})
return {
captionEngine, // 字幕引擎
audioType, // 音频类型