feat(renderer):实现多行字幕显示功能

- 在 CaptionStyle 组件中添加字幕行数设置选项
- 修改组件以支持多行字幕显示
- 优化字幕数据处理逻辑,支持按时间顺序显示多条字幕
This commit is contained in:
himeditator
2025-09-07 21:06:11 +08:00
parent 4abd6d0808
commit 4494b2c68b
9 changed files with 141 additions and 46 deletions

View File

@@ -63,7 +63,7 @@ class SosvRecognizer:
vad_config.silero_vad.threshold = 0.5
vad_config.silero_vad.min_silence_duration = 0.1
vad_config.silero_vad.min_speech_duration = 0.25
vad_config.silero_vad.max_speech_duration = 8
vad_config.silero_vad.max_speech_duration = 5
vad_config.sample_rate = 16000
self.window_size = vad_config.silero_vad.window_size
self.vad = sherpa_onnx.VoiceActivityDetector(vad_config, buffer_size_in_seconds=100)

View File

@@ -17,8 +17,11 @@ def audio_recording(stream: AudioStream, resample: bool, record = False, path =
wf = None
full_name = ''
if record:
if path != '' and path[-1] != '/':
path += '/'
if path != '':
if path.startswith('"') and path.endswith('"'):
path = path[1:-1]
if path[-1] != '/':
path += '/'
cur_dt = datetime.datetime.now()
name = cur_dt.strftime("audio-%Y-%m-%dT%H-%M-%S")
full_name = f'{path}{name}.wav'

View File

@@ -23,6 +23,7 @@ export interface Controls {
}
export interface Styles {
lineNumber: number,
lineBreak: number,
fontFamily: string,
fontSize: number,

View File

@@ -19,6 +19,7 @@ function getDesktopPath() {
}
const defaultStyles: Styles = {
lineNumber: 1,
lineBreak: 1,
fontFamily: 'sans-serif',
fontSize: 24,

View File

@@ -66,7 +66,7 @@ export class CaptionEngine {
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.command.push('-rp', `"${allConfig.controls.recordingPath}"`)
}
this.port = Math.floor(Math.random() * (65535 - 1024 + 1)) + 1024
this.command.push('-p', this.port.toString())

View File

@@ -6,6 +6,16 @@
<a @click="resetStyle">{{ $t('style.resetStyle') }}</a>
</template>
<div class="input-item">
<span class="input-label">{{ '字幕行数' }}</span>
<a-radio-group v-model:value="currentLineNumber">
<a-radio-button :value="1">1</a-radio-button>
<a-radio-button :value="2">2</a-radio-button>
<a-radio-button :value="3">3</a-radio-button>
<a-radio-button :value="4">4</a-radio-button>
</a-radio-group>
</div>
<div class="input-item">
<span class="input-label">{{ $t('style.longCaption') }}</span>
<a-select
@@ -178,28 +188,57 @@
textShadow: currentTextShadow ? `${currentOffsetX}px ${currentOffsetY}px ${currentBlur}px ${currentTextShadowColor}` : 'none'
}"
>
<p :class="[currentLineBreak?'':'left-ellipsis']"
:style="{
fontFamily: currentFontFamily,
fontSize: currentFontSize + 'px',
color: currentFontColor,
fontWeight: currentFontWeight * 100
}">
<span v-if="captionData.length">{{ captionData[captionData.length-1].text }}</span>
<span v-else>{{ $t('example.original') }}</span>
</p>
<p :class="[currentLineBreak?'':'left-ellipsis']"
v-if="currentTransDisplay"
:style="{
fontFamily: currentTransFontFamily,
fontSize: currentTransFontSize + 'px',
color: currentTransFontColor,
fontWeight: currentTransFontWeight * 100
}"
>
<span v-if="captionData.length">{{ captionData[captionData.length-1].translation }}</span>
<span v-else>{{ $t('example.translation') }}</span>
</p>
<template v-if="captionData.length">
<template
v-for="val in revArr[Math.min(currentLineNumber, captionData.length)]"
:key="captionData[captionData.length - val].time_s"
>
<p :class="[currentLineBreak?'':'left-ellipsis']"
:style="{
fontFamily: currentFontFamily,
fontSize: currentFontSize + 'px',
color: currentFontColor,
fontWeight: currentFontWeight * 100
}">
<span>{{ captionData[captionData.length - val].text }}</span>
</p>
<p :class="[currentLineBreak?'':'left-ellipsis']"
v-if="currentTransDisplay && captionData[captionData.length - val].translation"
:style="{
fontFamily: currentTransFontFamily,
fontSize: currentTransFontSize + 'px',
color: currentTransFontColor,
fontWeight: currentTransFontWeight * 100
}"
>
<span>{{ captionData[captionData.length - val].translation }}</span>
</p>
</template>
</template>
<template v-else>
<template v-for="val in currentLineNumber" :key="val">
<p :class="[currentLineBreak?'':'left-ellipsis']"
:style="{
fontFamily: currentFontFamily,
fontSize: currentFontSize + 'px',
color: currentFontColor,
fontWeight: currentFontWeight * 100
}">
<span>{{ $t('example.original') }}</span>
</p>
<p :class="[currentLineBreak?'':'left-ellipsis']"
v-if="currentTransDisplay"
:style="{
fontFamily: currentTransFontFamily,
fontSize: currentTransFontSize + 'px',
color: currentTransFontColor,
fontWeight: currentTransFontWeight * 100
}"
>
<span>{{ $t('example.translation') }}</span>
</p>
</template>
</template>
</div>
</Teleport>
@@ -212,6 +251,14 @@ import { storeToRefs } from 'pinia'
import { notification } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
import { useCaptionLogStore } from '@renderer/stores/captionLog';
const revArr = {
1: [1],
2: [2, 1],
3: [3, 2, 1],
4: [4, 3, 2, 1],
}
const captionLog = useCaptionLogStore();
const { captionData } = storeToRefs(captionLog);
@@ -220,6 +267,7 @@ const { t } = useI18n()
const captionStyle = useCaptionStyleStore()
const { changeSignal } = storeToRefs(captionStyle)
const currentLineNumber = ref<number>(1)
const currentLineBreak = ref<number>(0)
const currentFontFamily = ref<string>('sans-serif')
const currentFontSize = ref<number>(24)
@@ -253,6 +301,7 @@ function useSameStyle(){
}
function applyStyle(){
captionStyle.lineNumber = currentLineNumber.value;
captionStyle.lineBreak = currentLineBreak.value;
captionStyle.fontFamily = currentFontFamily.value;
captionStyle.fontSize = currentFontSize.value;
@@ -282,6 +331,7 @@ function applyStyle(){
}
function backStyle(){
currentLineNumber.value = captionStyle.lineNumber;
currentLineBreak.value = captionStyle.lineBreak;
currentFontFamily.value = captionStyle.fontFamily;
currentFontSize.value = captionStyle.fontSize;

View File

@@ -4,6 +4,7 @@ import { Styles } from '@renderer/types'
import { breakOptions } from '@renderer/i18n'
export const useCaptionStyleStore = defineStore('captionStyle', () => {
const lineNumber = ref<number>(1)
const lineBreak = ref<number>(1)
const fontFamily = ref<string>('sans-serif')
const fontSize = ref<number>(24)
@@ -38,6 +39,7 @@ export const useCaptionStyleStore = defineStore('captionStyle', () => {
function sendStylesChange() {
const styles: Styles = {
lineNumber: lineNumber.value,
lineBreak: lineBreak.value,
fontFamily: fontFamily.value,
fontSize: fontSize.value,
@@ -65,6 +67,7 @@ export const useCaptionStyleStore = defineStore('captionStyle', () => {
}
function setStyles(args: Styles){
lineNumber.value = args.lineNumber
lineBreak.value = args.lineBreak
fontFamily.value = args.fontFamily
fontSize.value = args.fontSize
@@ -91,6 +94,7 @@ export const useCaptionStyleStore = defineStore('captionStyle', () => {
})
return {
lineNumber, // 显示字幕行数
lineBreak, // 换行方式
fontFamily, // 字体族
fontSize, // 字体大小

View File

@@ -23,6 +23,7 @@ export interface Controls {
}
export interface Styles {
lineNumber: number,
lineBreak: number,
fontFamily: string,
fontSize: number,

View File

@@ -12,26 +12,53 @@
textShadow: captionStyle.textShadow ? `${captionStyle.offsetX}px ${captionStyle.offsetY}px ${captionStyle.blur}px ${captionStyle.textShadowColor}` : 'none'
}"
>
<p :class="[captionStyle.lineBreak?'':'left-ellipsis']" :style="{
fontFamily: captionStyle.fontFamily,
fontSize: captionStyle.fontSize + 'px',
color: captionStyle.fontColor,
fontWeight: captionStyle.fontWeight * 100
}">
<span v-if="captionData.length">{{ captionData[captionData.length-1].text }}</span>
<span v-else>{{ $t('example.original') }}</span>
</p>
<p :class="[captionStyle.lineBreak?'':'left-ellipsis']"
v-if="captionStyle.transDisplay"
:style="{
fontFamily: captionStyle.transFontFamily,
fontSize: captionStyle.transFontSize + 'px',
color: captionStyle.transFontColor,
fontWeight: captionStyle.transFontWeight * 100
}">
<span v-if="captionData.length">{{ captionData[captionData.length-1].translation }}</span>
<span v-else>{{ $t('example.translation') }}</span>
</p>
<template v-if="captionData.length">
<template
v-for="val in revArr[Math.min(captionStyle.lineNumber, captionData.length)]"
:key="captionData[captionData.length - val].time_s"
>
<p :class="[captionStyle.lineBreak?'':'left-ellipsis']" :style="{
fontFamily: captionStyle.fontFamily,
fontSize: captionStyle.fontSize + 'px',
color: captionStyle.fontColor,
fontWeight: captionStyle.fontWeight * 100
}">
<span>{{ captionData[captionData.length - val].text }}</span>
</p>
<p :class="[captionStyle.lineBreak?'':'left-ellipsis']"
v-if="captionStyle.transDisplay && captionData[captionData.length - val].translation"
:style="{
fontFamily: captionStyle.transFontFamily,
fontSize: captionStyle.transFontSize + 'px',
color: captionStyle.transFontColor,
fontWeight: captionStyle.transFontWeight * 100
}">
<span>{{ captionData[captionData.length - val].translation }}</span>
</p>
</template>
</template>
<template v-else>
<template v-for="val in captionStyle.lineNumber" :key="val">
<p :class="[captionStyle.lineBreak?'':'left-ellipsis']" :style="{
fontFamily: captionStyle.fontFamily,
fontSize: captionStyle.fontSize + 'px',
color: captionStyle.fontColor,
fontWeight: captionStyle.fontWeight * 100
}">
<span>{{ $t('example.original') }}</span>
</p>
<p :class="[captionStyle.lineBreak?'':'left-ellipsis']"
v-if="captionStyle.transDisplay"
:style="{
fontFamily: captionStyle.transFontFamily,
fontSize: captionStyle.transFontSize + 'px',
color: captionStyle.transFontColor,
fontWeight: captionStyle.transFontWeight * 100
}">
<span>{{ $t('example.translation') }}</span>
</p>
</template>
</template>
</div>
<div class="title-bar" :style="{color: captionStyle.fontColor}">
@@ -56,6 +83,14 @@ import { ref, onMounted } from 'vue';
import { useCaptionStyleStore } from '@renderer/stores/captionStyle';
import { useCaptionLogStore } from '@renderer/stores/captionLog';
import { storeToRefs } from 'pinia';
const revArr = {
1: [1],
2: [2, 1],
3: [3, 2, 1],
4: [4, 3, 2, 1],
}
const captionStyle = useCaptionStyleStore();
const captionLog = useCaptionLogStore();
const { captionData } = storeToRefs(captionLog);