feat(log): 添加字幕记录复制功能 (#3)

- 提高记录时间精度,精确到毫秒
- 在字幕记录组件中添加复制到剪贴板的功能
- 提供多种复制选项,包括是否添加序号、是否复制时间、选择复制内容等
This commit is contained in:
himeditator
2025-07-08 01:33:48 +08:00
parent 082eb8579b
commit 8ac1c99c63
8 changed files with 84 additions and 15 deletions

View File

@@ -77,7 +77,7 @@ $$
48000\, \text{samples/second} \times 2\,\text{bytes/sample} \times 1\, \text{channel} = 93.75\,\text{KB/s}
$$
模型结果回传流量消耗较小,可以不纳入考虑。
而且引擎只会获取到音频流的时候才会上传数据,因此实际上传速率可能更小。模型结果回传流量消耗较小,可以不纳入考虑。
### Vosk 字幕引擎(本地)

View File

@@ -76,7 +76,7 @@ $$
48000\, \text{samples/second} \times 2\,\text{bytes/sample} \times 1\, \text{channel} = 93.75\,\text{KB/s}
$$
The traffic consumption for returning the model results is relatively small and can be disregarded.
Moreover, the engine only uploads data when it gets an audio stream, so the actual upload speed may be smaller. The traffic consumption for returning the model results is relatively small and can be disregarded.
### Vosk Subtitle Engine (Local)

View File

@@ -49,10 +49,6 @@
- Linux プラットフォームでは、現在マイク入力の字幕生成のみがサポートされています。
- 現在、macOS プラットフォームには対応していません。
以下是你选择的内容翻译成日语的结果:
---
## ⚙️ 搭載字幕エンジンの説明
現在のソフトウェアには 1 つの字幕エンジンが搭載されており、新しい 2 つのエンジンが計画されています。それぞれの詳細情報は以下の通りです。
@@ -80,7 +76,7 @@ $$
48000\, \text{samples/second} \times 2\,\text{bytes/sample} \times 1\, \text{channel} = 93.75\,\text{KB/s}
$$
モデルからの結果返送によるトラフィック消費は小さく、考慮する必要はありません。
また、エンジンは音声ストリームを取得したときのみデータをアップロードするため、実際のアップロード速度はさらに小さくなる可能性があります。モデルからの結果返送によるトラフィック消費は小さく、考慮する必要はありません。
### Vosk 字幕エンジン(ローカル)

View File

@@ -39,12 +39,12 @@ class Callback(TranslationRecognizerCallback):
caption['text'] = transcription_result.text
if caption['index'] != self.cur_id:
self.cur_id = caption['index']
cur_time = datetime.now().strftime('%H:%M:%S')
cur_time = datetime.now().strftime('%H:%M:%S.%f')[:-3]
caption['time_s'] = cur_time
self.time_str = cur_time
else:
caption['time_s'] = self.time_str
caption['time_t'] = datetime.now().strftime('%H:%M:%S')
caption['time_t'] = datetime.now().strftime('%H:%M:%S.%f')[:-3]
caption['translation'] = ""
if translation_result is not None:

View File

@@ -9,15 +9,36 @@
style="margin-right: 20px;"
@click="exportCaptions"
:disabled="captionData.length === 0"
>
{{ $t('log.export') }}
</a-button>
>{{ $t('log.export') }}</a-button>
<a-popover :title="$t('log.copyOptions')">
<template #content>
<div class="input-item">
<span class="input-label">{{ $t('log.addIndex') }}</span>
<a-switch v-model:checked="showIndex" />
<span class="input-label">{{ $t('log.copyTime') }}</span>
<a-switch v-model:checked="copyTime" />
</div>
<div class="input-item">
<span class="input-label">{{ $t('log.copyContent') }}</span>
<a-radio-group v-model:value="copyOption">
<a-radio-button value="both">{{ $t('log.both') }}</a-radio-button>
<a-radio-button value="source">{{ $t('log.source') }}</a-radio-button>
<a-radio-button value="target">{{ $t('log.translation') }}</a-radio-button>
</a-radio-group>
</div>
</template>
<a-button
style="margin-right: 20px;"
@click="copyCaptions"
:disabled="captionData.length === 0"
>{{ $t('log.copy') }}</a-button>
</a-popover>
<a-button
danger
@click="clearCaptions"
>
{{ $t('log.clear') }}
</a-button>
>{{ $t('log.clear') }}</a-button>
</div>
<a-table
:columns="columns"
@@ -49,8 +70,17 @@
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useCaptionLogStore } from '@renderer/stores/captionLog'
import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const captionLog = useCaptionLogStore()
const { captionData } = storeToRefs(captionLog)
const showIndex = ref(true)
const copyTime = ref(true)
const copyOption = ref('both')
const pagination = ref({
current: 1,
pageSize: 10,
@@ -101,12 +131,28 @@ function exportCaptions() {
URL.revokeObjectURL(url)
}
function copyCaptions() {
let content = ''
for(let i = 0; i < captionData.value.length; i++){
const item = captionData.value[i]
if(showIndex.value) content += `${i+1}\n`
if(copyTime.value) content += `${item.time_s} --> ${item.time_t}\n`.replace(/\./g, ',')
if(copyOption.value === 'both') content += `${item.text}\n${item.translation}\n\n`
else if(copyOption.value === 'source') content += `${item.text}\n\n`
else if(copyOption.value === 'translation') content += `${item.translation}\n\n`
}
navigator.clipboard.writeText(content)
message.success(t('log.copySuccess'))
}
function clearCaptions() {
captionLog.clear()
}
</script>
<style scoped>
@import url(../assets/input.css);
.caption-list {
padding: 20px;
border-radius: 8px;

View File

@@ -99,6 +99,15 @@ export default {
},
log: {
"title": "Caption Log",
"copy": "Copy to Clipboard",
"copyOptions": "Copy Options",
"addIndex": "Add Index",
"copyTime": "Copy Time",
"copyContent": "Content",
"both": "Original and Translation",
"source": "Original Only",
"translation": "Translation Only",
"copySuccess": "Subtitle copied to clipboard",
"export": "Export Caption Log",
"clear": "Clear Caption Log"
}

View File

@@ -99,6 +99,15 @@ export default {
},
log: {
"title": "字幕ログ",
"copy": "クリップボードにコピー",
"copyOptions": "コピー設定",
"addIndex": "順序番号",
"copyTime": "時間",
"copyContent": "内容",
"both": "原文と翻訳",
"source": "原文のみ",
"translation": "翻訳のみ",
"copySuccess": "字幕がクリップボードにコピーされました",
"export": "エクスポート",
"clear": "字幕ログをクリア"
}

View File

@@ -100,6 +100,15 @@ export default {
log: {
"title": "字幕记录",
"export": "导出字幕记录",
"copy": "复制到剪贴板",
"copyOptions": "复制选项",
"addIndex": "添加序号",
"copyTime": "复制时间",
"copyContent": "复制内容",
"both": "原文与翻译",
"source": "仅原文",
"translation": "仅翻译",
"copySuccess": "字幕已复制到剪贴板",
"clear": "清空字幕记录"
}
}