feat(engine): 添加 Vosk 本地离线引擎支持

- 新增 Vosk 引擎配置和识别逻辑
- 更新用户界面,增加 Vosk 引擎选项和模型路径设置
- 更新依赖,添加 vosk 库
This commit is contained in:
himeditator
2025-07-09 19:53:30 +08:00
parent f97b885411
commit 1c29fd5adc
19 changed files with 389 additions and 41 deletions

View File

@@ -6,10 +6,11 @@ export interface Controls {
engineEnabled: boolean,
sourceLang: string,
targetLang: string,
engine: 'gummy',
engine: string,
audio: 0 | 1,
translation: boolean,
API_KEY: string,
modelPath: string,
customized: boolean,
customizedApp: string,
customizedCommand: string

View File

@@ -34,6 +34,7 @@ const defaultControls: Controls = {
audio: 0,
engineEnabled: false,
API_KEY: '',
modelPath: '',
translation: true,
customized: false,
customizedApp: '',

View File

@@ -13,26 +13,20 @@ export class CaptionEngine {
processStatus: 'running' | 'stopping' | 'stopped' = 'stopped'
private getApp(): boolean {
allConfig.controls.customized = false
if (allConfig.controls.customized && allConfig.controls.customizedApp) {
this.appPath = allConfig.controls.customizedApp
this.command = [allConfig.controls.customizedCommand]
allConfig.controls.customized = true
}
else if (allConfig.controls.engine === 'gummy') {
allConfig.controls.customized = false
if(!allConfig.controls.API_KEY && !process.env.DASHSCOPE_API_KEY) {
controlWindow.sendErrorMessage(i18n('gummy.key.missing'))
return false
}
let gummyName = ''
let gummyName = 'main-gummy'
if (process.platform === 'win32') {
gummyName = 'main-gummy.exe'
}
else if (process.platform === 'darwin' || process.platform === 'linux') {
gummyName = 'main-gummy'
}
else {
controlWindow.sendErrorMessage(i18n('platform.unsupported') + process.platform)
throw new Error(i18n('platform.unsupported'))
gummyName += '.exe'
}
if (is.dev) {
this.appPath = path.join(
@@ -55,10 +49,29 @@ export class CaptionEngine {
if(allConfig.controls.API_KEY) {
this.command.push('-k', allConfig.controls.API_KEY)
}
console.log('[INFO] Engine Path:', this.appPath)
console.log('[INFO] Engine Command:', this.command)
}
else if(allConfig.controls.engine === 'vosk'){
let voskName = 'main-vosk'
if (process.platform === 'win32') {
voskName += '.exe'
}
if (is.dev) {
this.appPath = path.join(
app.getAppPath(),
'caption-engine', 'dist', voskName
)
}
else {
this.appPath = path.join(
process.resourcesPath, 'caption-engine', voskName
)
}
this.command = []
this.command.push('-a', allConfig.controls.audio ? '1' : '0')
this.command.push('-m', `"${allConfig.controls.modelPath}"`)
}
console.log('[INFO] Engine Path:', this.appPath)
console.log('[INFO] Engine Command:', this.command)
return true
}

View File

@@ -16,6 +16,7 @@
<div class="input-item">
<span class="input-label">{{ $t('engine.transLang') }}</span>
<a-select
:disabled="currentEngine === 'vosk'"
class="input-area"
v-model:value="currentTargetLang"
:options="langList.filter((item) => item.value !== 'auto')"
@@ -47,7 +48,8 @@
<a-switch v-model:checked="showMore" />
</div>
</div>
<a-card size="small" :title="$t('engine.custom.title')" v-show="showMore">
<a-card size="small" :title="$t('engine.showMore')" v-show="showMore">
<div class="input-item">
<span class="input-label">{{ $t('engine.apikey') }}</span>
<a-input
@@ -56,6 +58,13 @@
v-model:value="currentAPI_KEY"
/>
</div>
<div class="input-item">
<span class="input-label">{{ $t('engine.modelPath') }}</span>
<a-input
class="input-area"
v-model:value="currentModelPath"
/>
</div>
<div class="input-item">
<span style="margin-right:5px;">{{ $t('engine.customEngine') }}</span>
<a-switch v-model:checked="currentCustomized" />
@@ -85,9 +94,8 @@
></a-input>
</div>
</a-card>
</div>
</div>
</a-card>
</a-card>
<div style="height: 20px;"></div>
</template>
@@ -95,6 +103,7 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useGeneralSettingStore } from '@renderer/stores/generalSetting'
import { useEngineControlStore } from '@renderer/stores/engineControl'
import { notification } from 'ant-design-vue'
import { InfoCircleOutlined } from '@ant-design/icons-vue';
@@ -108,10 +117,11 @@ const { platform, captionEngine, audioType, changeSignal } = storeToRefs(engineC
const currentSourceLang = ref('auto')
const currentTargetLang = ref('zh')
const currentEngine = ref<'gummy'>('gummy')
const currentEngine = ref<string>('gummy')
const currentAudio = ref<0 | 1>(0)
const currentTranslation = ref<boolean>(false)
const currentAPI_KEY = ref<string>('')
const currentModelPath = ref<string>('')
const currentCustomized = ref<boolean>(false)
const currentCustomizedApp = ref('')
const currentCustomizedCommand = ref('')
@@ -132,6 +142,7 @@ function applyChange(){
engineControl.audio = currentAudio.value
engineControl.translation = currentTranslation.value
engineControl.API_KEY = currentAPI_KEY.value
engineControl.modelPath = currentModelPath.value
engineControl.customized = currentCustomized.value
engineControl.customizedApp = currentCustomizedApp.value
engineControl.customizedCommand = currentCustomizedCommand.value
@@ -151,6 +162,7 @@ function cancelChange(){
currentAudio.value = engineControl.audio
currentTranslation.value = engineControl.translation
currentAPI_KEY.value = engineControl.API_KEY
currentModelPath.value = engineControl.modelPath
currentCustomized.value = engineControl.customized
currentCustomizedApp.value = engineControl.customizedApp
currentCustomizedCommand.value = engineControl.customizedCommand
@@ -162,6 +174,17 @@ watch(changeSignal, (val) => {
engineControl.changeSignal = false;
}
})
watch(currentEngine, (val) => {
if(val == 'vosk'){
currentSourceLang.value = 'auto'
currentTargetLang.value = ''
}
else if(val == 'gummy'){
currentSourceLang.value = 'auto'
currentTargetLang.value = useGeneralSettingStore().uiLanguage
}
})
</script>
<style scoped>

View File

@@ -16,6 +16,13 @@ export const engines = {
{ value: 'it', label: '意大利语' },
]
},
{
value: 'vosk',
label: '本地 - Vosk',
languages: [
{ value: 'auto', label: '需要自行配置模型' },
]
}
],
en: [
{
@@ -34,6 +41,13 @@ export const engines = {
{ value: 'it', label: 'Italian' },
]
},
{
value: 'vosk',
label: 'Local - Vosk',
languages: [
{ value: 'auto', label: 'Model needs to be configured manually' },
]
}
],
ja: [
{
@@ -52,6 +66,13 @@ export const engines = {
{ value: 'it', label: 'イタリア語' },
]
},
{
value: 'vosk',
label: 'ローカル - Vosk',
languages: [
{ value: 'auto', label: 'モデルを手動で設定する必要があります' },
]
}
]
}

View File

@@ -48,6 +48,7 @@ export default {
"enableTranslation": "启用翻译",
"showMore": "更多设置",
"apikey": "API KEY",
"modelPath": "模型路径",
"customEngine": "自定义引擎",
custom: {
"title": "自定义字幕引擎",

View File

@@ -16,13 +16,14 @@ export const useEngineControlStore = defineStore('engineControl', () => {
const captionEngine = ref(engines[useGeneralSettingStore().uiLanguage])
const audioType = ref(audioTypes[useGeneralSettingStore().uiLanguage])
const API_KEY = ref<string>('')
const engineEnabled = ref(false)
const sourceLang = ref<string>('en')
const targetLang = ref<string>('zh')
const engine = ref<'gummy'>('gummy')
const engine = ref<string>('gummy')
const audio = ref<0 | 1>(0)
const translation = ref<boolean>(true)
const API_KEY = ref<string>('')
const modelPath = ref<string>('')
const customized = ref<boolean>(false)
const customizedApp = ref<string>('')
const customizedCommand = ref<string>('')
@@ -38,6 +39,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
audio: audio.value,
translation: translation.value,
API_KEY: API_KEY.value,
modelPath: modelPath.value,
customized: customized.value,
customizedApp: customizedApp.value,
customizedCommand: customizedCommand.value
@@ -53,6 +55,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
engineEnabled.value = controls.engineEnabled
translation.value = controls.translation
API_KEY.value = controls.API_KEY
modelPath.value = controls.modelPath
customized.value = controls.customized
customizedApp.value = controls.customizedApp
customizedCommand.value = controls.customizedCommand
@@ -102,7 +105,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
return {
platform, // 系统平台
captionEngine, // 字幕引擎
captionEngine, // 字幕引擎列表
audioType, // 音频类型
engineEnabled, // 字幕引擎是否启用
sourceLang, // 源语言
@@ -111,6 +114,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
audio, // 选择音频
translation, // 是否启用翻译
API_KEY, // API KEY
modelPath, // vosk 模型路径
customized, // 是否使用自定义字幕引擎
customizedApp, // 自定义字幕引擎的应用程序
customizedCommand, // 自定义字幕引擎的命令

View File

@@ -6,10 +6,11 @@ export interface Controls {
engineEnabled: boolean,
sourceLang: string,
targetLang: string,
engine: 'gummy',
engine: string,
audio: 0 | 1,
translation: boolean,
API_KEY: string,
modelPath: string,
customized: boolean,
customizedApp: string,
customizedCommand: string