auto-save 2026-05-13 21:29 (~7)
This commit is contained in:
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user