补齐画板视频封面持久化

生成画板视频时截取首帧并上传为封面

将视频生成结果写入素材库并保留 thumbnailSrc

同步后端契约、SpacetimeDB 绑定、前端素材库和画布层映射

补充相关测试与文档说明
This commit is contained in:
2026-06-28 00:22:59 +08:00
parent 5976c2b337
commit 63790e22ef
24 changed files with 348 additions and 65 deletions
@@ -1097,6 +1097,18 @@ describe('editorProjectClient', () => {
taskId: 'video-task-1',
durationSeconds: 5,
resolution: '480p',
thumbnailSrc: '/generated-editor-videos/video-task-1/cover.png',
asset: {
assetId: 'asset-video-1',
folderId: 'user-1:asset-folder:project',
label: '生成视频 1',
imageSrc: '/generated-editor-videos/video-task-1/preview.mp4',
thumbnailSrc: '/generated-editor-videos/video-task-1/cover.png',
width: 1280,
height: 720,
sourceType: 'generated',
assetKind: 'video',
},
});
const result = await generateEditorVideo({
@@ -1107,9 +1119,18 @@ describe('editorProjectClient', () => {
resolution: '480p',
mode: 'std',
sound: 'off',
assetFolderId: 'project',
assetLabel: '生成视频 1',
});
expect(result.taskId).toBe('video-task-1');
expect(result.thumbnailSrc).toBe(
'/generated-editor-videos/video-task-1/cover.png',
);
expect(result.asset?.assetId).toBe('asset-video-1');
expect(result.asset?.thumbnailSrc).toBe(
'/generated-editor-videos/video-task-1/cover.png',
);
expect(requestJsonMock).toHaveBeenCalledWith(
'/api/editor/videos/generations',
expect.objectContaining({
@@ -1124,6 +1145,8 @@ describe('editorProjectClient', () => {
mode: 'std',
sound: 'off',
webSearchEnabled: true,
assetFolderId: 'project',
assetLabel: '生成视频 1',
}),
}),
'生成视频失败',
@@ -97,6 +97,7 @@ export type EditorAssetSnapshot = {
folderId: string;
label: string;
imageSrc: string;
thumbnailSrc?: string | null;
objectKey?: string | null;
assetObjectId?: string | null;
width: number;
@@ -329,10 +330,13 @@ export type EditorVideoGenerationInput = {
generationInputs?: EditorAssetGenerationInputs | null;
sourceResourceId?: string | null;
assetKind?: string | null;
assetFolderId?: string | null;
assetLabel?: string | null;
};
export type EditorVideoGenerationResult = {
videoSrc: string;
thumbnailSrc?: string | null;
objectKey?: string | null;
assetObjectId?: string | null;
width: number;
@@ -343,8 +347,10 @@ export type EditorVideoGenerationResult = {
model: string;
provider: string;
taskId: string;
durationSeconds: number;
priceMudPoints?: number;
project?: EditorProjectSnapshot | null;
asset?: EditorAssetSnapshot | null;
queueState?: ExternalGenerationJobStatusRecord | null;
};
@@ -912,6 +918,8 @@ export async function generateEditorVideo(input: EditorVideoGenerationInput) {
? { sourceResourceId: input.sourceResourceId }
: {}),
...(input.assetKind ? { assetKind: input.assetKind } : {}),
...(input.assetFolderId ? { assetFolderId: input.assetFolderId } : {}),
...(input.assetLabel ? { assetLabel: input.assetLabel } : {}),
}),
'生成视频失败',
{