feat(renderer): 创建字幕控制界面

This commit is contained in:
himeditator
2025-06-15 16:05:49 +08:00
parent 8858189bf6
commit a07c3283b7
13 changed files with 619 additions and 117 deletions

View File

@@ -3,16 +3,6 @@ import path from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
import { PythonConnector } from './pyComm';
const pythonConnector = new PythonConnector();
setTimeout(() => {
pythonConnector.send({
command: 'process_data',
payload: { some: 'data' }
});
}, 2000);
let mainWindow: BrowserWindow | undefined
function createMainWindow(): void {
@@ -42,7 +32,7 @@ function createMainWindow(): void {
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/main/index.html'))
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
}
}

View File

@@ -1,9 +1,56 @@
<template>
<ViedoPlayer />
<Caption />
<div>
<a-row>
<a-col :span="controlSpan">
<div class="caption-control">
<a-card size="small" title="页面设置">
<div>
<span class="span-label">页面宽度</span>
<a-input
type="range" class="span-input"
min="6" max="18"
v-model:value="controlSpan"
/>
</div>
</a-card>
<CaptionControl />
<CaptionStyle />
</div>
</a-col>
<a-col :span="24 - controlSpan">
<CaptionData />
</a-col>
</a-row>
</div>
</template>
<script setup lang="ts">
import ViedoPlayer from './components/VideoPlayer.vue'
import Caption from './components/Caption.vue'
import CaptionStyle from './components/CaptionStyle.vue'
import CaptionControl from './components/CaptionControl.vue';
import CaptionData from './components/CaptionData.vue'
import { ref } from 'vue'
const controlSpan = ref(9)
</script>
<style scoped>
.caption-control {
height: 100vh;
border-right: 1px solid #7774;
padding: 20px;
overflow-y: auto;
scrollbar-width: thin;
}
.span-label {
display: inline-block;
width: 80px;
text-align: right;
margin-right: 10px;
}
.span-input {
width: 100px;
}
</style>

View File

@@ -1,5 +1,6 @@
body {
margin: 0;
padding: 0;
background-color: black;
height: 100vh;
overflow: hidden;
}

View File

@@ -1,46 +0,0 @@
<template>
<div
class="video-caption"
v-if="captionContent"
ref="captionTarget"
:style="{
color: captionFontColor,
fontSize: captionFontSize + 'px',
fontFamily: captionFontFamily
}"
>
<div>{{ captionContent }}</div>
<div>{{ translation }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useCaptionStore } from '../stores/caption'
const captionContent = ref<String>("これは確か、ずっと前に人からもらって。")
const translation = ref<string>('这是很久以前从别人那里得到的。')
const {captionFontColor, captionFontFamily, captionFontSize} = storeToRefs(useCaptionStore())
const captionTarget = ref()
window.electron.ipcRenderer.on('new-caption', (_, data) => {
captionContent.value = data
})
</script>
<style scoped>
.video-caption {
color: white;
background-color: rgba(99, 99, 99, 0.4);
display: inline-block;
max-width: 80%;
padding: 10px;
font-size: 32px;
font-weight: bold;
border-radius: 10px;
position: fixed;
bottom: 50px;
left: 50%;
transform: translateX(-50%);
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<div style="height: 20px;"></div>
<a-card size="small" title="字幕控制">
<template #extra><a href="#">应用</a></template>
<div class="control-item">
<span class="control-label">源语言</span>
<a-select
class="control-input"
ref="select"
v-model:value="sourceLang"
:options="languages"
></a-select>
</div>
<div class="control-item">
<span class="control-label">翻译语言</span>
<a-select
class="control-input"
ref="select"
v-model:value="targetLang"
:options="languages"
></a-select>
</div>
<div class="control-item">
<span class="control-label">字幕引擎</span>
<a-select
class="control-input"
ref="select"
v-model:value="engine"
:options="captionEngine"
></a-select>
</div>
<div class="control-item">
<span class="control-label">启用翻译</span>
<a-switch v-model:checked="translation" />
</div>
</a-card>
<div style="height: 20px;"></div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const languages = ref([
{ value: 'en', label: '英语' },
{ value: 'zh', label: '简体中文' },
{ value: 'ja', label: '日语' },
])
const captionEngine = ref([
{value: 'gummy', label: '云端-阿里云-Gummy'}
])
const sourceLang = ref('en')
const targetLang = ref('zh')
const engine = ref('gummy')
const translation = ref<boolean>(true)
</script>
<style scoped>
.control-item {
margin: 10px 0;
}
.control-label {
display: inline-block;
width: 80px;
text-align: right;
margin-right: 10px;
}
.control-input {
width: calc(100% - 100px);
min-width: 100px;
}
.control-item-value {
width: 80px;
text-align: right;
font-size: 12px;
color: #666
}
</style>

View File

@@ -0,0 +1,14 @@
<template>
<div>
<a-row>
<a-col :span="12">col-12</a-col>
<a-col :span="12">col-12</a-col>
</a-row>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped></style>

View File

@@ -0,0 +1,208 @@
<template>
<a-card size="small" title="字幕样式设置">
<template #extra>
<a>应用样式</a> | <a>取消更改</a>
</template>
<div class="style-item">
<span class="style-label">字体族</span>
<a-input
class="style-input"
v-model:value="currentFontFamily"
/>
</div>
<div class="style-item">
<span class="style-label">字体大小</span>
<a-input
class="style-input"
type="number"
v-model:value="currentFontSize"
/>
</div>
<div class="style-item">
<span class="style-label">字体颜色</span>
<a-input
class="style-input"
type="color"
v-model:value="currentFontColor"
/>
<div class="style-item-value">{{ currentFontColor }}</div>
</div>
<div class="style-item">
<span class="style-label">背景颜色</span>
<a-input
class="style-input"
type="color"
v-model:value="currentBackground"
/>
<div class="style-item-value">{{ currentBackground }}</div>
</div>
<div class="style-item">
<span class="style-label">背景透明度</span>
<a-input
class="style-input range-input"
type="range"
min="0"
max="100"
v-model:value="currentOpacity"
/>
<div class="style-item-value">{{ currentOpacity }}</div>
</div>
<div class="style-item">
<span class="style-label">显示预览</span>
<a-switch v-model:checked="displayPreview" />
<span class="style-label">显示翻译</span>
<a-switch v-model:checked="currentTranslation" />
</div>
<div v-show="currentTranslation">
<a-card size="small" title="翻译样式设置">
<template #extra>
<a @click="useSameStyle">使用相同样式</a>
</template>
<div class="style-item">
<span class="style-label">翻译字体</span>
<a-input
class="style-input"
v-model:value="currentTransFontFamily"
/>
</div>
<div class="style-item">
<span class="style-label">翻译大小</span>
<a-input
class="style-input"
type="number"
v-model:value="currentTransFontSize"
/>
</div>
<div class="style-item">
<span class="style-label">翻译颜色</span>
<a-input
class="style-input"
type="color"
v-model:value="currentTransFontColor"
/>
<div class="style-item-value">{{ currentTransFontColor }}</div>
</div>
</a-card>
</div>
</a-card>
<Teleport to="body">
<div
v-if="displayPreview"
class="preview-container"
:style="{
backgroundColor: addOpicityToColor(currentBackground, currentOpacity)
}"
>
<div class="preview-caption"
:style="{
fontFamily: currentFontFamily,
fontSize: currentFontSize + 'px',
color: currentFontColor
}">
{{ "This is a preview of subtitle styles." }}
</div>
<div class="preview-translation" v-if="currentTranslation"
:style="{
fontFamily: currentTransFontFamily,
fontSize: currentTransFontSize + 'px',
color: currentTransFontColor
}"
>这是字幕样式预览(翻译)</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { ref } from 'vue'
// import { useCaptionStore } from '../stores/caption'
// const caption = useCaptionStore()
const currentFontFamily = ref<string>('sans-serif')
const currentFontSize = ref<number>(24)
const currentFontColor = ref<string>('#000000')
const currentBackground = ref<string>('#dbe2ef')
const currentOpacity = ref<number>(50)
const currentTranslation = ref<boolean>(true)
const currentTransFontFamily = ref<string>('sans-serif')
const currentTransFontSize = ref<number>(24)
const currentTransFontColor = ref<string>('#000000')
const displayPreview = ref<boolean>(true)
function addOpicityToColor(color: string, opicity: number) {
if (color.length !== 7 || color[0] !== '#') {
throw new Error('Invalid color format. Please use a valid hex color like #AABBCC.');
}
const opicityValue = Math.round(opicity * 255 / 100);
const opicityHex = opicityValue.toString(16).padStart(2, '0');
return `${color}${opicityHex}`;
}
function useSameStyle(){
currentTransFontFamily.value = currentFontFamily.value;
currentTransFontSize.value = currentFontSize.value;
currentTransFontColor.value = currentFontColor.value;
}
</script>
<style scoped>
.caption-style {
height: 100vh;
border-right: 1px solid #7774;
padding: 20px;
overflow-y: auto;
scrollbar-width: thin;
}
.caption-button {
display: flex;
justify-content: center;
}
.style-item {
margin: 10px 0;
}
.style-label {
display: inline-block;
width: 80px;
text-align: right;
margin-right: 10px;
}
.style-input {
width: calc(100% - 100px);
min-width: 100px;
}
.style-item-value {
width: 80px;
text-align: right;
font-size: 12px;
color: #666
}
.range-input {
width: calc(100% - 110px);
min-width: 90px;
padding-left: 0 !important;
padding-right: 0 !important;
margin-left: 5px;
margin-right: 5px;
}
.preview-container {
line-height: 2em;
width: 60%;
text-align: center;
position: absolute;
padding: 20px;
border-radius: 10px;
left: 50%;
transform: translateX(-50%);
bottom: 20px;
}
</style>

View File

@@ -1,28 +0,0 @@
<template>
<div class="settings">
<input type="color" v-model="captionFontColor">
<input type="text" v-model="captionFontFamily">
<input type="number" v-model="captionFontSize">
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCaptionStore } from '../stores/caption'
const {captionFontColor, captionFontFamily, captionFontSize} = storeToRefs(useCaptionStore())
</script>
<style scoped>
.settings {
display: flex;
align-items: center;
width: 100%;
height: 50px;
background-color: black;
box-sizing: border-box;
}
input {
height: 30px;
margin: auto 20px;
}
</style>

View File

@@ -1,24 +0,0 @@
<template>
<div class="video-player">
<video src="../assets/video/example-ja.mp4" controls></video>
<Settings />
</div>
</template>
<script setup lang="ts">
import Settings from './Settings.vue'
</script>
<style scoped>
.video-player {
display: flex;
height: 100vh;
align-items: center;
flex-wrap: wrap;
}
video {
width: 100%;
height: calc(100vh - 50px);
}
</style>

View File

@@ -2,7 +2,10 @@ import './assets/styles/reset.css'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './App.vue'
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
const app = createApp(App)
app.use(createPinia())
app.use(Antd)
app.mount('#app')

View File

@@ -5,5 +5,11 @@ export const useCaptionStore = defineStore('caption', () => {
const captionFontFamily = ref<string>('sans-serif')
const captionFontSize = ref<number>(24)
const captionFontColor = ref<string>('#ffffff')
return { captionFontFamily, captionFontSize, captionFontColor }
const backgroundColor = ref<string>('#000000')
return {
captionFontFamily,
captionFontSize,
captionFontColor,
backgroundColor
}
})