feat(renderer): 在用户界面中添加新功能的设置

- 添加录音功能,可保存为 WAV 文件
- 优化字幕引擎设置界面,支持更多配置选项
- 更新多语言翻译,增加模型下载链接等信息
This commit is contained in:
himeditator
2025-09-07 14:35:18 +08:00
parent 6bff978b88
commit 4abd6d0808
12 changed files with 351 additions and 123 deletions

View File

@@ -1,6 +1,7 @@
import wave
import argparse
import threading
import datetime
from utils import stdout, stdout_cmd
from utils import shared_data, start_server
from utils import merge_chunk_channels, resample_chunk_mono
@@ -10,36 +11,45 @@ from audio2text import SosvRecognizer
from sysaudio import AudioStream
def audio_recording(stream: AudioStream, resample: bool, save = False, path = ''):
def audio_recording(stream: AudioStream, resample: bool, record = False, path = ''):
global shared_data
stream.open_stream()
wf = None
if save:
if path != '':
full_name = ''
if record:
if path != '' and path[-1] != '/':
path += '/'
wf = wave.open(f'{path}record.wav', 'wb')
cur_dt = datetime.datetime.now()
name = cur_dt.strftime("audio-%Y-%m-%dT%H-%M-%S")
full_name = f'{path}{name}.wav'
wf = wave.open(full_name, 'wb')
wf.setnchannels(stream.CHANNELS)
wf.setsampwidth(stream.SAMP_WIDTH)
wf.setframerate(stream.CHUNK_RATE)
wf.setframerate(stream.RATE)
stdout_cmd("info", "Audio recording...")
while shared_data.status == 'running':
raw_chunk = stream.read_chunk()
if save: wf.writeframes(raw_chunk) # type: ignore
if record: wf.writeframes(raw_chunk) # type: ignore
if raw_chunk is None: continue
if resample:
chunk = resample_chunk_mono(raw_chunk, stream.CHANNELS, stream.RATE, 16000)
else:
chunk = merge_chunk_channels(raw_chunk, stream.CHANNELS)
shared_data.chunk_queue.put(chunk)
if save: wf.close() # type: ignore
if record:
stdout_cmd("info", f"Audio saved to {full_name}")
wf.close() # type: ignore
stream.close_stream_signal()
def main_gummy(s: str, t: str, a: int, c: int, k: str):
def main_gummy(s: str, t: str, a: int, c: int, k: str, r: bool, rp: str):
"""
Parameters:
s: Source language
t: Target language
k: Aliyun Bailian API key
r: Whether to record the audio
rp: Path to save the recorded audio
"""
stream = AudioStream(a, c)
if t == 'none':
@@ -50,7 +60,7 @@ def main_gummy(s: str, t: str, a: int, c: int, k: str):
engine.start()
stream_thread = threading.Thread(
target=audio_recording,
args=(stream, False),
args=(stream, False, r, rp),
daemon=True
)
stream_thread.start()
@@ -61,7 +71,7 @@ def main_gummy(s: str, t: str, a: int, c: int, k: str):
engine.stop()
def main_vosk(a: int, c: int, vosk: str, t: str, tm: str, omn: str):
def main_vosk(a: int, c: int, vosk: str, t: str, tm: str, omn: str, r: bool, rp: str):
"""
Parameters:
a: Audio source: 0 for output, 1 for input
@@ -70,6 +80,8 @@ def main_vosk(a: int, c: int, vosk: str, t: str, tm: str, omn: str):
t: Target language
tm: Translation model type, ollama or google
omn: Ollama model name
r: Whether to record the audio
rp: Path to save the recorded audio
"""
stream = AudioStream(a, c)
if t == 'none':
@@ -80,7 +92,7 @@ def main_vosk(a: int, c: int, vosk: str, t: str, tm: str, omn: str):
engine.start()
stream_thread = threading.Thread(
target=audio_recording,
args=(stream, True),
args=(stream, True, r, rp),
daemon=True
)
stream_thread.start()
@@ -91,7 +103,7 @@ def main_vosk(a: int, c: int, vosk: str, t: str, tm: str, omn: str):
engine.stop()
def main_sosv(a: int, c: int, sosv: str, s: str, t: str, tm: str, omn: str):
def main_sosv(a: int, c: int, sosv: str, s: str, t: str, tm: str, omn: str, r: bool, rp: str):
"""
Parameters:
a: Audio source: 0 for output, 1 for input
@@ -101,6 +113,8 @@ def main_sosv(a: int, c: int, sosv: str, s: str, t: str, tm: str, omn: str):
t: Target language
tm: Translation model type, ollama or google
omn: Ollama model name
r: Whether to record the audio
rp: Path to save the recorded audio
"""
stream = AudioStream(a, c)
if t == 'none':
@@ -111,7 +125,7 @@ def main_sosv(a: int, c: int, sosv: str, s: str, t: str, tm: str, omn: str):
engine.start()
stream_thread = threading.Thread(
target=audio_recording,
args=(stream, True),
args=(stream, True, r, rp),
daemon=True
)
stream_thread.start()
@@ -130,6 +144,8 @@ if __name__ == "__main__":
parser.add_argument('-c', '--chunk_rate', default=10, help='Number of audio stream chunks collected per second')
parser.add_argument('-p', '--port', default=0, help='The port to run the server on, 0 for no server')
parser.add_argument('-t', '--target_language', default='zh', help='Target language code, "none" for no translation')
parser.add_argument('-r', '--record', default=0, help='Whether to record the audio, 0 for no recording, 1 for recording')
parser.add_argument('-rp', '--record_path', default='', help='Path to save the recorded audio')
# gummy and sosv
parser.add_argument('-s', '--source_language', default='auto', help='Source language code')
# gummy only
@@ -154,7 +170,9 @@ if __name__ == "__main__":
args.target_language,
int(args.audio_type),
int(args.chunk_rate),
args.api_key
args.api_key,
True if int(args.record) == 1 else False,
args.record_path
)
elif args.caption_engine == 'vosk':
main_vosk(
@@ -163,7 +181,9 @@ if __name__ == "__main__":
args.vosk_model,
args.target_language,
args.translation_model,
args.ollama_name
args.ollama_name,
True if int(args.record) == 1 else False,
args.record_path
)
elif args.caption_engine == 'sosv':
main_sosv(
@@ -173,7 +193,9 @@ if __name__ == "__main__":
args.source_language,
args.target_language,
args.translation_model,
args.ollama_name
args.ollama_name,
True if int(args.record) == 1 else False,
args.record_path
)
else:
raise ValueError('Invalid caption engine specified.')

View File

@@ -11,8 +11,11 @@ export interface Controls {
engine: string,
audio: 0 | 1,
translation: boolean,
recording: boolean,
API_KEY: string,
modelPath: string,
voskModelPath: string,
sosvModelPath: string,
recordingPath: string,
customized: boolean,
customizedApp: string,
customizedCommand: string,

View File

@@ -6,12 +6,18 @@ import { Log } from './Log'
import { app, BrowserWindow } from 'electron'
import * as path from 'path'
import * as fs from 'fs'
import os from 'os'
interface CaptionTranslation {
time_s: string,
translation: string
}
function getDesktopPath() {
const homeDir = os.homedir()
return path.join(homeDir, 'Desktop')
}
const defaultStyles: Styles = {
lineBreak: 1,
fontFamily: 'sans-serif',
@@ -42,8 +48,11 @@ const defaultControls: Controls = {
audio: 0,
engineEnabled: false,
API_KEY: '',
modelPath: '',
voskModelPath: '',
sosvModelPath: '',
recordingPath: getDesktopPath(),
translation: true,
recording: false,
customized: false,
customizedApp: '',
customizedCommand: '',

View File

@@ -63,8 +63,11 @@ export class CaptionEngine {
this.appPath = path.join(process.resourcesPath, 'engine', 'main')
}
}
this.command.push('-a', allConfig.controls.audio ? '1' : '0')
if(allConfig.controls.recording) {
this.command.push('-r', '1')
this.command.push('-rp', allConfig.controls.recordingPath)
}
this.port = Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024
this.command.push('-p', this.port.toString())
this.command.push(
@@ -81,7 +84,14 @@ export class CaptionEngine {
}
else if(allConfig.controls.engine === 'vosk'){
this.command.push('-e', 'vosk')
this.command.push('-vosk', `"${allConfig.controls.modelPath}"`)
this.command.push('-vosk', `"${allConfig.controls.voskModelPath}"`)
this.command.push('-tm', allConfig.controls.transModel)
this.command.push('-omn', allConfig.controls.ollamaName)
}
else if(allConfig.controls.engine === 'sosv'){
this.command.push('-e', 'sosv')
this.command.push('-s', allConfig.controls.sourceLang)
this.command.push('-sosv', `"${allConfig.controls.sosvModelPath}"`)
this.command.push('-tm', allConfig.controls.transModel)
this.command.push('-omn', allConfig.controls.ollamaName)
}

View File

@@ -19,7 +19,7 @@
:disabled="currentEngine === 'vosk'"
class="input-area"
v-model:value="currentSourceLang"
:options="langList"
:options="sLangList"
></a-select>
</div>
<div class="input-item">
@@ -27,7 +27,7 @@
<a-select
class="input-area"
v-model:value="currentTargetLang"
:options="langList.filter((item) => item.value !== 'auto')"
:options="tLangList"
></a-select>
</div>
<div class="input-item" v-if="transModel">
@@ -65,9 +65,13 @@
<a-switch v-model:checked="currentTranslation" />
<span style="display:inline-block;width:10px;"></span>
<div style="display: inline-block;">
<span class="switch-label">{{ $t('engine.customEngine') }}</span>
<a-switch v-model:checked="currentCustomized" />
<span class="switch-label">{{ $t('engine.enableRecording') }}</span>
<a-switch v-model:checked="currentRecording" />
</div>
</div>
<div class="input-item">
<span class="input-label">{{ $t('engine.customEngine') }}</span>
<a-switch v-model:checked="currentCustomized" />
<span style="display:inline-block;width:10px;"></span>
<div style="display: inline-block;">
<span class="switch-label">{{ $t('engine.showMore') }}</span>
@@ -105,6 +109,9 @@
<a-popover placement="right">
<template #content>
<p class="label-hover-info">{{ $t('engine.apikeyInfo') }}</p>
<p><a href="https://bailian.console.aliyun.com" target="_blank">
https://bailian.console.aliyun.com
</a></p>
</template>
<span class="input-label info-label"
:style="{color: uiColor}"
@@ -119,21 +126,67 @@
<div class="input-item">
<a-popover placement="right">
<template #content>
<p class="label-hover-info">{{ $t('engine.modelPathInfo') }}</p>
<p class="label-hover-info">{{ $t('engine.voskModelPathInfo') }}</p>
<p class="label-hover-info">
<a href="https://alphacephei.com/vosk/models" target="_blank">Vosk {{ $t('engine.modelDownload') }}</a>
</p>
</template>
<span class="input-label info-label"
:style="{color: uiColor}"
>{{ $t('engine.modelPath') }}</span>
>{{ $t('engine.voskModelPath') }}</span>
</a-popover>
<span
class="input-folder"
:style="{color: uiColor}"
@click="selectFolderPath"
@click="selectFolderPath('vosk')"
><span><FolderOpenOutlined /></span></span>
<a-input
class="input-area"
style="width:calc(100% - 140px);"
v-model:value="currentModelPath"
v-model:value="currentVoskModelPath"
/>
</div>
<div class="input-item">
<a-popover placement="right">
<template #content>
<p class="label-hover-info">{{ $t('engine.sosvModelPathInfo') }}</p>
<p class="label-hover-info">
<a href="https://github.com/HiMeditator/auto-caption/releases/tag/sosv-model" target="_blank">SOSV {{ $t('engine.modelDownload') }}</a>
</p>
</template>
<span class="input-label info-label"
:style="{color: uiColor}"
>{{ $t('engine.sosvModelPath') }}</span>
</a-popover>
<span
class="input-folder"
:style="{color: uiColor}"
@click="selectFolderPath('sosv')"
><span><FolderOpenOutlined /></span></span>
<a-input
class="input-area"
style="width:calc(100% - 140px);"
v-model:value="currentSosvModelPath"
/>
</div>
<div class="input-item">
<a-popover placement="right">
<template #content>
<p class="label-hover-info">{{ $t('engine.recordingPathInfo') }}</p>
</template>
<span class="input-label info-label"
:style="{color: uiColor}"
>{{ $t('engine.recordingPath') }}</span>
</a-popover>
<span
class="input-folder"
:style="{color: uiColor}"
@click="selectFolderPath('rec')"
><span><FolderOpenOutlined /></span></span>
<a-input
class="input-area"
style="width:calc(100% - 140px);"
v-model:value="currentRecordingPath"
/>
</div>
<div class="input-item">
@@ -183,19 +236,31 @@ const currentTargetLang = ref('zh')
const currentEngine = ref<string>('gummy')
const currentAudio = ref<0 | 1>(0)
const currentTranslation = ref<boolean>(true)
const currentRecording = ref<boolean>(false)
const currentTransModel = ref('ollama')
const currentOllamaName = ref('')
const currentAPI_KEY = ref<string>('')
const currentModelPath = ref<string>('')
const currentVoskModelPath = ref<string>('')
const currentSosvModelPath = ref<string>('')
const currentRecordingPath = ref<string>('')
const currentCustomized = ref<boolean>(false)
const currentCustomizedApp = ref('')
const currentCustomizedCommand = ref('')
const currentStartTimeoutSeconds = ref<number>(30)
const langList = computed(() => {
const sLangList = computed(() => {
for(let item of captionEngine.value){
if(item.value === currentEngine.value) {
return item.languages
return item.languages.filter(item => item.type <= 0)
}
}
return []
})
const tLangList = computed(() => {
for(let item of captionEngine.value){
if(item.value === currentEngine.value) {
return item.languages.filter(item => item.type >= 0)
}
}
return []
@@ -231,8 +296,11 @@ function applyChange(){
engineControl.engine = currentEngine.value
engineControl.audio = currentAudio.value
engineControl.translation = currentTranslation.value
engineControl.recording = currentRecording.value
engineControl.API_KEY = currentAPI_KEY.value
engineControl.modelPath = currentModelPath.value
engineControl.voskModelPath = currentVoskModelPath.value
engineControl.sosvModelPath = currentSosvModelPath.value
engineControl.recordingPath = currentRecordingPath.value
engineControl.customized = currentCustomized.value
engineControl.customizedApp = currentCustomizedApp.value
engineControl.customizedCommand = currentCustomizedCommand.value
@@ -255,18 +323,26 @@ function cancelChange(){
currentEngine.value = engineControl.engine
currentAudio.value = engineControl.audio
currentTranslation.value = engineControl.translation
currentRecording.value = engineControl.recording
currentAPI_KEY.value = engineControl.API_KEY
currentModelPath.value = engineControl.modelPath
currentVoskModelPath.value = engineControl.voskModelPath
currentSosvModelPath.value = engineControl.sosvModelPath
currentRecordingPath.value = engineControl.recordingPath
currentCustomized.value = engineControl.customized
currentCustomizedApp.value = engineControl.customizedApp
currentCustomizedCommand.value = engineControl.customizedCommand
currentStartTimeoutSeconds.value = engineControl.startTimeoutSeconds
}
function selectFolderPath() {
function selectFolderPath(type: 'vosk' | 'sosv' | 'rec') {
window.electron.ipcRenderer.invoke('control.folder.select').then((folderPath) => {
if(!folderPath) return
currentModelPath.value = folderPath
if(type == 'vosk')
currentVoskModelPath.value = folderPath
else if(type == 'sosv')
currentSosvModelPath.value = folderPath
else if(type == 'rec')
currentRecordingPath.value = folderPath
})
}
@@ -285,7 +361,7 @@ watch(currentEngine, (val) => {
currentTargetLang.value = 'zh-cn'
}
}
else if(val == 'gummy'){
else{
currentSourceLang.value = 'auto'
currentTargetLang.value = useGeneralSettingStore().uiLanguage
}

View File

@@ -175,7 +175,13 @@ function openCaptionWindow() {
function startEngine() {
pending.value = true
isStarting.value = true
if(engineControl.engine === 'vosk' && engineControl.modelPath.trim() === '') {
if(engineControl.engine === 'vosk' && engineControl.voskModelPath.trim() === '') {
engineControl.emptyModelPathErr()
pending.value = false
isStarting.value = false
return
}
if(engineControl.engine === 'sosv' && engineControl.sosvModelPath.trim() === '') {
engineControl.emptyModelPathErr()
pending.value = false
isStarting.value = false

View File

@@ -1,35 +1,58 @@
// type: -1 仅为源语言0 两者皆可, 1 仅为翻译语言
export const engines = {
zh: [
{
value: 'gummy',
label: '云端 - 阿里云 - Gummy',
label: '云端 / 阿里云 / Gummy',
languages: [
{ value: 'auto', label: '自动检测' },
{ value: 'en', label: '英语' },
{ value: 'zh', label: '中文' },
{ value: 'ja', label: '日语' },
{ value: 'ko', label: '韩语' },
{ value: 'de', label: '德语' },
{ value: 'fr', label: '法语' },
{ value: 'ru', label: '俄语' },
{ value: 'es', label: '西班牙语' },
{ value: 'it', label: '意大利语' },
{ value: 'auto', type: -1, label: '自动检测' },
{ value: 'en', type: 0, label: '英语' },
{ value: 'zh', type: 0, label: '中文' },
{ value: 'ja', type: 0, label: '日语' },
{ value: 'ko', type: 0, label: '韩语' },
{ value: 'de', type: -1, label: '德语' },
{ value: 'fr', type: -1, label: '法语' },
{ value: 'ru', type: -1, label: '俄语' },
{ value: 'es', type: -1, label: '西班牙语' },
{ value: 'it', type: -1, label: '意大利语' },
{ value: 'yue', type: -1, label: '粤语' },
]
},
{
value: 'vosk',
label: '本地 - Vosk',
label: '本地 / Vosk',
languages: [
{ value: 'auto', label: '需要自行配置模型' },
{ value: 'en', label: '英语' },
{ value: 'zh-cn', label: '中文' },
{ value: 'ja', label: '日语' },
{ value: 'ko', label: '韩语' },
{ value: 'de', label: '德语' },
{ value: 'fr', label: '法语' },
{ value: 'ru', label: '俄语' },
{ value: 'es', label: '西班牙语' },
{ value: 'it', label: '意大利语' },
{ value: 'auto', type: -1, label: '需要自行配置模型' },
{ value: 'en', type: 1, label: '英语' },
{ value: 'zh-cn', type: 1, label: '中文' },
{ value: 'ja', type: 1, label: '日语' },
{ value: 'ko', type: 1, label: '韩语' },
{ value: 'de', type: 1, label: '德语' },
{ value: 'fr', type: 1, label: '法语' },
{ value: 'ru', type: 1, label: '俄语' },
{ value: 'es', type: 1, label: '西班牙语' },
{ value: 'it', type: 1, label: '意大利语' },
],
transModel: [
{ value: 'ollama', label: 'Ollama 本地模型' },
{ value: 'google', label: 'Google API 调用' },
]
},
{
value: 'sosv',
label: '本地 / SOSV',
languages: [
{ value: 'auto', type: -1, label: '自动检测' },
{ value: 'en', type: 0, label: '英语' },
{ value: 'zh', type: 0, label: '中文' },
{ value: 'ja', type: 0, label: '日语' },
{ value: 'ko', type: 0, label: '韩语' },
{ value: 'yue', type: -1, label: '粤语' },
{ value: 'de', type: 1, label: '德语' },
{ value: 'fr', type: 1, label: '法语' },
{ value: 'ru', type: 1, label: '俄语' },
{ value: 'es', type: 1, label: '西班牙语' },
{ value: 'it', type: 1, label: '意大利语' },
],
transModel: [
{ value: 'ollama', label: 'Ollama 本地模型' },
@@ -40,34 +63,56 @@ export const engines = {
en: [
{
value: 'gummy',
label: 'Cloud - Alibaba Cloud - Gummy',
label: 'Cloud / Alibaba Cloud / Gummy',
languages: [
{ value: 'auto', label: 'Auto Detect' },
{ value: 'en', label: 'English' },
{ value: 'zh', label: 'Chinese' },
{ value: 'ja', label: 'Japanese' },
{ value: 'ko', label: 'Korean' },
{ value: 'de', label: 'German' },
{ value: 'fr', label: 'French' },
{ value: 'ru', label: 'Russian' },
{ value: 'es', label: 'Spanish' },
{ value: 'it', label: 'Italian' },
{ value: 'auto', type: -1, label: 'Auto Detect' },
{ value: 'en', type: 0, label: 'English' },
{ value: 'zh', type: 0, label: 'Chinese' },
{ value: 'ja', type: 0, label: 'Japanese' },
{ value: 'ko', type: 0, label: 'Korean' },
{ value: 'de', type: -1, label: 'German' },
{ value: 'fr', type: -1, label: 'French' },
{ value: 'ru', type: -1, label: 'Russian' },
{ value: 'es', type: -1, label: 'Spanish' },
{ value: 'it', type: -1, label: 'Italian' },
{ value: 'yue', type: -1, label: 'Cantonese' },
]
},
{
value: 'vosk',
label: 'Local - Vosk',
label: 'Local / Vosk',
languages: [
{ value: 'auto', label: 'Model needs to be configured manually' },
{ value: 'en', label: 'English' },
{ value: 'zh-cn', label: 'Chinese' },
{ value: 'ja', label: 'Japanese' },
{ value: 'ko', label: 'Korean' },
{ value: 'de', label: 'German' },
{ value: 'fr', label: 'French' },
{ value: 'ru', label: 'Russian' },
{ value: 'es', label: 'Spanish' },
{ value: 'it', label: 'Italian' },
{ value: 'auto', type: -1, label: 'Model needs to be configured manually' },
{ value: 'en', type: 1, label: 'English' },
{ value: 'zh-cn', type: 1, label: 'Chinese' },
{ value: 'ja', type: 1, label: 'Japanese' },
{ value: 'ko', type: 1, label: 'Korean' },
{ value: 'de', type: 1, label: 'German' },
{ value: 'fr', type: 1, label: 'French' },
{ value: 'ru', type: 1, label: 'Russian' },
{ value: 'es', type: 1, label: 'Spanish' },
{ value: 'it', type: 1, label: 'Italian' },
],
transModel: [
{ value: 'ollama', label: 'Ollama Local Model' },
{ value: 'google', label: 'Google API Call' },
]
},
{
value: 'sosv',
label: 'Local / SOSV',
languages: [
{ value: 'auto', type: -1, label: 'Auto Detect' },
{ value: 'en', type: 0, label: 'English' },
{ value: 'zh-cn', type: 0, label: 'Chinese' },
{ value: 'ja', type: 0, label: 'Japanese' },
{ value: 'ko', type: 0, label: 'Korean' },
{ value: 'yue', type: -1, label: 'Cantonese' },
{ value: 'de', type: 1, label: 'German' },
{ value: 'fr', type: 1, label: 'French' },
{ value: 'ru', type: 1, label: 'Russian' },
{ value: 'es', type: 1, label: 'Spanish' },
{ value: 'it', type: 1, label: 'Italian' },
],
transModel: [
{ value: 'ollama', label: 'Ollama Local Model' },
@@ -78,34 +123,56 @@ export const engines = {
ja: [
{
value: 'gummy',
label: 'クラウド - アリババクラウド - Gummy',
label: 'クラウド / アリババクラウド / Gummy',
languages: [
{ value: 'auto', label: '自動検出' },
{ value: 'en', label: '英語' },
{ value: 'zh', label: '中国語' },
{ value: 'ja', label: '日本語' },
{ value: 'ko', label: '韓国語' },
{ value: 'de', label: 'ドイツ語' },
{ value: 'fr', label: 'フランス語' },
{ value: 'ru', label: 'ロシア語' },
{ value: 'es', label: 'スペイン語' },
{ value: 'it', label: 'イタリア語' },
{ value: 'auto', type: -1, label: '自動検出' },
{ value: 'en', type: 0, label: '英語' },
{ value: 'zh', type: 0, label: '中国語' },
{ value: 'ja', type: 0, label: '日本語' },
{ value: 'ko', type: 0, label: '韓国語' },
{ value: 'de', type: -1, label: 'ドイツ語' },
{ value: 'fr', type: -1, label: 'フランス語' },
{ value: 'ru', type: -1, label: 'ロシア語' },
{ value: 'es', type: -1, label: 'スペイン語' },
{ value: 'it', type: -1, label: 'イタリア語' },
{ value: 'yue', type: -1, label: '広東語' },
]
},
{
value: 'vosk',
label: 'ローカル - Vosk',
label: 'ローカル / Vosk',
languages: [
{ value: 'auto', label: 'モデルを手動で設定する必要があります' },
{ value: 'en', label: '英語' },
{ value: 'zh-cn', label: '中国語' },
{ value: 'ja', label: '日本語' },
{ value: 'ko', label: '韓国語' },
{ value: 'de', label: 'ドイツ語' },
{ value: 'fr', label: 'フランス語' },
{ value: 'ru', label: 'ロシア語' },
{ value: 'es', label: 'スペイン語' },
{ value: 'it', label: 'イタリア語' },
{ value: 'auto', type: -1, label: 'モデルを手動で設定する必要があります' },
{ value: 'en', type: 1, label: '英語' },
{ value: 'zh-cn', type: 1, label: '中国語' },
{ value: 'ja', type: 1, label: '日本語' },
{ value: 'ko', type: 1, label: '韓国語' },
{ value: 'de', type: 1, label: 'ドイツ語' },
{ value: 'fr', type: 1, label: 'フランス語' },
{ value: 'ru', type: 1, label: 'ロシア語' },
{ value: 'es', type: 1, label: 'スペイン語' },
{ value: 'it', type: 1, label: 'イタリア語' },
],
transModel: [
{ value: 'ollama', label: 'Ollama ローカルモデル' },
{ value: 'google', label: 'Google API 呼び出し' },
]
},
{
value: 'sosv',
label: 'ローカル / SOSV',
languages: [
{ value: 'auto', type: -1, label: '自動検出' },
{ value: 'en', type: 0, label: '英語' },
{ value: 'zh-cn', type: 0, label: '中国語' },
{ value: 'ja', type: 0, label: '日本語' },
{ value: 'ko', type: 0, label: '韓国語' },
{ value: 'yue', type: -1, label: '広東語' },
{ value: 'de', type: 1, label: 'ドイツ語' },
{ value: 'fr', type: 1, label: 'フランス語' },
{ value: 'ru', type: 1, label: 'ロシア語' },
{ value: 'es', type: 1, label: 'スペイン語' },
{ value: 'it', type: 1, label: 'イタリア語' },
],
transModel: [
{ value: 'ollama', label: 'Ollama ローカルモデル' },

View File

@@ -18,7 +18,7 @@ export default {
"args": ", command arguments: ",
"pidInfo": ", caption engine process PID: ",
"empty": "Model Path is Empty",
"emptyInfo": "The Vosk model path is empty. Please set the Vosk model path in the additional settings of the subtitle engine settings.",
"emptyInfo": "The model path for the selected local model is empty. Please download the corresponding model first, and then configure the model path in [Caption Engine Settings > More Settings].",
"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",
@@ -57,15 +57,22 @@ export default {
"systemOutput": "System Audio Output (Speaker)",
"systemInput": "System Audio Input (Microphone)",
"enableTranslation": "Translation",
"enableRecording": "Enable Recording",
"showMore": "More Settings",
"apikey": "API KEY",
"modelPath": "Model Path",
"voskModelPath": "Vosk Path",
"sosvModelPath": "SOSV Path",
"recordingPath": "Save Path",
"startTimeout": "Timeout",
"seconds": "seconds",
"apikeyInfo": "API KEY required for the Gummy subtitle engine, which needs to be obtained from the Alibaba Cloud Bailing platform. For more details, see the project user manual.",
"modelPathInfo": "The folder path of the model required by the Vosk subtitle engine. You need to download the required model to your local machine in advance. For more details, see the project user manual.",
"voskModelPathInfo": "The folder path of the model required by the Vosk subtitle engine. You need to download the required model to your local machine in advance. For more details, see the project user manual.",
"sosvModelPathInfo": "The folder path of the model required by the SOSV subtitle engine. You need to download the required model to your local machine in advance. For more details, see the project user manual.",
"recordingPathInfo": "The path to save recording files, requiring a folder path. The software will automatically name the recording file and save it as .wav file.",
"modelDownload": "Model Download Link",
"startTimeoutInfo": "Caption engine startup timeout duration. Engine will be forcefully stopped if startup exceeds this time. Recommended range: 10-120 seconds.",
"customEngine": "Custom Engine",
"customEngine": "Custom Eng",
custom: {
"title": "Custom Caption Engine",
"attention": "Attention",

View File

@@ -18,7 +18,7 @@ export default {
"args": "、コマンド引数:",
"pidInfo": "、字幕エンジンプロセス PID",
"empty": "モデルパスが空です",
"emptyInfo": "Vosk モデルのパスが空です。字幕エンジン設定の追加設定で Vosk モデルのパスを設定してください。",
"emptyInfo": "選択されたローカルモデルに対応するモデルパスが空です。まず対応するモデルをダウンロードし、次に【字幕エンジン設定 > 詳細設定】でモデルのパスを設定してください。",
"stopped": "字幕エンジンが停止しました",
"stoppedInfo": "字幕エンジンが停止しました。再起動するには「字幕エンジンを開始」ボタンをクリックしてください。",
"error": "エラーが発生しました",
@@ -57,15 +57,21 @@ export default {
"systemOutput": "システムオーディオ出力(スピーカー)",
"systemInput": "システムオーディオ入力(マイク)",
"enableTranslation": "翻訳",
"enableRecording": "録音",
"showMore": "詳細設定",
"apikey": "API KEY",
"modelPath": "モデルパス",
"voskModelPath": "Voskパス",
"sosvModelPath": "SOSVパス",
"recordingPath": "保存パス",
"startTimeout": "時間制限",
"seconds": "秒",
"apikeyInfo": "Gummy 字幕エンジンに必要な API KEY は、アリババクラウド百煉プラットフォームから取得する必要があります。詳細情報はプロジェクトのユーザーマニュアルをご覧ください。",
"modelPathInfo": "Vosk 字幕エンジンに必要なモデルのフォルダパスです。必要なモデルを事前にローカルマシンにダウンロードする必要があります。詳細情報はプロジェクトのユーザーマニュアルをご覧ください。",
"voskModelPathInfo": "Vosk 字幕エンジンに必要なモデルのフォルダパスです。必要なモデルを事前にローカルマシンにダウンロードする必要があります。詳細情報はプロジェクトのユーザーマニュアルをご覧ください。",
"sosvModelPathInfo": "SOSV 字幕エンジンに必要なモデルのフォルダパスです。必要なモデルを事前にローカルマシンにダウンロードする必要があります。詳細情報はプロジェクトのユーザーマニュアルをご覧ください。",
"recordingPathInfo": "録音ファイルの保存パスで、フォルダパスを指定する必要があります。ソフトウェアが自動的に録音ファイルに名前を付けて .wav ファイルとして保存します。",
"modelDownload": "モデルダウンロードリンク",
"startTimeoutInfo": "字幕エンジンの起動タイムアウト時間です。この時間を超えると自動的に強制停止されます。10-120秒の範囲で設定することを推奨します。",
"customEngine": "カスタムエンジン",
"customEngine": "カスタム",
custom: {
"title": "カスタムキャプションエンジン",
"attention": "注意事項",
@@ -110,7 +116,7 @@ export default {
"cpu": "CPU 使用率",
"mem": "メモリ使用量",
"elapsed": "稼働時間",
"customized": "カスタマイズ済み",
"customized": "カスタ",
"status": "エンジン状態",
"started": "開始済み",
"stopped": "未開始",

View File

@@ -18,7 +18,7 @@ export default {
"args": ",命令参数:",
"pidInfo": ",字幕引擎进程 PID",
"empty": "模型路径为空",
"emptyInfo": "Vosk 模型模型路径为空,请在字幕引擎设置更多设置中设置 Vosk 模型的路径。",
"emptyInfo": "选择的本地模型对应的模型路径为空。请先下载对应模型,然后在【字幕引擎设置 > 更多设置】中配置对应模型的路径。",
"stopped": "字幕引擎停止",
"stoppedInfo": "字幕引擎已经停止,可点击“启动字幕引擎”按钮重新启动",
"error": "发生错误",
@@ -57,13 +57,19 @@ export default {
"systemOutput": "系统音频输出(扬声器)",
"systemInput": "系统音频输入(麦克风)",
"enableTranslation": "启用翻译",
"enableRecording": "启用录制",
"showMore": "更多设置",
"apikey": "API KEY",
"modelPath": "模型路径",
"voskModelPath": "Vosk路径",
"sosvModelPath": "SOSV路径",
"recordingPath": "保存路径",
"startTimeout": "启动超时",
"seconds": "秒",
"apikeyInfo": "Gummy 字幕引擎需要的 API KEY需要在阿里云百炼平台获取。详细信息见项目用户手册。",
"modelPathInfo": "Vosk 字幕引擎需要的模型的文件夹路径,需要提前下载需要的模型到本地。信息详情见项目用户手册。",
"voskModelPathInfo": "Vosk 字幕引擎需要的模型的文件夹路径,需要提前下载需要的模型到本地。信息详情见项目用户手册。",
"sosvModelPathInfo": "SOSV 字幕引擎需要的模型的文件夹路径,需要提前下载需要的模型到本地。信息详情见项目用户手册。",
"recordingPathInfo": "录音文件保存路径,需要提供文件夹路径。软件会自动命名录音文件并保存为 .wav 文件。",
"modelDownload": "模型下载地址",
"startTimeoutInfo": "字幕引擎启动超时时间,超过此时间将自动强制停止。建议设置为 10-120 秒之间。",
"customEngine": "自定义引擎",
custom: {

View File

@@ -24,8 +24,11 @@ export const useEngineControlStore = defineStore('engineControl', () => {
const engine = ref<string>('gummy')
const audio = ref<0 | 1>(0)
const translation = ref<boolean>(true)
const recording = ref<boolean>(false)
const API_KEY = ref<string>('')
const modelPath = ref<string>('')
const voskModelPath = ref<string>('')
const sosvModelPath = ref<string>('')
const recordingPath = ref<string>('')
const customized = ref<boolean>(false)
const customizedApp = ref<string>('')
const customizedCommand = ref<string>('')
@@ -44,8 +47,11 @@ export const useEngineControlStore = defineStore('engineControl', () => {
engine: engine.value,
audio: audio.value,
translation: translation.value,
recording: recording.value,
API_KEY: API_KEY.value,
modelPath: modelPath.value,
voskModelPath: voskModelPath.value,
sosvModelPath: sosvModelPath.value,
recordingPath: recordingPath.value,
customized: customized.value,
customizedApp: customizedApp.value,
customizedCommand: customizedCommand.value,
@@ -78,8 +84,11 @@ export const useEngineControlStore = defineStore('engineControl', () => {
audio.value = controls.audio
engineEnabled.value = controls.engineEnabled
translation.value = controls.translation
recording.value = controls.recording
API_KEY.value = controls.API_KEY
modelPath.value = controls.modelPath
voskModelPath.value = controls.voskModelPath
sosvModelPath.value = controls.sosvModelPath
recordingPath.value = controls.recordingPath
customized.value = controls.customized
customizedApp.value = controls.customizedApp
customizedCommand.value = controls.customizedCommand
@@ -89,9 +98,10 @@ export const useEngineControlStore = defineStore('engineControl', () => {
function emptyModelPathErr() {
notification.open({
placement: 'topLeft',
message: t('noti.empty'),
description: t('noti.emptyInfo')
description: t('noti.emptyInfo'),
duration: null,
icon: () => h(ExclamationCircleOutlined, { style: 'color: #ff4d4f' })
});
}
@@ -139,12 +149,15 @@ export const useEngineControlStore = defineStore('engineControl', () => {
sourceLang, // 源语言
targetLang, // 目标语言
transModel, // 翻译模型
ollamaName, // Ollama 模型
ollamaName, // Ollama 模型
engine, // 字幕引擎
audio, // 选择音频
translation, // 是否启用翻译
recording, // 是否启用录音
API_KEY, // API KEY
modelPath, // vosk 模型路径
voskModelPath, // vosk 模型路径
sosvModelPath, // sosv 模型路径
recordingPath, // 录音保存路径
customized, // 是否使用自定义字幕引擎
customizedApp, // 自定义字幕引擎的应用程序
customizedCommand, // 自定义字幕引擎的命令

View File

@@ -11,8 +11,11 @@ export interface Controls {
engine: string,
audio: 0 | 1,
translation: boolean,
recording: boolean,
API_KEY: string,
modelPath: string,
voskModelPath: string,
sosvModelPath: string,
recordingPath: string,
customized: boolean,
customizedApp: string,
customizedCommand: string,