feat: plan storyboard frame endpoints
This commit is contained in:
@@ -84,6 +84,20 @@ const PRODUCT_FUSION_NEGATIVE_PROMPT = [
|
||||
"no product passing through the neck, no product inside the transparent body, no x-ray blending, no transparent product, no product becoming bones or skin, no product fused with spine/ribs/throat, no clipping through shoulders, no floating device, no melted device, no deformed U-shape, no wrong body part, no necklace/scarf/headphones/brace, no random replacement product.",
|
||||
].join("\n")
|
||||
|
||||
function storyboardNeedsProduct(scene: StoryboardScene) {
|
||||
if (scene.needs_product === false) return false
|
||||
if (scene.needs_product === true) return true
|
||||
const text = `${scene.visual_mode ?? ""} ${scene.product ?? ""} ${scene.product_placement ?? ""}`.toLowerCase()
|
||||
return !/(不出现产品|不露产品|无需产品|不需要产品|无产品|no product|environment|person_only)/.test(text)
|
||||
}
|
||||
|
||||
function storyboardNeedsSubject(scene: StoryboardScene) {
|
||||
if (scene.needs_subject === false) return false
|
||||
if (scene.needs_subject === true) return true
|
||||
const text = `${scene.visual_mode ?? ""} ${scene.subject ?? ""}`.toLowerCase()
|
||||
return !/(不需要人物|无人物|不出现人物|no person|product_only|environment)/.test(text)
|
||||
}
|
||||
|
||||
// 合并 input + download + split 为一个节点
|
||||
// 分叉:上路 input → visual lab ↘
|
||||
// 下路 input → audio ──────────────────────────→ compose
|
||||
@@ -565,8 +579,10 @@ export default function Home() {
|
||||
: null
|
||||
const firstRef = scene.first_image ?? keyframeRef
|
||||
const lastRef = scene.last_image ?? defaultLastRef
|
||||
let productRefs = (scene.product_images?.length ? scene.product_images : scene.product_image ? [scene.product_image] : [])
|
||||
if (productRefs.length === 0) {
|
||||
const needsProduct = storyboardNeedsProduct(scene)
|
||||
const needsSubject = storyboardNeedsSubject(scene)
|
||||
let productRefs = needsProduct ? (scene.product_images?.length ? scene.product_images : scene.product_image ? [scene.product_image] : []) : []
|
||||
if (needsProduct && productRefs.length === 0) {
|
||||
try {
|
||||
productRefs = await ensureDefaultProductRefs(job.id)
|
||||
} catch (e) {
|
||||
@@ -574,7 +590,7 @@ export default function Home() {
|
||||
return
|
||||
}
|
||||
}
|
||||
const subjectRefs: ImageRef[] = (frame.elements ?? [])
|
||||
const subjectRefs: ImageRef[] = needsSubject ? (frame.elements ?? [])
|
||||
.flatMap((element) => element.subject_assets ?? [])
|
||||
.slice(0, 6)
|
||||
.map((asset) => ({
|
||||
@@ -583,8 +599,8 @@ export default function Home() {
|
||||
element_id: asset.id,
|
||||
cutout_id: asset.id,
|
||||
label: asset.label,
|
||||
}))
|
||||
const primarySubjectRef = subjectRefs[0] ?? firstRef
|
||||
})) : []
|
||||
const primarySubjectRef = needsSubject ? (subjectRefs[0] ?? firstRef) : null
|
||||
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}` : ""
|
||||
@@ -607,31 +623,47 @@ export default function Home() {
|
||||
].join("\n")
|
||||
const prompt = [
|
||||
`竖屏 9:16,${duration.toFixed(1)} 秒,SKG 产品短视频广告。`,
|
||||
productNature,
|
||||
productRefs.length
|
||||
needsProduct
|
||||
? productNature
|
||||
: "本条分镜规划为非产品主镜头:可以只拍人物状态、场景过渡、情绪停点或节奏承接。不要硬插 SKG 产品、白底产品图、包装或任何随机商品。",
|
||||
needsProduct && productRefs.length
|
||||
? `已上传 ${productRefs.length} 张 SKG 真实产品参考图。产品参考图是唯一产品真源:视频中出现的产品必须严格匹配这些图的外观、颜色、材质、结构比例和关键细节。`
|
||||
: "未上传产品图时,仍需生成一个干净高级的 SKG 产品广告画面,但不得保留原视频里的竞品包装或平台元素。",
|
||||
"首帧和尾帧只用于控制画面起止、构图、场景和动作方向;如果首尾帧里有竞品、文字包装或非 SKG 产品,必须替换为上传的 SKG 产品参考。",
|
||||
: needsProduct
|
||||
? "未上传产品图时,仍需生成一个干净高级的 SKG 产品广告画面,但不得保留原视频里的竞品包装或平台元素。"
|
||||
: "本条不传产品参考图;如首尾帧里出现竞品、包装或非 SKG 商品,应弱化、移除或作为模糊背景,不要替换成 SKG 产品。",
|
||||
needsProduct
|
||||
? "首帧和尾帧只用于控制画面起止、构图、场景和动作方向;如果首尾帧里有竞品、文字包装或非 SKG 产品,必须替换为上传的 SKG 产品参考。"
|
||||
: "首帧和尾帧用于控制画面起止、构图、场景和动作方向;本条没有产品任务,不要因为广告语而自动添加产品。",
|
||||
"使用首帧和尾帧生成连续过渡视频:首帧必须严格作为视频开始画面,尾帧必须作为视频结束目标画面,中间只做自然运动补间。",
|
||||
"生成一段单镜头连续视频,一镜到底,从首帧平滑过渡到尾帧;不要跳切,不要突然换场景,不要突然换主体,不要蒙太奇,不要多镜头拼接。",
|
||||
"如果提供了原视频链接,把它只作为节奏、镜头运动、动作顺序和画面调度参考;不要照搬原视频里的品牌、文字、水印、竞品产品或具体人物。",
|
||||
"时间线:0%-15% 锁住首帧构图并轻微启动;15%-85% 做平滑连续运动;85%-100% 缓慢贴近尾帧并稳定收住。",
|
||||
TRANSPARENT_HUMAN_VIDEO_PROMPT,
|
||||
`镜头类型:${scene.visual_mode ?? "未标注"};需要人物=${needsSubject ? "是" : "否"};需要产品=${needsProduct ? "是" : "否"}。`,
|
||||
scene.first_frame_plan ? `首帧规划:${scene.first_frame_plan}` : "",
|
||||
scene.last_frame_plan ? `尾帧规划:${scene.last_frame_plan}` : "",
|
||||
scene.product_placement ? `产品出现方式:${scene.product_placement}` : "",
|
||||
needsSubject
|
||||
? TRANSPARENT_HUMAN_VIDEO_PROMPT
|
||||
: "本条不传人物主体参考图;如果画面需要人物,只能作为背景、手部局部或模糊生活方式元素,不要生成主角式透明骨架人。",
|
||||
`主体改造:${subjectDirection}`,
|
||||
`产品替换:${productDirection} 产品必须作为颈部/肩颈按摩仪被正确佩戴或展示,不要放在脸上、手臂上、桌面当摆件,也不要变成瓶子、面霜、医疗设备或食品。`,
|
||||
needsProduct
|
||||
? `产品替换:${productDirection} 产品必须作为颈部/肩颈按摩仪被正确佩戴或展示,不要放在脸上、手臂上、桌面当摆件,也不要变成瓶子、面霜、医疗设备或食品。`
|
||||
: `产品处理:${productDirection} 本条不需要露出 SKG 产品,不要硬插产品、包装、瓶罐、医疗器械或随机商品。`,
|
||||
`场景改造:${sceneDirection}`,
|
||||
`连续动作和镜头:${actionDirection}`,
|
||||
`首帧:${labelOf(firstRef, "当前分镜关键帧")}`,
|
||||
`尾帧:${labelOf(lastRef, "未指定,按首帧小幅自然运动收尾")}`,
|
||||
`SKG 产品参考:${productRefs.length ? productRefs.map((ref, i) => `${i + 1}. ${labelOf(ref, "SKG 产品角度")}`).join(";") : "SKG 产品视觉主角"}`,
|
||||
subjectRefs.length ? `关键元素 6 视图参考:${subjectRefs.map((ref, i) => `${i + 1}. ${labelOf(ref, "元素视图")}`).join(";")}` : "如果该分镜还没有关键元素 6 视图,优先使用首帧主体关系生成。",
|
||||
needsProduct ? `SKG 产品参考:${productRefs.length ? productRefs.map((ref, i) => `${i + 1}. ${labelOf(ref, "SKG 产品角度")}`).join(";") : "SKG 产品视觉主角"}` : "SKG 产品参考:本条不使用产品参考图。",
|
||||
needsSubject
|
||||
? (subjectRefs.length ? `关键元素 6 视图参考:${subjectRefs.map((ref, i) => `${i + 1}. ${labelOf(ref, "元素视图")}`).join(";")}` : "如果该分镜还没有关键元素 6 视图,优先使用首帧主体关系生成。")
|
||||
: "关键元素 6 视图参考:本条不使用人物主体参考图。",
|
||||
sourceScene,
|
||||
sourceStyle,
|
||||
sourceObjects,
|
||||
"产品一致性要求:整个视频只能出现同一个白色 U 形 SKG 颈部按摩仪或同一套包装;不要生成第二种产品,不要改变 U 形机身、金属按摩触点、侧边按键、白色材质和整体比例,不要凭空增加屏幕、线缆、文字标签或说明书。",
|
||||
"产品呈现要求:至少一次让产品在脖子/肩颈位置清晰占据视觉中心,边缘清楚、材质真实、比例可信;手部接触产品时不要遮挡关键外观,产品不能融化、扭曲、穿帮或漂移。",
|
||||
"状态改善要求:画面应形成明确的使用前后感受变化:使用前可以是低头久坐、揉脖子、肩颈疲惫或紧绷;使用后变为肩颈放松、抬头、动作舒展、精神更好。人形骷髅也可以表现为从僵硬难受变轻松放松。表达舒缓和放松,不要承诺治疗。",
|
||||
"运动要求:动作幅度小而连续,速度均匀,手部和产品位置前后一致,产品外形不变形,人物表情和姿态不漂移,背景只允许轻微景深和光影变化。",
|
||||
needsProduct ? "产品一致性要求:整个视频只能出现同一个白色 U 形 SKG 颈部按摩仪或同一套包装;不要生成第二种产品,不要改变 U 形机身、金属按摩触点、侧边按键、白色材质和整体比例,不要凭空增加屏幕、线缆、文字标签或说明书。" : "",
|
||||
needsProduct ? "产品呈现要求:至少一次让产品在脖子/肩颈位置清晰占据视觉中心,边缘清楚、材质真实、比例可信;手部接触产品时不要遮挡关键外观,产品不能融化、扭曲、穿帮或漂移。" : "",
|
||||
needsSubject || needsProduct ? "状态改善要求:画面应形成明确的使用前后感受变化:使用前可以是低头久坐、揉脖子、肩颈疲惫或紧绷;使用后变为肩颈放松、抬头、动作舒展、精神更好。人形骷髅也可以表现为从僵硬难受变轻松放松。表达舒缓和放松,不要承诺治疗。" : "节奏要求:作为过渡镜头时只负责情绪、空间和节奏承接,不承诺疗效,不强行展示使用动作。",
|
||||
needsProduct ? "运动要求:动作幅度小而连续,速度均匀,手部和产品位置前后一致,产品外形不变形,人物表情和姿态不漂移,背景只允许轻微景深和光影变化。" : "运动要求:动作幅度小而连续,速度均匀,构图从首帧自然过渡到尾帧,不突然添加人物或产品。",
|
||||
"商业质感:真实拍摄感,干净高级,柔和稳定打光,产品边缘清晰,材质真实,画面无抖动、无拉伸、无闪烁。",
|
||||
"禁止:字幕、文字、平台 UI、TikTok 水印、logo 水印、免责声明、竞品包装、随机新物体、非 SKG 产品、医学骨架、夸张病症画面、恐怖元素、画面撕裂、人物或产品突然变形。",
|
||||
TRANSPARENT_HUMAN_NEGATIVE_PROMPT,
|
||||
@@ -649,7 +681,7 @@ export default function Home() {
|
||||
subject_image: primarySubjectRef,
|
||||
subject_images: subjectRefs,
|
||||
scene_image: null,
|
||||
product_image: productRefs[0] ?? null,
|
||||
product_image: needsProduct ? (productRefs[0] ?? null) : null,
|
||||
action_image: null,
|
||||
source_ref: sourceUrl ? { kind: "source_video", url: sourceUrl } : null,
|
||||
model,
|
||||
|
||||
Reference in New Issue
Block a user