11111
This commit is contained in:
211
src/components/workflow/Step05SubtitleMusic.vue
Normal file
211
src/components/workflow/Step05SubtitleMusic.vue
Normal file
@@ -0,0 +1,211 @@
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useWorkflowStore } from "../../stores/workflow.js";
|
||||
import SubtitleTemplateDialog from "../subtitle/SubtitleTemplateDialog.vue";
|
||||
import { revealLocalFileInFolder } from "../../services/fileExport.js";
|
||||
|
||||
const workflow = useWorkflowStore();
|
||||
const {
|
||||
autoSubtitle,
|
||||
smartSubtitle,
|
||||
bgmEnabled,
|
||||
bgmVolume,
|
||||
subtitleTemplateId,
|
||||
subtitleBgmGenerating,
|
||||
generatedVideoPath,
|
||||
subtitlePreviewVideoPath,
|
||||
subtitlePreviewVideoSrc,
|
||||
bgmPreviewSrc,
|
||||
} = storeToRefs(workflow);
|
||||
|
||||
const feedback = ref({ severity: "", message: "" });
|
||||
const templateDialogVisible = ref(false);
|
||||
|
||||
const hasSourceVideo = computed(() => Boolean(generatedVideoPath.value));
|
||||
const hasSubtitleVideoPreview = computed(() =>
|
||||
Boolean(subtitlePreviewVideoSrc.value),
|
||||
);
|
||||
|
||||
const canGenerate = computed(
|
||||
() =>
|
||||
hasSourceVideo.value &&
|
||||
!subtitleBgmGenerating.value &&
|
||||
(autoSubtitle.value || bgmEnabled.value) &&
|
||||
(!bgmEnabled.value || Boolean(workflow.bgmPath)),
|
||||
);
|
||||
|
||||
function showFeedback(result) {
|
||||
if (!result?.message) return;
|
||||
feedback.value = {
|
||||
severity: result.ok ? "success" : "error",
|
||||
message: result.message,
|
||||
};
|
||||
}
|
||||
|
||||
function onSelectTemplate(template) {
|
||||
workflow.subtitleTemplateId = template.id;
|
||||
workflow.subtitleTemplate = template;
|
||||
}
|
||||
|
||||
async function onGenerate() {
|
||||
feedback.value = { severity: "", message: "" };
|
||||
showFeedback(await workflow.generateSubtitleAndBgm());
|
||||
}
|
||||
|
||||
async function onPickBgm() {
|
||||
feedback.value = { severity: "", message: "" };
|
||||
showFeedback(await workflow.chooseBgm());
|
||||
}
|
||||
|
||||
async function onPreviewBgm() {
|
||||
feedback.value = { severity: "", message: "" };
|
||||
const result = await workflow.previewBgm();
|
||||
if (result?.message) showFeedback(result);
|
||||
}
|
||||
|
||||
async function onOpenVideoInExplorer() {
|
||||
if (!subtitlePreviewVideoPath.value) {
|
||||
feedback.value = { severity: "error", message: "暂无字幕预览视频" };
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await revealLocalFileInFolder(subtitlePreviewVideoPath.value);
|
||||
} catch (err) {
|
||||
feedback.value = {
|
||||
severity: "error",
|
||||
message: err instanceof Error ? err.message : String(err),
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardCard title="字幕和音乐" step="05" :grow="true">
|
||||
<label class="flex items-center gap-2 text-sm text-slate-200">
|
||||
<Checkbox v-model="autoSubtitle" binary />
|
||||
自动生成字幕
|
||||
</label>
|
||||
<p class="dashboard-muted mt-1 pl-6 text-xs">
|
||||
提取视频音频并识别,烧录 SRT 字幕到画面
|
||||
</p>
|
||||
|
||||
<label class="mt-2 flex items-center gap-2 text-sm text-slate-200">
|
||||
<Checkbox v-model="smartSubtitle" binary :disabled="!autoSubtitle" />
|
||||
启用智能字幕
|
||||
</label>
|
||||
<p class="dashboard-muted mt-1 pl-6 text-xs">
|
||||
用步骤 01 文案替换识别文本,保留语音时间轴
|
||||
</p>
|
||||
|
||||
<div class="mt-2 flex flex-wrap items-center gap-2">
|
||||
<Button
|
||||
label="模板选择"
|
||||
size="small"
|
||||
outlined
|
||||
icon="pi pi-palette"
|
||||
:disabled="!autoSubtitle"
|
||||
@click="templateDialogVisible = true"
|
||||
/>
|
||||
<span class="dashboard-muted truncate text-sm">
|
||||
已选模板: {{ workflow.subtitleTemplateLabel }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<label class="mt-3 flex items-center gap-2 text-sm text-slate-200">
|
||||
<Checkbox v-model="bgmEnabled" binary />
|
||||
添加背景音乐
|
||||
</label>
|
||||
<div class="mt-2 flex flex-wrap items-center gap-2">
|
||||
<InputNumber
|
||||
v-model="bgmVolume"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:disabled="!bgmEnabled"
|
||||
size="small"
|
||||
class="w-20"
|
||||
/>
|
||||
<span class="text-sm text-slate-400">%</span>
|
||||
<Button
|
||||
label="选择音乐"
|
||||
size="small"
|
||||
severity="secondary"
|
||||
:disabled="!bgmEnabled || subtitleBgmGenerating"
|
||||
@click="onPickBgm"
|
||||
/>
|
||||
<Button
|
||||
label="试听"
|
||||
size="small"
|
||||
outlined
|
||||
:disabled="!bgmEnabled || !workflow.bgmPath"
|
||||
@click="onPreviewBgm"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="workflow.bgmFileName" class="mt-1 truncate text-xs text-slate-400">
|
||||
{{ workflow.bgmFileName }}
|
||||
</p>
|
||||
<audio
|
||||
v-if="bgmPreviewSrc"
|
||||
:src="bgmPreviewSrc"
|
||||
controls
|
||||
class="mt-2 h-8 w-full"
|
||||
/>
|
||||
|
||||
<p
|
||||
v-if="!hasSourceVideo"
|
||||
class="mt-2 text-xs text-amber-400/90"
|
||||
>
|
||||
请先在步骤 02 生成口播视频
|
||||
</p>
|
||||
|
||||
<p
|
||||
v-if="feedback.message"
|
||||
class="mt-2 text-sm"
|
||||
:class="feedback.severity === 'error' ? 'text-red-400' : 'text-emerald-400'"
|
||||
>
|
||||
{{ feedback.message }}
|
||||
</p>
|
||||
|
||||
<Button
|
||||
label="自动生成字幕和BGM"
|
||||
size="small"
|
||||
class="mt-3 w-full"
|
||||
:loading="subtitleBgmGenerating"
|
||||
:disabled="!canGenerate"
|
||||
@click="onGenerate"
|
||||
/>
|
||||
|
||||
<div class="dashboard-preview-box mt-3">
|
||||
<div class="mb-2 flex items-center gap-0.5">
|
||||
<span class="text-xs text-slate-400">字幕视频预览</span>
|
||||
<Button
|
||||
icon="pi pi-folder-open"
|
||||
size="small"
|
||||
severity="secondary"
|
||||
text
|
||||
:disabled="!subtitlePreviewVideoPath"
|
||||
aria-label="在资源管理器中打开"
|
||||
@click="onOpenVideoInExplorer"
|
||||
/>
|
||||
</div>
|
||||
<div class="dashboard-aspect-video">
|
||||
<video
|
||||
v-if="hasSubtitleVideoPreview"
|
||||
:key="subtitlePreviewVideoSrc"
|
||||
:src="subtitlePreviewVideoSrc"
|
||||
controls
|
||||
class="h-full w-full rounded object-contain"
|
||||
/>
|
||||
<span v-else class="text-xs text-gray-400">
|
||||
{{ hasSourceVideo ? "生成字幕后将在此预览" : "请先在步骤 02 生成口播视频" }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SubtitleTemplateDialog
|
||||
v-model:visible="templateDialogVisible"
|
||||
v-model:selected-id="subtitleTemplateId"
|
||||
@select="onSelectTemplate"
|
||||
/>
|
||||
</DashboardCard>
|
||||
</template>
|
||||
Reference in New Issue
Block a user