添加视频帧提取功能和阿里云OSS存储支持

- 新增从视频素材提取首帧/尾帧的功能,支持画面连续性编辑
- 添加阿里云OSS存储支持,可配置本地或OSS存储方式
- 导入视频素材时自动探测并更新视频时长信息
- 前端添加从素材提取尾帧的UI界面
- 添加FramePrompt模型的数据库迁移

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-18 21:44:39 +08:00
parent fe595db96e
commit d970107a34
13 changed files with 351 additions and 19 deletions

View File

@@ -1,10 +1,10 @@
import type {
Asset,
AssetCollection,
AssetTag,
CreateAssetRequest,
ListAssetsParams,
UpdateAssetRequest
Asset,
AssetCollection,
AssetTag,
CreateAssetRequest,
ListAssetsParams,
UpdateAssetRequest
} from '../types/asset'
import request from '../utils/request'
@@ -43,5 +43,9 @@ export const assetAPI = {
importFromVideo(videoGenId: number) {
return request.post<Asset>(`/assets/import/video/${videoGenId}`)
},
extractFrame(assetId: number, data: { position: string; storyboard_id: number; frame_type?: string }) {
return request.post(`/assets/${assetId}/extract-frame`, data)
}
}

View File

@@ -239,6 +239,24 @@
}}</span>
</div>
<!-- 从素材提取尾帧 -->
<div class="extract-frame-section" v-if="selectedFrameType === 'first'" style="margin: 12px 0; padding: 12px; background: #f5f7fa; border-radius: 8px;">
<div class="section-label" style="margin-bottom: 8px;">从素材提取尾帧用于画面连续性</div>
<div style="display: flex; gap: 10px; align-items: center;">
<el-select v-model="selectedAssetForExtract" placeholder="选择视频素材" style="width: 300px;" size="small">
<el-option
v-for="asset in videoAssets"
:key="asset.id"
:label="`镜头 #${asset.storyboard_num || asset.id} - ${asset.name}`"
:value="asset.id"
/>
</el-select>
<el-button type="primary" size="small" :loading="extractingFrame" :disabled="!selectedAssetForExtract" @click="extractLastFrame">
提取尾帧
</el-button>
</div>
</div>
<!-- 提示词区域 -->
<div class="prompt-section">
<div class="section-label">
@@ -952,6 +970,8 @@ const generatingImage = ref(false)
const generatedImages = ref<ImageGeneration[]>([])
const isSwitchingFrameType = ref(false) // 标志位:是否正在切换帧类型
const loadingImages = ref(false)
const selectedAssetForExtract = ref<number | null>(null) // 选中的素材用于提取尾帧
const extractingFrame = ref(false) // 是否正在提取尾帧
let pollingTimer: any = null
let pollingFrameType: FrameType | null = null // 记录正在轮询的帧类型
@@ -1603,6 +1623,35 @@ const generateFrameImage = async () => {
}
}
// 从素材提取尾帧
const extractLastFrame = async () => {
if (!selectedAssetForExtract.value || !currentStoryboard.value) {
ElMessage.warning('请先选择视频素材')
return
}
extractingFrame.value = true
try {
const result = await assetAPI.extractFrame(selectedAssetForExtract.value, {
position: 'last',
storyboard_id: currentStoryboard.value.id,
frame_type: 'first' // 提取的尾帧用作当前分镜的首帧
})
ElMessage.success('尾帧提取成功,已添加到首帧图片列表')
// 刷新图片列表
await loadStoryboardImages(currentStoryboard.value!.id, 'first')
// 清空选择
selectedAssetForExtract.value = null
} catch (error: any) {
ElMessage.error('提取失败: ' + (error.message || '未知错误'))
} finally {
extractingFrame.value = false
}
}
// 获取状态标签类型
const getStatusType = (status: string) => {
const statusMap: Record<string, any> = {
@@ -2154,7 +2203,13 @@ const handleTimelineSelect = (sceneId: number) => {
}
const handleAddStoryboard = async () => {
ElMessage.info('添加分镜功能开发中')
if (!currentStoryboard.value) {
ElMessage.warning('请先选择分镜')
return
}
// 调用现有的 generateVideo 函数为当前分镜生成视频
await generateVideo()
}
const togglePlay = () => {