feat(gummy): 支持通过设置添加 API KEY

- 更新 main-gummy.py 以支持 API KEY 参数
- 修改 electron-builder.yml 以调整 Gummy 可执行文件路径
This commit is contained in:
himeditator mac
2025-07-08 21:05:43 +08:00
parent 7e953db6bd
commit cbbaaa95a3
17 changed files with 99 additions and 53 deletions

View File

@@ -4,6 +4,7 @@ from dashscope.audio.asr import (
TranslationResult, TranslationResult,
TranslationRecognizerRealtime TranslationRecognizerRealtime
) )
import dashscope
from datetime import datetime from datetime import datetime
import json import json
import sys import sys
@@ -77,7 +78,9 @@ class GummyTranslator:
source: 源语言代码字符串zh, en, ja 等) source: 源语言代码字符串zh, en, ja 等)
target: 目标语言代码字符串zh, en, ja 等) target: 目标语言代码字符串zh, en, ja 等)
""" """
def __init__(self, rate, source, target): def __init__(self, rate, source, target, api_key):
if api_key:
dashscope.api_key = api_key
self.translator = TranslationRecognizerRealtime( self.translator = TranslationRecognizerRealtime(
model = "gummy-realtime-v1", model = "gummy-realtime-v1",
format = "pcm", format = "pcm",

View File

@@ -14,14 +14,14 @@ from audioprcs import mergeChunkChannels
from audio2text import InvalidParameter, GummyTranslator from audio2text import InvalidParameter, GummyTranslator
def convert_audio_to_text(s_lang, t_lang, audio_type, chunk_rate): def convert_audio_to_text(s_lang, t_lang, audio_type, chunk_rate, api_key):
sys.stdout.reconfigure(line_buffering=True) # type: ignore sys.stdout.reconfigure(line_buffering=True) # type: ignore
stream = AudioStream(audio_type, chunk_rate) stream = AudioStream(audio_type, chunk_rate)
if t_lang == 'none': if t_lang == 'none':
gummy = GummyTranslator(stream.RATE, s_lang, None) gummy = GummyTranslator(stream.RATE, s_lang, None, api_key)
else: else:
gummy = GummyTranslator(stream.RATE, s_lang, t_lang) gummy = GummyTranslator(stream.RATE, s_lang, t_lang, api_key)
stream.openStream() stream.openStream()
gummy.start() gummy.start()
@@ -47,10 +47,12 @@ if __name__ == "__main__":
parser.add_argument('-t', '--target_language', default='zh', help='Target language code') parser.add_argument('-t', '--target_language', default='zh', help='Target language code')
parser.add_argument('-a', '--audio_type', default=0, help='Audio stream source: 0 for output audio stream, 1 for input audio stream') parser.add_argument('-a', '--audio_type', default=0, help='Audio stream source: 0 for output audio stream, 1 for input audio stream')
parser.add_argument('-c', '--chunk_rate', default=20, help='The number of audio stream chunks collected per second.') parser.add_argument('-c', '--chunk_rate', default=20, help='The number of audio stream chunks collected per second.')
parser.add_argument('-k', '--api_key', default='', help='API KEY for Gummy model')
args = parser.parse_args() args = parser.parse_args()
convert_audio_to_text( convert_audio_to_text(
args.source_language, args.source_language,
args.target_language, args.target_language,
int(args.audio_type), int(args.audio_type),
int(args.chunk_rate) int(args.chunk_rate),
args.api_key
) )

View File

@@ -10,8 +10,8 @@ files:
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}' - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
extraResources: extraResources:
from: ./caption-engine/dist/main-gummy.exe from: ./caption-engine/dist/main-gummy
to: ./caption-engine/dist/main-gummy.exe to: ./caption-engine/main-gummy
asarUnpack: asarUnpack:
- resources/** - resources/**
win: win:

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "auto-caption", "name": "auto-caption",
"version": "0.2.1", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "auto-caption", "name": "auto-caption",
"version": "0.2.1", "version": "0.3.0",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/preload": "^3.0.1",

View File

@@ -1,7 +1,7 @@
{ {
"name": "auto-caption", "name": "auto-caption",
"productName": "Auto Caption", "productName": "Auto Caption",
"version": "0.2.1", "version": "0.3.0",
"description": "A cross-platform subtitle display software.", "description": "A cross-platform subtitle display software.",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "himeditator", "author": "himeditator",

View File

@@ -1,5 +1,5 @@
export default { export default {
"gummy.env.missing": "DASHSCOPE_API_KEY environment variable not detected. To use the gummy engine, you need to obtain an API Key from Alibaba Cloud's Bailian platform and add it to your local environment variables.", "gummy.key.missing": "API KEY is not set, and the DASHSCOPE_API_KEY environment variable is not detected. To use the gummy engine, you need to obtain an API KEY from the Alibaba Cloud Bailian platform and add it to the settings or configure it in the local environment variables.",
"platform.unsupported": "Unsupported platform: ", "platform.unsupported": "Unsupported platform: ",
"engine.start.error": "Caption engine failed to start: ", "engine.start.error": "Caption engine failed to start: ",
"engine.output.parse.error": "Unable to parse caption engine output as a JSON object: ", "engine.output.parse.error": "Unable to parse caption engine output as a JSON object: ",

View File

@@ -1,5 +1,5 @@
export default { export default {
"gummy.env.missing": "DASHSCOPE_API_KEY 環境変数検出されませんでした。Gummy エンジンを使用するには、Alibaba Cloud の百煉プラットフォームから API Key を取得し、ローカル環境変数に追加する必要があります。", "gummy.key.missing": "API KEY が設定されておらず、DASHSCOPE_API_KEY 環境変数検出されていません。Gummy エンジンを使用するには、Alibaba Cloud Bailian プラットフォームから API KEY を取得し、設定に追加するか、ローカル環境変数に設定する必要があります。",
"platform.unsupported": "サポートされていないプラットフォーム: ", "platform.unsupported": "サポートされていないプラットフォーム: ",
"engine.start.error": "字幕エンジンの起動に失敗しました: ", "engine.start.error": "字幕エンジンの起動に失敗しました: ",
"engine.output.parse.error": "字幕エンジンの出力を JSON オブジェクトとして解析できませんでした: ", "engine.output.parse.error": "字幕エンジンの出力を JSON オブジェクトとして解析できませんでした: ",

View File

@@ -1,5 +1,5 @@
export default { export default {
"gummy.env.missing": "没有检测到 DASHSCOPE_API_KEY 环境变量如果要使用 gummy 引擎,需要在阿里云百炼平台获取 API Key 并添加到本机环境变量", "gummy.key.missing": "没有设置 API KEY也没有检测到 DASHSCOPE_API_KEY 环境变量如果要使用 gummy 引擎,需要在阿里云百炼平台获取 API KEY并在添加到设置中或者配置到本机环境变量",
"platform.unsupported": "不支持的平台:", "platform.unsupported": "不支持的平台:",
"engine.start.error": "字幕引擎启动失败:", "engine.start.error": "字幕引擎启动失败:",
"engine.output.parse.error": "字幕引擎输出内容无法解析为 JSON 对象:", "engine.output.parse.error": "字幕引擎输出内容无法解析为 JSON 对象:",

View File

@@ -9,6 +9,7 @@ export interface Controls {
engine: 'gummy', engine: 'gummy',
audio: 0 | 1, audio: 0 | 1,
translation: boolean, translation: boolean,
API_KEY: string,
customized: boolean, customized: boolean,
customizedApp: string, customizedApp: string,
customizedCommand: string customizedCommand: string

View File

@@ -26,6 +26,7 @@ const defaultControls: Controls = {
engine: 'gummy', engine: 'gummy',
audio: 0, audio: 0,
engineEnabled: false, engineEnabled: false,
API_KEY: '',
translation: true, translation: true,
customized: false, customized: false,
customizedApp: '', customizedApp: '',
@@ -82,7 +83,7 @@ class AllConfig {
} }
} }
public setStyles(args: Styles) { public setStyles(args: Object) {
for(let key in this.styles) { for(let key in this.styles) {
if(key in args) { if(key in args) {
this.styles[key] = args[key] this.styles[key] = args[key]
@@ -100,7 +101,7 @@ class AllConfig {
console.log(`[INFO] Send Styles to #${window.id}:`, this.styles) console.log(`[INFO] Send Styles to #${window.id}:`, this.styles)
} }
public setControls(args: Controls) { public setControls(args: Object) {
const engineEnabled = this.controls.engineEnabled const engineEnabled = this.controls.engineEnabled
for(let key in this.controls){ for(let key in this.controls){
if(key in args) { if(key in args) {

View File

@@ -19,8 +19,8 @@ export class CaptionEngine {
} }
else if (allConfig.controls.engine === 'gummy') { else if (allConfig.controls.engine === 'gummy') {
allConfig.controls.customized = false allConfig.controls.customized = false
if(!process.env.DASHSCOPE_API_KEY) { if(!allConfig.controls.API_KEY && !process.env.DASHSCOPE_API_KEY) {
controlWindow.sendErrorMessage(i18n('gummy.env.missing')) controlWindow.sendErrorMessage(i18n('gummy.key.missing'))
return false return false
} }
let gummyName = '' let gummyName = ''
@@ -42,8 +42,7 @@ export class CaptionEngine {
} }
else { else {
this.appPath = path.join( this.appPath = path.join(
process.resourcesPath, process.resourcesPath, 'caption-engine', gummyName
'caption-engine', 'dist', gummyName
) )
} }
this.command = [] this.command = []
@@ -53,6 +52,9 @@ export class CaptionEngine {
allConfig.controls.targetLang : 'none' allConfig.controls.targetLang : 'none'
) )
this.command.push('-a', allConfig.controls.audio ? '1' : '0') this.command.push('-a', allConfig.controls.audio ? '1' : '0')
if(allConfig.controls.API_KEY) {
this.command.push('-k', allConfig.controls.API_KEY)
}
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)
@@ -61,7 +63,7 @@ export class CaptionEngine {
} }
public start() { public start() {
if (this.processStatus!== 'stopped') { if (this.processStatus !== 'stopped') {
return return
} }
if(!this.getApp()){ return } if(!this.getApp()){ return }
@@ -122,7 +124,7 @@ export class CaptionEngine {
public stop() { public stop() {
if(this.processStatus !== 'running') return if(this.processStatus !== 'running') return
if (this.process) { if (this.process.pid) {
console.log('[INFO] Trying to stop process, PID:', this.process.pid) console.log('[INFO] Trying to stop process, PID:', this.process.pid)
let cmd = `kill ${this.process.pid}`; let cmd = `kill ${this.process.pid}`;
if (process.platform === "win32") { if (process.platform === "win32") {
@@ -135,6 +137,17 @@ export class CaptionEngine {
} }
}) })
} }
else {
this.process = undefined;
allConfig.controls.engineEnabled = false
if(controlWindow.window){
allConfig.sendControls(controlWindow.window)
controlWindow.window.webContents.send('control.engine.stopped')
}
this.processStatus = 'stopped'
console.log('[INFO] Process PID undefined, caption engine process stopped')
return
}
this.processStatus = 'stopping' this.processStatus = 'stopping'
console.log('[INFO] Caption engine process stopping') console.log('[INFO] Caption engine process stopping')
} }

View File

@@ -43,36 +43,51 @@
<a-switch v-model:checked="currentTranslation" /> <a-switch v-model:checked="currentTranslation" />
<span style="display:inline-block;width:20px;"></span> <span style="display:inline-block;width:20px;"></span>
<div style="display: inline-block;"> <div style="display: inline-block;">
<span class="switch-label">{{ $t('engine.customEngine') }}</span> <span class="switch-label">{{ $t('engine.showMore') }}</span>
<a-switch v-model:checked="currentCustomized" /> <a-switch v-model:checked="showMore" />
</div> </div>
</div> </div>
<div v-show="currentCustomized"> <a-card size="small" :title="$t('engine.custom.title')" v-show="showMore">
<a-card size="small" :title="$t('engine.custom.title')"> <div class="input-item">
<template #extra> <span class="input-label">{{ $t('engine.apikey') }}</span>
<a-popover> <a-input
<template #content> class="input-area"
<p class="customize-note">{{ $t('engine.custom.note') }}</p> type="password"
</template> v-model:value="currentAPI_KEY"
<a><InfoCircleOutlined />{{ $t('engine.custom.attention') }}</a> />
</a-popover> </div>
</template> <div class="input-item">
<div class="input-item"> <span style="margin-right:5px;">{{ $t('engine.customEngine') }}</span>
<span class="input-label">{{ $t('engine.custom.app') }}</span> <a-switch v-model:checked="currentCustomized" />
<a-input </div>
class="input-area" <div v-show="currentCustomized">
v-model:value="currentCustomizedApp" <a-card size="small" :title="$t('engine.custom.title')">
></a-input> <template #extra>
</div> <a-popover>
<div class="input-item"> <template #content>
<span class="input-label">{{ $t('engine.custom.command') }}</span> <p class="customize-note">{{ $t('engine.custom.note') }}</p>
<a-input </template>
class="input-area" <a><InfoCircleOutlined />{{ $t('engine.custom.attention') }}</a>
v-model:value="currentCustomizedCommand" </a-popover>
></a-input> </template>
</div> <div class="input-item">
</a-card> <span class="input-label">{{ $t('engine.custom.app') }}</span>
</div> <a-input
class="input-area"
v-model:value="currentCustomizedApp"
></a-input>
</div>
<div class="input-item">
<span class="input-label">{{ $t('engine.custom.command') }}</span>
<a-input
class="input-area"
v-model:value="currentCustomizedCommand"
></a-input>
</div>
</a-card>
</div>
</a-card>
</a-card> </a-card>
<div style="height: 20px;"></div> <div style="height: 20px;"></div>
</template> </template>
@@ -86,6 +101,7 @@ import { InfoCircleOutlined } from '@ant-design/icons-vue';
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
const showMore = ref(false)
const engineControl = useEngineControlStore() const engineControl = useEngineControlStore()
const { platform, captionEngine, audioType, changeSignal } = storeToRefs(engineControl) const { platform, captionEngine, audioType, changeSignal } = storeToRefs(engineControl)
@@ -95,7 +111,7 @@ const currentTargetLang = ref('zh')
const currentEngine = ref<'gummy'>('gummy') const currentEngine = ref<'gummy'>('gummy')
const currentAudio = ref<0 | 1>(0) const currentAudio = ref<0 | 1>(0)
const currentTranslation = ref<boolean>(false) const currentTranslation = ref<boolean>(false)
const currentAPI_KEY = ref<string>('')
const currentCustomized = ref<boolean>(false) const currentCustomized = ref<boolean>(false)
const currentCustomizedApp = ref('') const currentCustomizedApp = ref('')
const currentCustomizedCommand = ref('') const currentCustomizedCommand = ref('')
@@ -115,7 +131,7 @@ function applyChange(){
engineControl.engine = currentEngine.value engineControl.engine = currentEngine.value
engineControl.audio = currentAudio.value engineControl.audio = currentAudio.value
engineControl.translation = currentTranslation.value engineControl.translation = currentTranslation.value
engineControl.API_KEY = currentAPI_KEY.value
engineControl.customized = currentCustomized.value engineControl.customized = currentCustomized.value
engineControl.customizedApp = currentCustomizedApp.value engineControl.customizedApp = currentCustomizedApp.value
engineControl.customizedCommand = currentCustomizedCommand.value engineControl.customizedCommand = currentCustomizedCommand.value
@@ -134,7 +150,7 @@ function cancelChange(){
currentEngine.value = engineControl.engine currentEngine.value = engineControl.engine
currentAudio.value = engineControl.audio currentAudio.value = engineControl.audio
currentTranslation.value = engineControl.translation currentTranslation.value = engineControl.translation
currentAPI_KEY.value = engineControl.API_KEY
currentCustomized.value = engineControl.customized currentCustomized.value = engineControl.customized
currentCustomizedApp.value = engineControl.customizedApp currentCustomizedApp.value = engineControl.customizedApp
currentCustomizedCommand.value = engineControl.customizedCommand currentCustomizedCommand.value = engineControl.customizedCommand

View File

@@ -46,6 +46,8 @@ export default {
"systemOutput": "System Audio Output (Speaker)", "systemOutput": "System Audio Output (Speaker)",
"systemInput": "System Audio Input (Microphone)", "systemInput": "System Audio Input (Microphone)",
"enableTranslation": "Translation", "enableTranslation": "Translation",
"showMore": "More Settings",
"apikey": "API KEY",
"customEngine": "Custom Engine", "customEngine": "Custom Engine",
custom: { custom: {
"title": "Custom Caption Engine", "title": "Custom Caption Engine",

View File

@@ -46,6 +46,8 @@ export default {
"systemOutput": "システムオーディオ出力(スピーカー)", "systemOutput": "システムオーディオ出力(スピーカー)",
"systemInput": "システムオーディオ入力(マイク)", "systemInput": "システムオーディオ入力(マイク)",
"enableTranslation": "翻訳", "enableTranslation": "翻訳",
"showMore": "詳細設定",
"apikey": "API KEY",
"customEngine": "カスタムエンジン", "customEngine": "カスタムエンジン",
custom: { custom: {
"title": "カスタムキャプションエンジン", "title": "カスタムキャプションエンジン",

View File

@@ -46,6 +46,8 @@ export default {
"systemOutput": "系统音频输出(扬声器)", "systemOutput": "系统音频输出(扬声器)",
"systemInput": "系统音频输入(麦克风)", "systemInput": "系统音频输入(麦克风)",
"enableTranslation": "启用翻译", "enableTranslation": "启用翻译",
"showMore": "更多设置",
"apikey": "API KEY",
"customEngine": "自定义引擎", "customEngine": "自定义引擎",
custom: { custom: {
"title": "自定义字幕引擎", "title": "自定义字幕引擎",

View File

@@ -16,7 +16,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
const captionEngine = ref(engines[useGeneralSettingStore().uiLanguage]) const captionEngine = ref(engines[useGeneralSettingStore().uiLanguage])
const audioType = ref(audioTypes[useGeneralSettingStore().uiLanguage]) const audioType = ref(audioTypes[useGeneralSettingStore().uiLanguage])
const API_KEY = ref<string>('')
const engineEnabled = ref(false) const engineEnabled = ref(false)
const sourceLang = ref<string>('en') const sourceLang = ref<string>('en')
const targetLang = ref<string>('zh') const targetLang = ref<string>('zh')
@@ -37,6 +37,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
engine: engine.value, engine: engine.value,
audio: audio.value, audio: audio.value,
translation: translation.value, translation: translation.value,
API_KEY: API_KEY.value,
customized: customized.value, customized: customized.value,
customizedApp: customizedApp.value, customizedApp: customizedApp.value,
customizedCommand: customizedCommand.value customizedCommand: customizedCommand.value
@@ -51,6 +52,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
audio.value = controls.audio audio.value = controls.audio
engineEnabled.value = controls.engineEnabled engineEnabled.value = controls.engineEnabled
translation.value = controls.translation translation.value = controls.translation
API_KEY.value = controls.API_KEY
customized.value = controls.customized customized.value = controls.customized
customizedApp.value = controls.customizedApp customizedApp.value = controls.customizedApp
customizedCommand.value = controls.customizedCommand customizedCommand.value = controls.customizedCommand
@@ -108,6 +110,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
engine, // 字幕引擎 engine, // 字幕引擎
audio, // 选择音频 audio, // 选择音频
translation, // 是否启用翻译 translation, // 是否启用翻译
API_KEY, // API KEY
customized, // 是否使用自定义字幕引擎 customized, // 是否使用自定义字幕引擎
customizedApp, // 自定义字幕引擎的应用程序 customizedApp, // 自定义字幕引擎的应用程序
customizedCommand, // 自定义字幕引擎的命令 customizedCommand, // 自定义字幕引擎的命令

View File

@@ -9,6 +9,7 @@ export interface Controls {
engine: 'gummy', engine: 'gummy',
audio: 0 | 1, audio: 0 | 1,
translation: boolean, translation: boolean,
API_KEY: string,
customized: boolean, customized: boolean,
customizedApp: string, customizedApp: string,
customizedCommand: string customizedCommand: string