mirror of
https://github.com/HiMeditator/auto-caption.git
synced 2026-02-04 04:14:42 +08:00
feat(engine): 优化字幕引擎、提升程序健壮性
- 优化服务器启动流程,增加异常处理 - 主程序和字幕引擎的 WebSocket 端口号改为随机生成
This commit is contained in:
19
README.md
19
README.md
@@ -122,7 +122,7 @@ npm install
|
||||
|
||||
### 构建字幕引擎
|
||||
|
||||
首先进入 `engine` 文件夹,执行如下指令创建虚拟环境:
|
||||
首先进入 `engine` 文件夹,执行如下指令创建虚拟环境(需要使用大于等于 Python 3.10 的 Python 运行环境):
|
||||
|
||||
```bash
|
||||
# in ./engine folder
|
||||
@@ -160,11 +160,10 @@ pip install samplerate --only-binary=:all:
|
||||
然后使用 `pyinstaller` 构建项目:
|
||||
|
||||
```bash
|
||||
pyinstaller ./main-gummy.spec
|
||||
pyinstaller ./main-vosk.spec
|
||||
pyinstaller ./main.spec
|
||||
```
|
||||
|
||||
注意 `main-vosk.spec` 文件中 `vosk` 库的路径可能不正确,需要根据实际状况配置。
|
||||
注意 `main.spec` 文件中 `vosk` 库的路径可能不正确,需要根据实际状况配置。
|
||||
|
||||
```
|
||||
# Windows
|
||||
@@ -197,13 +196,9 @@ npm run build:linux
|
||||
```yml
|
||||
extraResources:
|
||||
# For Windows
|
||||
- from: ./engine/dist/main-gummy.exe
|
||||
to: ./engine/main-gummy.exe
|
||||
- from: ./engine/dist/main-vosk.exe
|
||||
to: ./engine/main-vosk.exe
|
||||
- from: ./engine/dist/main.exe
|
||||
to: ./engine/main.exe
|
||||
# For macOS and Linux
|
||||
# - from: ./engine/dist/main-gummy
|
||||
# to: ./engine/main-gummy
|
||||
# - from: ./engine/dist/main-vosk
|
||||
# to: ./engine/main-vosk
|
||||
# - from: ./engine/dist/main
|
||||
# to: ./engine/main
|
||||
```
|
||||
|
||||
@@ -236,13 +236,13 @@
|
||||
|
||||
### `control.engine.started`
|
||||
|
||||
**介绍:** 引擎启动成功
|
||||
**介绍:** 引擎启动成功,参数为引擎的进程 ID
|
||||
|
||||
**发起方:** 后端
|
||||
|
||||
**接收方:** 前端控制窗口
|
||||
|
||||
**数据类型:** 无数据
|
||||
**数据类型:** `number`
|
||||
|
||||
### `control.engine.stopped`
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ if __name__ == "__main__":
|
||||
parser.add_argument('-e', '--caption_engine', default='gummy', help='Caption engine: gummy or vosk')
|
||||
parser.add_argument('-a', '--audio_type', default=0, help='Audio stream source: 0 for output, 1 for input')
|
||||
parser.add_argument('-c', '--chunk_rate', default=20, help='Number of audio stream chunks collected per second')
|
||||
parser.add_argument('-p', '--port', default=7070, help='The port to run the server on, 0 for no server')
|
||||
parser.add_argument('-p', '--port', default=8080, help='The port to run the server on, 0 for no server')
|
||||
# gummy
|
||||
parser.add_argument('-s', '--source_language', default='en', help='Source language code')
|
||||
parser.add_argument('-t', '--target_language', default='zh', help='Target language code')
|
||||
|
||||
@@ -26,8 +26,13 @@ def handle_client(client_socket):
|
||||
|
||||
def start_server(port: int):
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.bind(('localhost', port))
|
||||
server.listen(1)
|
||||
try:
|
||||
server.bind(('localhost', port))
|
||||
server.listen(1)
|
||||
except Exception as e:
|
||||
stderr(str(e))
|
||||
stdout_cmd('kill')
|
||||
return
|
||||
stdout_cmd('connect')
|
||||
|
||||
client, addr = server.accept()
|
||||
|
||||
871
package-lock.json
generated
871
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "auto-caption",
|
||||
"productName": "Auto Caption",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"description": "A cross-platform subtitle display software.",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "himeditator",
|
||||
@@ -13,7 +13,7 @@
|
||||
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
|
||||
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||
"start": "electron-vite preview",
|
||||
"dev": "chcp 65001 && electron-vite dev",
|
||||
"dev": "electron-vite dev",
|
||||
"build": "npm run typecheck && electron-vite build",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "npm run build && electron-builder --dir",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -58,6 +58,7 @@ export interface FullConfig {
|
||||
export interface EngineInfo {
|
||||
pid: number,
|
||||
ppid: number,
|
||||
port:number,
|
||||
cpu: number,
|
||||
mem: number,
|
||||
elapsed: number
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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, // 错误信号
|
||||
}
|
||||
})
|
||||
|
||||
@@ -58,6 +58,7 @@ export interface FullConfig {
|
||||
export interface EngineInfo {
|
||||
pid: number,
|
||||
ppid: number,
|
||||
port:number,
|
||||
cpu: number,
|
||||
mem: number,
|
||||
elapsed: number
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user