Files
makelore/src/pages/ImageCanvas/video-first-frame.ts

105 lines
3.6 KiB
TypeScript
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.

import type {
DesignAsset,
DesignSpecificationValues,
DesignUserFieldOperation,
DesignWorkspace,
} from '../../../shared/image-workspace';
import {
insertReferenceToken,
promptReferencesImage,
} from './reference-tokens';
type ReferenceValue = DesignSpecificationValues['references'][number];
function createReferenceId(): string {
const random = globalThis.crypto?.randomUUID?.()
?? `${Date.now()}-${Math.random().toString(16).slice(2)}`;
return `reference-${random}`.slice(0, 64);
}
export function creatorPromptForWorkspace(workspace: DesignWorkspace): string {
const values = workspace.form.specification.values;
return values.content.concept?.trim()
|| values.content.narrative?.trim()
|| [
...values.content.subjects.map((subject) => subject.description.trim()),
values.content.action?.trim(),
values.content.environment?.trim(),
values.content.emotional_tone?.trim(),
].filter((value): value is string => Boolean(value)).join(',');
}
/**
* Build the explicit field changes for using an existing image as a video's
* first frame. The asset is only bound to the design; it is never copied or
* deleted, and existing reference rows stay in place so @图片 numbers remain
* stable.
*/
export function createVideoFirstFrameOperations(
workspace: DesignWorkspace,
asset: DesignAsset,
promptOverride?: string,
): { operations: DesignUserFieldOperation[]; nextPrompt: string; referenceIndex: number } {
if (asset.mediaType !== 'image') {
throw new Error('视频开始画面必须使用图片');
}
const values = workspace.form.specification.values;
const references = values.references;
const currentFirstFrameIndex = references.findIndex((reference) => (
reference.role === 'first_frame'
|| reference.asset_id === values.video.first_frame_asset_id
));
const selectedReferenceIndex = references.findIndex((reference) => (
reference.asset_id === asset.assetId
));
let nextReferences: ReferenceValue[];
let referenceIndex: number;
if (selectedReferenceIndex >= 0) {
referenceIndex = selectedReferenceIndex;
nextReferences = references.map((reference, index) => {
if (index === referenceIndex) return { ...reference, role: 'first_frame' };
if (index === currentFirstFrameIndex && index !== referenceIndex) {
return { ...reference, role: 'inspiration' };
}
return reference;
});
} else if (currentFirstFrameIndex >= 0) {
referenceIndex = currentFirstFrameIndex;
nextReferences = references.map((reference, index) => (
index === referenceIndex
? { ...reference, asset_id: asset.assetId, asset_revision: null, role: 'first_frame' }
: reference
));
} else {
referenceIndex = references.length;
nextReferences = [
...references,
{
id: createReferenceId(),
asset_id: asset.assetId,
asset_revision: null,
role: 'first_frame',
preserve: [],
adapt: [],
do_not_copy: [],
reviewed_observations: [],
},
];
}
const prompt = promptOverride ?? creatorPromptForWorkspace(workspace);
const inserted = promptReferencesImage(prompt, referenceIndex)
? { value: prompt }
: insertReferenceToken(prompt, referenceIndex, null);
const operations: DesignUserFieldOperation[] = [
{ kind: 'set', path: 'references', value: nextReferences },
{ kind: 'set', path: 'video.first_frame_asset_id', value: asset.assetId },
];
if (inserted.value.trim() !== prompt.trim()) {
operations.push({ kind: 'set', path: 'content.concept', value: inserted.value.trim() });
}
return { operations, nextPrompt: inserted.value.trim(), referenceIndex };
}