Files
aiclientpureui/src/components/workflow/Step05SubtitleMusic.vue
949036910@qq.com 4120f37c94 11
2026-05-31 22:19:51 +08:00

215 lines
5.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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,
processedVideoPath,
subtitlePreviewVideoPath,
subtitlePreviewVideoSrc,
bgmPreviewSrc,
} = storeToRefs(workflow);
const feedback = ref({ severity: "", message: "" });
const templateDialogVisible = ref(false);
const hasSourceVideo = computed(() =>
Boolean(processedVideoPath.value || 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>