auto-save 2026-05-13 21:29 (~7)

This commit is contained in:
2026-05-13 21:30:04 +08:00
parent 2befdf4e40
commit 7b59ed9bf1
7 changed files with 123 additions and 159 deletions

View File

@@ -232,6 +232,20 @@ export default function Home() {
if (!frame) return
const labelOf = (ref?: ImageRef | null, fallback = "未提供") => ref?.label || fallback
const keyframeRef: ImageRef = {
kind: "keyframe",
frame_idx: frameIdx,
label: `分镜 ${frameIdx + 1} 首帧`,
}
const orderedSelected = job.frames
.filter((f) => selectedFrames.has(f.index))
.sort((a, b) => a.timestamp - b.timestamp)
const nextFrame = orderedSelected.find((f) => f.timestamp > frame.timestamp) ?? null
const defaultLastRef: ImageRef | null = nextFrame
? { kind: "keyframe", frame_idx: nextFrame.index, label: `分镜 ${nextFrame.index + 1} 尾帧` }
: null
const firstRef = scene.first_image ?? keyframeRef
const lastRef = scene.last_image ?? defaultLastRef
const duration = scene.duration && scene.duration > 0 ? scene.duration : 5
const sourceScene = frame.description?.scene ? `参考画面识别:${frame.description.scene}` : ""
const sourceStyle = frame.description?.style ? `参考风格:${frame.description.style}` : ""
@@ -245,21 +259,21 @@ export default function Home() {
const sceneDirection = scene.scene?.trim()
|| "借鉴参考画面的构图、可信感和空间层次,但改造成适合 SKG 产品广告的现代家居、办公或零售场景。"
const actionDirection = scene.action?.trim()
|| "一镜到底缓慢推进,先建立画面,再出现自然手部互动,最后停在产品细节或使用状态特写。"
|| "按首帧到尾帧做平滑过渡,动作连续自然,镜头运动稳定,最后准确停在尾帧意图。"
const prompt = [
`竖屏 9:16${duration.toFixed(1)}SKG 产品短视频广告。`,
"直接根据当前分镜关键帧生成视频。必须使用输入的完整视频关键帧作为第一帧和视觉锚点:第一帧构图、主体位置、透视关系和光线方向保持稳定,然后从这一帧自然动起来。",
"生成一段单镜头连续视频,一镜到底,不要跳切,不要突然换场景,不要突然换主体,不要蒙太奇,不要多镜头拼接。",
"使用首帧和尾帧生成连续过渡视频:首帧必须严格作为视频开始画面,尾帧必须作为视频结束目标画面,中间只做自然运动补间。",
"生成一段单镜头连续视频,一镜到底,从首帧平滑过渡到尾帧;不要跳切,不要突然换场景,不要突然换主体,不要蒙太奇,不要多镜头拼接。",
"如果提供了原视频链接,把它只作为节奏、镜头运动、动作顺序和画面调度参考;不要照搬原视频里的品牌、文字、水印、竞品产品或具体人物。",
"时间线0%-25% 保持首帧构图并轻微启动;25%-70% 做一个清晰、缓慢、可信的产品展示动作70%-100% 镜头自然停稳在 SKG 产品或使用效果特写。",
"时间线0%-15% 锁住首帧构图并轻微启动;15%-85% 做平滑连续运动85%-100% 缓慢贴近尾帧并稳定收住。",
`主体改造:${subjectDirection}`,
`产品替换:${productDirection}`,
`场景改造:${sceneDirection}`,
`连续动作和镜头:${actionDirection}`,
`参考主体图槽${labelOf(scene.subject_image, "产品演示主体或手部姿态")}`,
`参考场景图槽${labelOf(scene.scene_image, "现代健康生活场景")}`,
`SKG 产品图槽${labelOf(scene.product_image, "SKG 产品视觉主角")}`,
`参考动作图槽${labelOf(scene.action_image, "自然拿取、佩戴、展示或靠近产品的动作")}`,
`首帧${labelOf(firstRef, "当前分镜关键帧")}`,
`尾帧${labelOf(lastRef, "未指定,按首帧小幅自然运动收尾")}`,
`SKG 产品参考${labelOf(scene.product_image, "SKG 产品视觉主角")}`,
`动作参考:${labelOf(scene.action_image, "自然拿取、佩戴、展示或靠近产品的动作")}`,
sourceScene,
sourceStyle,
sourceObjects,
@@ -270,16 +284,13 @@ export default function Home() {
try {
toast.info(`已提交 ${model} 生视频 · 分镜 ${frameIdx + 1}`)
const keyframeRef: ImageRef = {
kind: "keyframe",
frame_idx: frameIdx,
label: `分镜 ${frameIdx + 1} 关键帧`,
}
const sourceUrl = job.url?.trim()
const updated = await generateStoryboardVideo(job.id, frameIdx, {
prompt,
duration,
subject_image: keyframeRef,
first_image: firstRef,
last_image: lastRef,
subject_image: firstRef,
scene_image: null,
product_image: null,
action_image: null,
@@ -293,7 +304,7 @@ export default function Home() {
} catch (e) {
toast.error("提交视频失败:" + (e instanceof Error ? e.message : String(e)))
}
}, [job, setJob])
}, [job, selectedFrames, setJob])
// URL ?job=xxx,yyy 自动恢复多个 job
useEffect(() => {