mirror of
https://github.com/HiMeditator/auto-caption.git
synced 2026-02-04 04:14:42 +08:00
feat功能): 完善字幕引擎并添加字幕记录导出功能
- 丰富了 README.md 文件,介绍了字幕引擎的原理和构建方法 - 更新了 .gitignore 文件,排除了 build 目录 - 移除了 python-prototype 和 python-subprocess 目录下的无用代码 - 添加了字幕记录导出功能,用户可以将字幕数据导出为 JSON 文件 - 调整了字幕控制面板,移除了未使用的 Whisper 引擎选项
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,4 +5,5 @@ out
|
||||
.eslintcache
|
||||
*.log*
|
||||
__pycache__
|
||||
subenv
|
||||
subenv
|
||||
build
|
||||
15
README.md
15
README.md
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
38
python-subprocess/main.spec
Normal file
38
python-subprocess/main.spec
Normal 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,
|
||||
)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}` : '')
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user