|
|
@@ -141,6 +141,13 @@
|
|
|
>
|
|
|
{{ isGeneratingImages ? '生成中...' : '一键生成所有背景图' }}
|
|
|
</button>
|
|
|
+ <button
|
|
|
+ class="toolbar-btn"
|
|
|
+ @click="generateAllVideos"
|
|
|
+ :disabled="isGeneratingVideos || isAnyVideoGenerating"
|
|
|
+ >
|
|
|
+ {{ isGeneratingVideos ? '生成中...' : '一键生成所有视频' }}
|
|
|
+ </button>
|
|
|
<button
|
|
|
class="toolbar-btn"
|
|
|
@click="generateAllVoiceovers"
|
|
|
@@ -829,8 +836,9 @@ import { ChatConversationApi } from '@/api/ai/chat/conversation'
|
|
|
import { ChatRoleApi } from '@/api/ai/model/chatRole'
|
|
|
import { ImageApi } from '@/api/ai/image'
|
|
|
import { TtsApi } from '@/api/ai/tts'
|
|
|
+import { VideoApi} from '@/api/ai/video'
|
|
|
import VideoPreview from '@/views/bjdx/course/aiGenerate/VideoPreview.vue'
|
|
|
-import { AiImageStatusEnum, AiPlatformEnum } from '@/views/ai/utils/constants'
|
|
|
+import { AiImageStatusEnum, AiPlatformEnum, AiVideoStatusEnum } from '@/views/ai/utils/constants'
|
|
|
import { marked } from 'marked'
|
|
|
import { useUserStore } from '@/store/modules/user'
|
|
|
|
|
|
@@ -882,6 +890,7 @@ const hasDraftCache = ref(false)
|
|
|
// 步骤2生成状态
|
|
|
const isGeneratingImages = ref(false)
|
|
|
const isGeneratingVoiceovers = ref(false)
|
|
|
+const isGeneratingVideos = ref(false)
|
|
|
|
|
|
// 数字人列表
|
|
|
const digitalHumans = ref([])
|
|
|
@@ -1165,6 +1174,18 @@ const isAnyVoiceoverGenerating = computed(() => {
|
|
|
)
|
|
|
})
|
|
|
|
|
|
+// 计算属性:检查是否有任何视频正在生成
|
|
|
+const isAnyVideoGenerating = computed(() => {
|
|
|
+ return scriptData.sections.some((section) => {
|
|
|
+ // 检查背景视频是否正在生成
|
|
|
+ if (section.backgroundVideo.generating) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ // 检查对话视频是否正在生成
|
|
|
+ return section.dialogues.some((dialogue) => dialogue.generatingVideo)
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
// 校验结果
|
|
|
const isValidationPassed = ref(false)
|
|
|
const validationMessage = ref('')
|
|
|
@@ -1476,6 +1497,10 @@ const removeSection = (sectionIndex) => {
|
|
|
const inProgressImageMap = ref({}) // 监听的图片映射,key 为 image 编号,value 为 { sectionIndex, type: 'image'|'audio' }
|
|
|
const inProgressTimer = ref(null) // 生成中的图片定时器,轮询生成进展
|
|
|
|
|
|
+// 视频生成相关状态
|
|
|
+const inProgressVideoMap = ref({}) // 监听的视频映射,key 为 video 编号,value 为 { sectionIndex, dialogueIndex, type: 'background'|'dialogue' }
|
|
|
+const inProgressVideoTimer = ref(null) // 生成中的视频定时器,轮询生成进展
|
|
|
+
|
|
|
// 步骤2:生成单个媒体(仅背景图)
|
|
|
const generateMedia = async (sectionIndex) => {
|
|
|
const section = scriptData.sections[sectionIndex]
|
|
|
@@ -1525,12 +1550,21 @@ const generateVideo = async (sectionIndex) => {
|
|
|
|
|
|
media.generating = true
|
|
|
try {
|
|
|
- // 这里需要实现视频生成的API调用
|
|
|
- // 暂时使用模拟数据
|
|
|
- setTimeout(() => {
|
|
|
- media.url = 'http://learn-ai.com.cn/admin-api/infra/file/29/get/20260421/13-2_1776760082321.mp4'
|
|
|
- media.generating = false
|
|
|
- }, 2000)
|
|
|
+ const form = {
|
|
|
+ platform: AiPlatformEnum.DOU_BAO,
|
|
|
+ modelId: 60,
|
|
|
+ prompt: media.prompt,
|
|
|
+ width: 1024,
|
|
|
+ height: 768,
|
|
|
+ resolution: '1080P',
|
|
|
+ duration: 4,
|
|
|
+ options: {}
|
|
|
+ }
|
|
|
+ const response = await VideoApi.drawVideo(form)
|
|
|
+ inProgressVideoMap.value[response] = {
|
|
|
+ sectionIndex,
|
|
|
+ type: 'background'
|
|
|
+ }
|
|
|
} catch (error) {
|
|
|
console.error(`生成视频失败:`, error)
|
|
|
media.generating = false
|
|
|
@@ -1554,12 +1588,22 @@ const generateDialogueVideo = async (sectionIndex, dialogueIndex) => {
|
|
|
|
|
|
dialogue.generatingVideo = true
|
|
|
try {
|
|
|
- // 这里需要实现视频生成的API调用
|
|
|
- // 暂时使用模拟数据
|
|
|
- setTimeout(() => {
|
|
|
- dialogue.videoUrl = 'http://learn-ai.com.cn/admin-api/infra/file/29/get/20260421/13-2_1776760082321.mp4'
|
|
|
- dialogue.generatingVideo = false
|
|
|
- }, 2000)
|
|
|
+ const form = {
|
|
|
+ platform: AiPlatformEnum.DOU_BAO,
|
|
|
+ modelId: 60,
|
|
|
+ prompt: dialogue.videoPrompt,
|
|
|
+ width: 1024,
|
|
|
+ height: 768,
|
|
|
+ resolution: '1080P',
|
|
|
+ duration: 4,
|
|
|
+ options: {}
|
|
|
+ }
|
|
|
+ const response = await VideoApi.drawVideo(form)
|
|
|
+ inProgressVideoMap.value[response] = {
|
|
|
+ sectionIndex,
|
|
|
+ dialogueIndex,
|
|
|
+ type: 'dialogue'
|
|
|
+ }
|
|
|
} catch (error) {
|
|
|
console.error(`生成对话视频失败:`, error)
|
|
|
dialogue.generatingVideo = false
|
|
|
@@ -1595,6 +1639,38 @@ const generateAllImages = async () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 步骤2:一键生成所有视频
|
|
|
+const generateAllVideos = async () => {
|
|
|
+ isGeneratingVideos.value = true
|
|
|
+ try {
|
|
|
+ for (let i = 0; i < scriptData.sections.length; i++) {
|
|
|
+ const section = scriptData.sections[i]
|
|
|
+ // 处理视频背景
|
|
|
+ if (section.backgroundType === 'video' && !section.backgroundVideo.url) {
|
|
|
+ await generateVideo(i)
|
|
|
+ }
|
|
|
+ // 处理对话视频
|
|
|
+ for (let j = 0; j < section.dialogues.length; j++) {
|
|
|
+ const dialogue = section.dialogues[j]
|
|
|
+ if (dialogue.type === 'video' && !dialogue.videoUrl) {
|
|
|
+ await generateDialogueVideo(i, j)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否有视频正在生成
|
|
|
+ const checkVideosComplete = setInterval(() => {
|
|
|
+ if (Object.keys(inProgressVideoMap.value).length === 0) {
|
|
|
+ isGeneratingVideos.value = false
|
|
|
+ clearInterval(checkVideosComplete)
|
|
|
+ }
|
|
|
+ }, 1000)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('生成所有视频失败:', error)
|
|
|
+ isGeneratingVideos.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 轮询生成中的图片列表
|
|
|
const refreshWatchImages = async () => {
|
|
|
const imageIds = Object.keys(inProgressImageMap.value).map(Number)
|
|
|
@@ -1630,6 +1706,56 @@ const refreshWatchImages = async () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 轮询生成中的视频列表
|
|
|
+const refreshWatchVideos = async () => {
|
|
|
+ const videoIds = Object.keys(inProgressVideoMap.value).map(Number)
|
|
|
+ if (videoIds.length === 0) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const list = await VideoApi.getVideoListMyByIds(videoIds)
|
|
|
+ const newWatchVideos = {}
|
|
|
+ list.forEach((video) => {
|
|
|
+ const info = inProgressVideoMap.value[video.id]
|
|
|
+ if (info) {
|
|
|
+ if (video.status === AiVideoStatusEnum.IN_PROGRESS) {
|
|
|
+ newWatchVideos[video.id] = info
|
|
|
+ } else if (video.status === AiVideoStatusEnum.SUCCESS && video.videoUrl) {
|
|
|
+ if (info.type === 'background') {
|
|
|
+ const section = scriptData.sections[info.sectionIndex]
|
|
|
+ section.backgroundVideo.url = video.videoUrl
|
|
|
+ section.backgroundVideo.generating = false
|
|
|
+ } else if (info.type === 'dialogue') {
|
|
|
+ const dialogue = scriptData.sections[info.sectionIndex].dialogues[info.dialogueIndex]
|
|
|
+ dialogue.videoUrl = video.videoUrl
|
|
|
+ dialogue.generatingVideo = false
|
|
|
+ }
|
|
|
+ } else if (video.status === AiVideoStatusEnum.FAIL) {
|
|
|
+ if (info.type === 'background') {
|
|
|
+ const section = scriptData.sections[info.sectionIndex]
|
|
|
+ section.backgroundVideo.generating = false
|
|
|
+ // 生成失败,从replacedUrls中移除旧URL
|
|
|
+ if (section.backgroundVideo.url) {
|
|
|
+ replacedUrls.value.delete(section.backgroundVideo.url)
|
|
|
+ }
|
|
|
+ } else if (info.type === 'dialogue') {
|
|
|
+ const dialogue = scriptData.sections[info.sectionIndex].dialogues[info.dialogueIndex]
|
|
|
+ dialogue.generatingVideo = false
|
|
|
+ // 生成失败,从replacedUrls中移除旧URL
|
|
|
+ if (dialogue.videoUrl) {
|
|
|
+ replacedUrls.value.delete(dialogue.videoUrl)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ inProgressVideoMap.value = newWatchVideos
|
|
|
+ } catch (error) {
|
|
|
+ console.error('轮询视频状态失败:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 播放音频通用函数
|
|
|
const playAudio = (url, type) => {
|
|
|
if (audioState.currentAudio) {
|
|
|
@@ -1891,6 +2017,10 @@ onMounted(async () => {
|
|
|
inProgressTimer.value = setInterval(async () => {
|
|
|
await refreshWatchImages()
|
|
|
}, 3000)
|
|
|
+
|
|
|
+ inProgressVideoTimer.value = setInterval(async () => {
|
|
|
+ await refreshWatchVideos()
|
|
|
+ }, 3000)
|
|
|
})
|
|
|
|
|
|
// 关闭事件
|
|
|
@@ -1904,6 +2034,9 @@ onUnmounted(() => {
|
|
|
if (inProgressTimer.value) {
|
|
|
clearInterval(inProgressTimer.value)
|
|
|
}
|
|
|
+ if (inProgressVideoTimer.value) {
|
|
|
+ clearInterval(inProgressVideoTimer.value)
|
|
|
+ }
|
|
|
})
|
|
|
</script>
|
|
|
|