mirror of
https://github.com/HiMeditator/auto-caption.git
synced 2026-02-04 04:14:42 +08:00
refactor: 重构项目后端
- 移除 .npmrc 中的镜像配置 - 移除 package.json 中未使用的依赖 - 大幅重构后端代码
This commit is contained in:
4
.npmrc
4
.npmrc
@@ -1,2 +1,2 @@
|
|||||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
# electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||||
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
|
# electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
|
||||||
|
|||||||
@@ -19,4 +19,9 @@
|
|||||||
### 新增文档
|
### 新增文档
|
||||||
|
|
||||||
- 添加用户说明文档
|
- 添加用户说明文档
|
||||||
- 添加字幕引擎说明文档
|
- 添加字幕引擎说明文档
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
> 预计为稳定版,之后除非大改,否则版本号第一位不再改变。
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## 字幕引擎介绍
|
## 字幕引擎介绍
|
||||||
|
|
||||||
所谓的字幕引擎实际上是一个子程序,它会实时获取系统音频输入(录音)或输出(播放声音)的流式数据,并调用音频转文字的模型生成对应音频的字幕。生成的字幕通过 IPC 输出为转换为 JSON 格式的字符串数据,并返回给主程序。主程序读取字幕数据,处理后显示在窗口上。
|
所谓的字幕引擎实际上是一个子程序,它会实时获取系统音频输入(录音)或输出(播放声音)的流式数据,并调用音频转文字的模型生成对应音频的字幕。生成的字幕转换为 JSON 格式的字符串数据,并通过 IPC 传递给主程序。主程序读取字幕数据,处理后显示在窗口上。
|
||||||
|
|
||||||
## 字幕引擎需要实现的功能
|
## 字幕引擎需要实现的功能
|
||||||
|
|
||||||
|
|||||||
@@ -25,15 +25,13 @@
|
|||||||
"@electron-toolkit/utils": "^4.0.0",
|
"@electron-toolkit/utils": "^4.0.0",
|
||||||
"ant-design-vue": "^4.2.6",
|
"ant-design-vue": "^4.2.6",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.5.1"
|
||||||
"ws": "^8.18.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron-toolkit/eslint-config-prettier": "3.0.0",
|
"@electron-toolkit/eslint-config-prettier": "3.0.0",
|
||||||
"@electron-toolkit/eslint-config-ts": "^3.0.0",
|
"@electron-toolkit/eslint-config-ts": "^3.0.0",
|
||||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||||
"@types/node": "^22.14.1",
|
"@types/node": "^22.14.1",
|
||||||
"@types/ws": "^8.18.1",
|
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
"electron": "^35.1.5",
|
"electron": "^35.1.5",
|
||||||
"electron-builder": "^25.1.8",
|
"electron-builder": "^25.1.8",
|
||||||
|
|||||||
Binary file not shown.
@@ -2,13 +2,13 @@ import { shell, BrowserWindow, ipcMain } from 'electron'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { is } from '@electron-toolkit/utils'
|
import { is } from '@electron-toolkit/utils'
|
||||||
import icon from '../../resources/icon.png?asset'
|
import icon from '../../resources/icon.png?asset'
|
||||||
import { controlWindow } from './control'
|
import { controlWindow } from './ControlWindow'
|
||||||
import { sendStyles, sendCaptionLog } from './utils/config'
|
import { allConfig } from './utils/AllConfig'
|
||||||
|
|
||||||
class CaptionWindow {
|
class CaptionWindow {
|
||||||
window: BrowserWindow | undefined;
|
window: BrowserWindow | undefined;
|
||||||
|
|
||||||
public createWindow(): void {
|
public createWindow(): void {
|
||||||
this.window = new BrowserWindow({
|
this.window = new BrowserWindow({
|
||||||
icon: icon,
|
icon: icon,
|
||||||
width: 900,
|
width: 900,
|
||||||
@@ -26,11 +26,11 @@ class CaptionWindow {
|
|||||||
sandbox: false
|
sandbox: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.window) {
|
if (this.window) {
|
||||||
sendStyles(this.window);
|
allConfig.sendStyles(this.window);
|
||||||
sendCaptionLog(this.window, 'set');
|
allConfig.sendCaptionLog(this.window, 'set');
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ class CaptionWindow {
|
|||||||
shell.openExternal(details.url)
|
shell.openExternal(details.url)
|
||||||
return { action: 'deny' }
|
return { action: 'deny' }
|
||||||
})
|
})
|
||||||
|
|
||||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||||
this.window.loadURL(`${process.env['ELECTRON_RENDERER_URL']}/#/caption`)
|
this.window.loadURL(`${process.env['ELECTRON_RENDERER_URL']}/#/caption`)
|
||||||
} else {
|
} else {
|
||||||
@@ -57,7 +57,7 @@ class CaptionWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public handleMessage() {
|
public handleMessage() {
|
||||||
// 字幕窗口请求创建控制窗口
|
// 激活控制窗口
|
||||||
ipcMain.on('caption.controlWindow.activate', () => {
|
ipcMain.on('caption.controlWindow.activate', () => {
|
||||||
if(!controlWindow.window){
|
if(!controlWindow.window){
|
||||||
controlWindow.createWindow()
|
controlWindow.createWindow()
|
||||||
@@ -66,18 +66,21 @@ class CaptionWindow {
|
|||||||
controlWindow.window.show()
|
controlWindow.window.show()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 字幕窗口高度发生变化
|
// 字幕窗口高度发生变化
|
||||||
ipcMain.on('caption.windowHeight.change', (_, height) => {
|
ipcMain.on('caption.windowHeight.change', (_, height) => {
|
||||||
if(this.window){
|
if(this.window){
|
||||||
this.window.setSize(this.window.getSize()[0], height)
|
this.window.setSize(this.window.getSize()[0], height)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 关闭字幕窗口
|
// 关闭字幕窗口
|
||||||
ipcMain.on('caption.window.close', () => {
|
ipcMain.on('caption.window.close', () => {
|
||||||
if(this.window){
|
if(this.window){
|
||||||
this.window.close()
|
this.window.close()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 是否固定在最前面
|
// 是否固定在最前面
|
||||||
ipcMain.on('caption.pin.set', (_, pinned) => {
|
ipcMain.on('caption.pin.set', (_, pinned) => {
|
||||||
if(this.window){
|
if(this.window){
|
||||||
@@ -2,20 +2,9 @@ import { shell, BrowserWindow, ipcMain } from 'electron'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { is } from '@electron-toolkit/utils'
|
import { is } from '@electron-toolkit/utils'
|
||||||
import icon from '../../resources/icon.png?asset'
|
import icon from '../../resources/icon.png?asset'
|
||||||
import { captionWindow } from './caption'
|
import { captionWindow } from './CaptionWindow'
|
||||||
import {
|
import { allConfig } from './utils/AllConfig'
|
||||||
captionEngine,
|
import { captionEngine } from './utils/CaptionEngine'
|
||||||
captionLog,
|
|
||||||
controls,
|
|
||||||
setStyles,
|
|
||||||
resetStyles,
|
|
||||||
sendStyles,
|
|
||||||
sendCaptionLog,
|
|
||||||
setControls,
|
|
||||||
sendControls,
|
|
||||||
readConfig,
|
|
||||||
writeConfig
|
|
||||||
} from './utils/config'
|
|
||||||
|
|
||||||
class ControlWindow {
|
class ControlWindow {
|
||||||
window: BrowserWindow | undefined;
|
window: BrowserWindow | undefined;
|
||||||
@@ -25,8 +14,8 @@ class ControlWindow {
|
|||||||
icon: icon,
|
icon: icon,
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 800,
|
height: 800,
|
||||||
minWidth: 900,
|
minWidth: 600,
|
||||||
minHeight: 600,
|
minHeight: 400,
|
||||||
show: false,
|
show: false,
|
||||||
center: true,
|
center: true,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
@@ -37,30 +26,30 @@ class ControlWindow {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
allConfig.readConfig()
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.window) {
|
if (this.window) {
|
||||||
readConfig()
|
allConfig.sendStyles(this.window)
|
||||||
sendStyles(this.window) // 配置初始样式
|
allConfig.sendControls(this.window)
|
||||||
sendCaptionLog(this.window, 'set') // 配置当前字幕记录
|
allConfig.sendCaptionLog(this.window, 'set')
|
||||||
sendControls(this.window) // 配置字幕引擎配置
|
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
|
|
||||||
this.window.on('ready-to-show', () => {
|
this.window.on('ready-to-show', () => {
|
||||||
this.window?.show()
|
this.window?.show()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.window.on('closed', () => {
|
this.window.on('closed', () => {
|
||||||
this.window = undefined
|
this.window = undefined
|
||||||
writeConfig()
|
allConfig.writeConfig()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.window.webContents.setWindowOpenHandler((details) => {
|
this.window.webContents.setWindowOpenHandler((details) => {
|
||||||
shell.openExternal(details.url)
|
shell.openExternal(details.url)
|
||||||
return { action: 'deny' }
|
return { action: 'deny' }
|
||||||
})
|
})
|
||||||
|
|
||||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||||
this.window.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
this.window.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||||
} else {
|
} else {
|
||||||
@@ -69,23 +58,26 @@ class ControlWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public handleMessage() {
|
public handleMessage() {
|
||||||
// 控制窗口样式更新
|
// 样式变更
|
||||||
ipcMain.on('control.style.change', (_, args) => {
|
ipcMain.on('control.style.change', (_, args) => {
|
||||||
setStyles(args)
|
allConfig.setStyles(args)
|
||||||
if(captionWindow.window){
|
if(captionWindow.window){
|
||||||
sendStyles(captionWindow.window)
|
allConfig.sendStyles(captionWindow.window)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 样式重置
|
||||||
ipcMain.on('control.style.reset', () => {
|
ipcMain.on('control.style.reset', () => {
|
||||||
resetStyles()
|
allConfig.resetStyles()
|
||||||
if(captionWindow.window){
|
|
||||||
sendStyles(captionWindow.window)
|
|
||||||
}
|
|
||||||
if(this.window){
|
if(this.window){
|
||||||
sendStyles(this.window)
|
allConfig.sendStyles(this.window)
|
||||||
|
}
|
||||||
|
if(captionWindow.window){
|
||||||
|
allConfig.sendStyles(captionWindow.window)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 控制窗口请求创建字幕窗口
|
|
||||||
|
// 激活字幕窗口
|
||||||
ipcMain.on('control.captionWindow.activate', () => {
|
ipcMain.on('control.captionWindow.activate', () => {
|
||||||
if(!captionWindow.window){
|
if(!captionWindow.window){
|
||||||
captionWindow.createWindow()
|
captionWindow.createWindow()
|
||||||
@@ -94,37 +86,31 @@ class ControlWindow {
|
|||||||
captionWindow.window.show()
|
captionWindow.window.show()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 字幕引擎控制配置更新并启动引擎
|
|
||||||
|
// 字幕引擎配置更新
|
||||||
ipcMain.on('control.control.change', (_, args) => {
|
ipcMain.on('control.control.change', (_, args) => {
|
||||||
setControls(args)
|
allConfig.setControls(args)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 启动字幕引擎
|
// 启动字幕引擎
|
||||||
ipcMain.on('control.engine.start', () => {
|
ipcMain.on('control.engine.start', () => {
|
||||||
if(controls.engineEnabled){
|
if(allConfig.controls.engineEnabled){
|
||||||
this.window?.webContents.send('control.engine.already')
|
this.window?.webContents.send('control.engine.already')
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(
|
captionEngine.start()
|
||||||
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 并添加到本机环境变量')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 停止字幕引擎
|
// 停止字幕引擎
|
||||||
ipcMain.on('control.engine.stop', () => {
|
ipcMain.on('control.engine.stop', () => {
|
||||||
captionEngine.stop()
|
captionEngine.stop()
|
||||||
this.window?.webContents.send('control.engine.stopped')
|
this.window?.webContents.send('control.engine.stopped')
|
||||||
})
|
})
|
||||||
|
|
||||||
// 清空字幕记录
|
// 清空字幕记录
|
||||||
ipcMain.on('control.caption.clear', () => {
|
ipcMain.on('control.caption.clear', () => {
|
||||||
captionLog.splice(0)
|
allConfig.captionLog.splice(0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,4 +119,4 @@ class ControlWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const controlWindow = new ControlWindow()
|
export const controlWindow = new ControlWindow()
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { app, BrowserWindow } from 'electron'
|
import { app, BrowserWindow } from 'electron'
|
||||||
import { electronApp, optimizer } from '@electron-toolkit/utils'
|
import { electronApp, optimizer } from '@electron-toolkit/utils'
|
||||||
import { controlWindow } from './control'
|
import { controlWindow } from './ControlWindow'
|
||||||
import { captionWindow } from './caption'
|
import { captionWindow } from './CaptionWindow'
|
||||||
import { captionEngine, writeConfig } from './utils/config'
|
import { allConfig } from './utils/AllConfig'
|
||||||
|
import { captionEngine } from './utils/CaptionEngine'
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
electronApp.setAppUserModelId('com.himeditator.autocaption')
|
electronApp.setAppUserModelId('com.himeditator.autocaption')
|
||||||
@@ -23,9 +24,9 @@ app.whenReady().then(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('will-quit', async () => {
|
app.on('will-quit', async () => {
|
||||||
captionEngine.stop()
|
captionEngine.stop()
|
||||||
writeConfig()
|
allConfig.writeConfig()
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
export type UILanguage = "zh" | "en" | "ja"
|
||||||
|
|
||||||
export interface Styles {
|
export interface Styles {
|
||||||
fontFamily: string,
|
fontFamily: string,
|
||||||
fontSize: number,
|
fontSize: number,
|
||||||
@@ -22,10 +24,10 @@ export interface Controls {
|
|||||||
engineEnabled: boolean,
|
engineEnabled: boolean,
|
||||||
sourceLang: string,
|
sourceLang: string,
|
||||||
targetLang: string,
|
targetLang: string,
|
||||||
engine: string,
|
engine: 'gummy',
|
||||||
audio: 0 | 1,
|
audio: 0 | 1,
|
||||||
translation: boolean,
|
translation: boolean,
|
||||||
customized: boolean,
|
customized: boolean,
|
||||||
customizedApp: string,
|
customizedApp: string,
|
||||||
customizedCommand: string
|
customizedCommand: string
|
||||||
}
|
}
|
||||||
|
|||||||
106
src/main/utils/AllConfig.ts
Normal file
106
src/main/utils/AllConfig.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { Styles, CaptionItem, Controls } from '../types'
|
||||||
|
import { app, BrowserWindow } from 'electron'
|
||||||
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
const defaultStyles: Styles = {
|
||||||
|
fontFamily: 'sans-serif',
|
||||||
|
fontSize: 24,
|
||||||
|
fontColor: '#000000',
|
||||||
|
background: '#dbe2ef',
|
||||||
|
opacity: 80,
|
||||||
|
transDisplay: true,
|
||||||
|
transFontFamily: 'sans-serif',
|
||||||
|
transFontSize: 24,
|
||||||
|
transFontColor: '#000000'
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultControls: Controls = {
|
||||||
|
sourceLang: 'en',
|
||||||
|
targetLang: 'zh',
|
||||||
|
engine: 'gummy',
|
||||||
|
audio: 0,
|
||||||
|
engineEnabled: false,
|
||||||
|
translation: true,
|
||||||
|
customized: false,
|
||||||
|
customizedApp: '',
|
||||||
|
customizedCommand: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class AllConfig {
|
||||||
|
styles: Styles = {...defaultStyles};
|
||||||
|
controls: Controls = {...defaultControls};
|
||||||
|
captionLog: CaptionItem[] = [];
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
public readConfig() {
|
||||||
|
const configPath = path.join(app.getPath('userData'), 'config.json')
|
||||||
|
if(fs.existsSync(configPath)){
|
||||||
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||||
|
this.setStyles(config.styles)
|
||||||
|
this.setControls(config.controls)
|
||||||
|
console.log('[INFO] Read Config from:', configPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public writeConfig() {
|
||||||
|
const config = {
|
||||||
|
controls: this.controls,
|
||||||
|
styles: this.styles
|
||||||
|
}
|
||||||
|
const configPath = path.join(app.getPath('userData'), 'config.json')
|
||||||
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
|
||||||
|
console.log('[INFO] Write Config to:', configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
public setStyles(args: any) {
|
||||||
|
this.styles = {...args}
|
||||||
|
console.log('[INFO] Set Styles:', this.styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetStyles() {
|
||||||
|
this.setStyles(defaultStyles)
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendStyles(window: BrowserWindow) {
|
||||||
|
window.webContents.send('caption.style.set', this.styles)
|
||||||
|
console.log(`[INFO] Send Styles to #${window.id}:`, this.styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
public setControls(args: any) {
|
||||||
|
const engineEnabled = args.engineEnabled
|
||||||
|
this.controls = {...args}
|
||||||
|
this.controls.engineEnabled = engineEnabled
|
||||||
|
console.log('[INFO] Set Controls:', this.controls)
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendControls(window: BrowserWindow) {
|
||||||
|
window.webContents.send('control.control.set', this.controls)
|
||||||
|
console.log(`[INFO] Send Controls to #${window.id}:`, this.controls)
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateCaptionLog(log: CaptionItem) {
|
||||||
|
if(this.captionLog.length && this.captionLog[this.captionLog.length - 1].index === log.index) {
|
||||||
|
this.captionLog.splice(this.captionLog.length - 1, 1, log)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.captionLog.push(log)
|
||||||
|
}
|
||||||
|
for(const window of BrowserWindow.getAllWindows()){
|
||||||
|
this.sendCaptionLog(window, 'add')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendCaptionLog(window: BrowserWindow, command: 'add' | 'set') {
|
||||||
|
if(command === 'add'){
|
||||||
|
window.webContents.send(`both.log.add`, this.captionLog[this.captionLog.length - 1])
|
||||||
|
}
|
||||||
|
else if(command === 'set'){
|
||||||
|
window.webContents.send(`both.log.${command}`, this.captionLog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const allConfig = new AllConfig()
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
import { spawn, exec } from 'child_process'
|
import { spawn, exec } from 'child_process'
|
||||||
import { app, BrowserWindow } 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'
|
||||||
import { addCaptionLog, controls, sendControls } from './config'
|
import { controlWindow } from '../ControlWindow'
|
||||||
import { controlWindow } from '../control'
|
import { allConfig } from './AllConfig'
|
||||||
|
|
||||||
export class CaptionEngine {
|
export class CaptionEngine {
|
||||||
appPath: string = ''
|
appPath: string = ''
|
||||||
command: string[] = []
|
command: string[] = []
|
||||||
process: any | undefined
|
process: any | undefined
|
||||||
|
|
||||||
private getApp() {
|
private getApp(): boolean {
|
||||||
if (controls.customized && controls.customizedApp) {
|
if (allConfig.controls.customized && allConfig.controls.customizedApp) {
|
||||||
this.appPath = controls.customizedApp
|
this.appPath = allConfig.controls.customizedApp
|
||||||
this.command = [controls.customizedCommand]
|
this.command = [allConfig.controls.customizedCommand]
|
||||||
}
|
}
|
||||||
else if (controls.engine === 'gummy') {
|
else if (allConfig.controls.engine === 'gummy') {
|
||||||
controls.customized = false
|
allConfig.controls.customized = false
|
||||||
|
if(!process.env.DASHSCOPE_API_KEY) {
|
||||||
|
controlWindow.sendErrorMessage('没有检测到 DASHSCOPE_API_KEY 环境变量,如果要使用 gummy 引擎,需要在阿里云百炼平台获取 API Key 并添加到本机环境变量')
|
||||||
|
return false
|
||||||
|
}
|
||||||
let gummyName = ''
|
let gummyName = ''
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
gummyName = 'main-gummy.exe'
|
gummyName = 'main-gummy.exe'
|
||||||
@@ -25,7 +29,7 @@ export class CaptionEngine {
|
|||||||
gummyName = 'main-gummy'
|
gummyName = 'main-gummy'
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
controlWindow.sendErrorMessage('不支持的操作系统平台:' + process.platform)
|
controlWindow.sendErrorMessage('Unsupported platform: ' + process.platform)
|
||||||
throw new Error('Unsupported platform')
|
throw new Error('Unsupported platform')
|
||||||
}
|
}
|
||||||
if (is.dev) {
|
if (is.dev) {
|
||||||
@@ -41,20 +45,23 @@ export class CaptionEngine {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
this.command = []
|
this.command = []
|
||||||
this.command.push('-s', controls.sourceLang)
|
this.command.push('-s', allConfig.controls.sourceLang)
|
||||||
this.command.push('-t', controls.translation ? controls.targetLang : 'none')
|
this.command.push(
|
||||||
this.command.push('-a', controls.audio ? '1' : '0')
|
'-t', allConfig.controls.translation ?
|
||||||
|
allConfig.controls.targetLang : 'none'
|
||||||
|
)
|
||||||
|
this.command.push('-a', allConfig.controls.audio ? '1' : '0')
|
||||||
|
|
||||||
console.log('[INFO] Engine Path:', this.appPath)
|
console.log('[INFO] Engine Path:', this.appPath)
|
||||||
console.log('[INFO] Engine Command:', this.command)
|
console.log('[INFO] Engine Command:', this.command)
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(window: BrowserWindow) {
|
public start() {
|
||||||
if (this.process) {
|
if (this.process) { this.stop() }
|
||||||
this.stop();
|
if(!this.getApp()){ return }
|
||||||
}
|
|
||||||
this.getApp()
|
|
||||||
try {
|
try {
|
||||||
this.process = spawn(this.appPath, this.command)
|
this.process = spawn(this.appPath, this.command)
|
||||||
}
|
}
|
||||||
@@ -64,14 +71,14 @@ export class CaptionEngine {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[INFO] Caption Engine Started: ', {
|
console.log('[INFO] Caption Engine Started')
|
||||||
appPath: this.appPath,
|
|
||||||
command: this.command
|
|
||||||
})
|
|
||||||
|
|
||||||
controls.engineEnabled = true
|
allConfig.controls.engineEnabled = true
|
||||||
sendControls(window)
|
|
||||||
window.webContents.send('control.engine.started')
|
if(controlWindow.window){
|
||||||
|
allConfig.sendControls(controlWindow.window)
|
||||||
|
controlWindow.window.webContents.send('control.engine.started')
|
||||||
|
}
|
||||||
|
|
||||||
this.process.stdout.on('data', (data) => {
|
this.process.stdout.on('data', (data) => {
|
||||||
const lines = data.toString().split('\n');
|
const lines = data.toString().split('\n');
|
||||||
@@ -79,7 +86,7 @@ export class CaptionEngine {
|
|||||||
if (line.trim()) {
|
if (line.trim()) {
|
||||||
try {
|
try {
|
||||||
const caption = JSON.parse(line);
|
const caption = JSON.parse(line);
|
||||||
addCaptionLog(caption);
|
allConfig.updateCaptionLog(caption);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
controlWindow.sendErrorMessage('字幕引擎输出内容无法解析为 JSON 对象:' + e)
|
controlWindow.sendErrorMessage('字幕引擎输出内容无法解析为 JSON 对象:' + e)
|
||||||
console.error('[ERROR] Error parsing JSON:', e);
|
console.error('[ERROR] Error parsing JSON:', e);
|
||||||
@@ -96,8 +103,10 @@ export class CaptionEngine {
|
|||||||
this.process.on('close', (code: any) => {
|
this.process.on('close', (code: any) => {
|
||||||
console.log(`[INFO] Subprocess exited with code ${code}`);
|
console.log(`[INFO] Subprocess exited with code ${code}`);
|
||||||
this.process = undefined;
|
this.process = undefined;
|
||||||
controls.engineEnabled = false
|
allConfig.controls.engineEnabled = false
|
||||||
sendControls(window)
|
if(controlWindow.window){
|
||||||
|
allConfig.sendControls(controlWindow.window)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +116,7 @@ export class CaptionEngine {
|
|||||||
exec(`taskkill /pid ${this.process.pid} /t /f`, (error) => {
|
exec(`taskkill /pid ${this.process.pid} /t /f`, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
controlWindow.sendErrorMessage('字幕引擎进程关闭失败:' + error)
|
controlWindow.sendErrorMessage('字幕引擎进程关闭失败:' + error)
|
||||||
console.error(`[ERROR] Failed to kill process: ${error}`);
|
console.error(`[ERROR] Failed to kill process: ${error}`)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -115,8 +124,12 @@ export class CaptionEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.process = undefined;
|
this.process = undefined;
|
||||||
controls.engineEnabled = false;
|
allConfig.controls.engineEnabled = false;
|
||||||
console.log('[INFO] Caption engine process stopped');
|
console.log('[INFO] Caption engine process stopped')
|
||||||
if(controlWindow.window) sendControls(controlWindow.window);
|
if(controlWindow.window) {
|
||||||
|
allConfig.sendControls(controlWindow.window)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const captionEngine = new CaptionEngine()
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
import { Styles, CaptionItem, Controls } from '../types'
|
|
||||||
import { app, BrowserWindow } from 'electron'
|
|
||||||
import { CaptionEngine } from './engine'
|
|
||||||
import * as path from 'path'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
|
|
||||||
export const captionEngine = new CaptionEngine()
|
|
||||||
|
|
||||||
export const styles: Styles = {
|
|
||||||
fontFamily: 'sans-serif',
|
|
||||||
fontSize: 24,
|
|
||||||
fontColor: '#000000',
|
|
||||||
background: '#dbe2ef',
|
|
||||||
opacity: 80,
|
|
||||||
transDisplay: true,
|
|
||||||
transFontFamily: 'sans-serif',
|
|
||||||
transFontSize: 24,
|
|
||||||
transFontColor: '#000000'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const captionLog: CaptionItem[] = []
|
|
||||||
|
|
||||||
export const controls: Controls = {
|
|
||||||
sourceLang: 'en',
|
|
||||||
targetLang: 'zh',
|
|
||||||
engine: 'gummy',
|
|
||||||
audio: 0,
|
|
||||||
engineEnabled: false,
|
|
||||||
translation: true,
|
|
||||||
customized: false,
|
|
||||||
customizedApp: '',
|
|
||||||
customizedCommand: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setStyles(args: any) {
|
|
||||||
styles.fontFamily = args.fontFamily
|
|
||||||
styles.fontSize = args.fontSize
|
|
||||||
styles.fontColor = args.fontColor
|
|
||||||
styles.background = args.background
|
|
||||||
styles.opacity = args.opacity
|
|
||||||
styles.transDisplay = args.transDisplay
|
|
||||||
styles.transFontFamily = args.transFontFamily
|
|
||||||
styles.transFontSize = args.transFontSize
|
|
||||||
styles.transFontColor = args.transFontColor
|
|
||||||
console.log('[INFO] Set Styles:', styles)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resetStyles() {
|
|
||||||
setStyles({
|
|
||||||
fontFamily: 'sans-serif',
|
|
||||||
fontSize: 24,
|
|
||||||
fontColor: '#000000',
|
|
||||||
background: '#dbe2ef',
|
|
||||||
opacity: 80,
|
|
||||||
transDisplay: true,
|
|
||||||
transFontFamily: 'sans-serif',
|
|
||||||
transFontSize: 24,
|
|
||||||
transFontColor: '#000000'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendStyles(window: BrowserWindow) {
|
|
||||||
window.webContents.send('caption.style.set', styles)
|
|
||||||
console.log(`[INFO] Send Styles to #${window.id}:`, styles)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendCaptionLog(window: BrowserWindow, command: string) {
|
|
||||||
if(command === 'add'){
|
|
||||||
window.webContents.send(`both.log.add`, captionLog[captionLog.length - 1])
|
|
||||||
}
|
|
||||||
else if(command === 'set'){
|
|
||||||
window.webContents.send(`both.log.${command}`, captionLog)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addCaptionLog(log: CaptionItem) {
|
|
||||||
if(captionLog.length && captionLog[captionLog.length - 1].index === log.index) {
|
|
||||||
captionLog.splice(captionLog.length - 1, 1, log)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
captionLog.push(log)
|
|
||||||
}
|
|
||||||
for(const window of BrowserWindow.getAllWindows()){
|
|
||||||
sendCaptionLog(window, 'add')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setControls(args: any) {
|
|
||||||
controls.sourceLang = args.sourceLang
|
|
||||||
controls.targetLang = args.targetLang
|
|
||||||
controls.engine = args.engine
|
|
||||||
controls.audio = args.audio
|
|
||||||
controls.translation = args.translation
|
|
||||||
controls.customized = args.customized
|
|
||||||
controls.customizedApp = args.customizedApp
|
|
||||||
controls.customizedCommand = args.customizedCommand
|
|
||||||
console.log('[INFO] Set Controls:', controls)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendControls(window: BrowserWindow) {
|
|
||||||
window.webContents.send('control.control.set', controls)
|
|
||||||
console.log(`[INFO] Send Controls to #${window.id}:`, controls)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readConfig() {
|
|
||||||
const configPath = path.join(app.getPath('userData'), 'config.json')
|
|
||||||
if(fs.existsSync(configPath)){
|
|
||||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
|
||||||
setStyles(config.styles)
|
|
||||||
setControls(config.controls)
|
|
||||||
console.log('[INFO] Read Config from:', configPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function writeConfig() {
|
|
||||||
const config = {
|
|
||||||
controls: controls,
|
|
||||||
styles: styles
|
|
||||||
}
|
|
||||||
const configPath = path.join(app.getPath('userData'), 'config.json')
|
|
||||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
||||||
console.log('[INFO] Write Config to:', configPath)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
class configSave {
|
|
||||||
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user