feat功能): 完善字幕引擎并添加字幕记录导出功能

- 丰富了 README.md 文件,介绍了字幕引擎的原理和构建方法
- 更新了 .gitignore 文件,排除了 build 目录
- 移除了 python-prototype 和 python-subprocess 目录下的无用代码
- 添加了字幕记录导出功能,用户可以将字幕数据导出为 JSON 文件
- 调整了字幕控制面板,移除了未使用的 Whisper 引擎选项
This commit is contained in:
himeditator
2025-06-21 20:35:49 +08:00
parent c446f846bd
commit 7030aaaae3
8 changed files with 81 additions and 138 deletions

3
.gitignore vendored
View File

@@ -5,4 +5,5 @@ out
.eslintcache
*.log*
__pycache__
subenv
subenv
build

View File

@@ -17,7 +17,10 @@
## ✨ 特性
暂无
- 丰富的字幕样式设置
- 灵活的字幕引擎选择
- 多语言识别与翻译
- 字幕记录展示与导出
## 🚀 项目运行
@@ -27,6 +30,14 @@
npm install
```
### 构建字幕引擎
字幕引擎原理:所谓的字幕引擎实际上是一个子程序,它会实时获取系统音频输入(录音)或输出(播放声音)的流式数据,并调用音频转文字的模型生成对应音频的字幕。生成的字幕通过 IPC 输出为转换为字符串的 JSON 数据,并返回给主程序。主程序读取字幕数据,处理后显示在窗口上。
目前项目默认使用 [阿里云 Gummy 模型](https://help.aliyun.com/zh/model-studio/gummy-speech-recognition-translation/),需要有阿里云百炼平台的 API KEY 才能正常使用该模型。
gummy 字幕引擎是一个 python 子程序,可以选择配置好 python 环境后直接运行该程序,也可以使用 pyinstaller 构建一个可执行文件。 运行字幕引擎子程序的代码在 `src\main\utils\engine.ts` 文件中
### 运行项目
```bash
@@ -34,6 +45,8 @@ npm run dev
```
### 构建项目
注意目前软件没有适配 macOS 平台,请使用 Windows 或 Linux 系统进行构建。
```bash
# For windows
npm run build:win

View File

@@ -1,63 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "4604aefd",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Received: {\"message\":\"Electron Initialized\"}\n",
"Client disconnected\n"
]
}
],
"source": [
"import asyncio\n",
"import websockets\n",
"import nest_asyncio\n",
"import json\n",
"\n",
"# 应用补丁,允许在 Jupyter 中运行嵌套事件循环\n",
"nest_asyncio.apply()\n",
"\n",
"async def handle_client(websocket, path=\"/\"):\n",
" try:\n",
" async for message in websocket:\n",
" print(f\"Received: {message}\")\n",
" await websocket.send(json.dumps({\"message\": \"Hello from server!\"}))\n",
" except websockets.exceptions.ConnectionClosed:\n",
" print(\"Client disconnected\")\n",
"\n",
"start_server = websockets.serve(handle_client, \"localhost\", 8765)\n",
"\n",
"asyncio.get_event_loop().run_until_complete(start_server)\n",
"asyncio.get_event_loop().run_forever()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "mystd",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,38 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

View File

@@ -1,61 +0,0 @@
import json
import websockets
class WebSocketServer:
def __init__(self):
self.server = None
self.websocket = None
async def start(self, port=8765):
"""启动 WebSocket 服务器"""
self.server = await websockets.serve(self.handle_client, "localhost", port)
print(f"INFO websocket server started on ws://localhost:{port}")
async def stop(self):
"""关闭 WebSocket 服务器"""
if self.server:
try:
if self.websocket:
await self.close()
self.server.close()
await self.server.wait_closed()
print("INFO server closed successfully")
except Exception as e:
print(f"ERROR failed to close server: {e}")
finally:
self.server = None
async def handle_client(self, websocket, path="/"):
"""处理客户端连接"""
try:
self.websocket = websocket
async for message in websocket:
print(f"INFO received: {message}")
except websockets.exceptions.ConnectionClosed:
print("INFO client disconnected")
self.websocket = None
async def send(self, data):
"""向连接的客户端发送数据"""
if self.websocket:
try:
await self.websocket.send(json.dumps(data))
print(f"INFO sent: {data}")
return True
except websockets.exceptions.ConnectionClosed:
print("ERROR: Client disconnected while sending data")
self.websocket = None
return False
return False
async def close(self):
"""安全地断开WebSocket连接"""
if self.websocket:
try:
await self.websocket.close()
print("INFO connection closed successfully")
except Exception as e:
print(f"ERROR failed to close connection: {e}")
finally:
self.websocket = None

View File

@@ -24,7 +24,7 @@ export class CaptionEngine {
'python-subprocess', 'main.py'
))
this.command.push('-s', controls.sourceLang)
this.command.push('-t', controls.targetLang)
this.command.push('-t', controls.translation ? controls.targetLang : 'none')
console.log(this.appPath)
console.log(this.command)

View File

@@ -30,7 +30,16 @@
</div>
<div class="caption-list">
<div class="caption-title">字幕记录</div>
<div class="caption-title">
<span style="margin-right: 30px;">字幕记录</span>
<a-button
type="primary"
@click="exportCaptions"
:disabled="captionData.length === 0"
>
导出字幕记录
</a-button>
</div>
<a-table
:columns="columns"
:data-source="captionData"
@@ -105,6 +114,20 @@ const columns = [
function openCaptionWindow() {
window.electron.ipcRenderer.send('control.captionWindow.activate')
}
function exportCaptions() {
const jsonData = JSON.stringify(captionData.value, null, 2)
const blob = new Blob([jsonData], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
a.download = `captions-${timestamp}.json`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
}
</script>
<style scoped>

View File

@@ -15,15 +15,6 @@ export const useCaptionControlStore = defineStore('captionControl', () => {
{ value: 'ko', label: '韩语' }
]
},
{
value: 'whisper',
label: '本地-OpenAI-Whisper',
languages: [
{ value: 'auto', label: '自动检测' },
{ value: 'en', label: '英语' },
{ value: 'zh', label: '简体中文' }
]
},
])
const engineEnabled = ref(false)
@@ -82,7 +73,8 @@ export const useCaptionControlStore = defineStore('captionControl', () => {
engineEnabled.value = true
notification.open({
message: '字幕引擎启动',
description: `原语言:${sourceLang.value},是否翻译:${translation.value?'是':'否'},翻译语言:${targetLang.value}`
description: `原语言:${sourceLang.value},是否翻译:${translation.value?'是':'否'}` +
(translation.value ? `,翻译语言:${targetLang.value}` : '')
});
})