feat(seed): per-scene loop video + thumbnail for every scene

Each scene now carries both a still (content.scenes.image) AND a short ~1.5s LOOP
video (content.scenes.demo), so the studio scene cards show a looping preview, not
a static swatch.

- LOOP_SCENES = {CharacterStory, LogoMotion3D}: their scenes get a dedicated
  per-scene loop ({tid}-{asp}-c{n}-loop.mp4 / {tid}-{asp}-loop.mp4); other
  templates fall back to their full preview MP4.
- Renders 42 loops: LogoMotion3D ×3 (frames 30–74) + CharacterStory 13 scenes ×3
  aspects (frames (n-1)*90+20 … +64), each a 45-frame / 1.5s clip mid-scene.
- Seed sets image + snapshot_url + demo per scene; verified all 42 serve 200 and
  the DB wires each scene to its own still + loop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-24 21:09:04 +03:30
parent 825f25be55
commit e4fd936953
43 changed files with 11 additions and 5 deletions
+11 -5
View File
@@ -101,6 +101,10 @@ TEXTCOLORS = {
# the matching render, not the 16:9 cropped). Others reuse the single 16:9 preview.
PERASPECT_VIDEO = {"AppShowcase3D", "CharacterStory", "LogoMotion3D"}
# Templates that ship a short ~1.5s LOOP video PER SCENE (scene.demo), so the studio
# scene cards play a looping preview. Others fall back to the full preview MP4.
LOOP_SCENES = {"CharacterStory", "LogoMotion3D"}
# Templates whose content is split across MANY scenes (key c1..cN), one editable
# scene card per beat. value = scene count; texts are assigned 2-per-scene in order.
MULTISCENE = {"CharacterStory": len(CS_BEATS)}
@@ -143,18 +147,20 @@ for idx, (tid, slug, name, desc, dur, texts, (accent, sec, bg)) in enumerate(T):
# one editable scene card per beat; 2 text fields (title+caption) each.
for sc in range(1, nscenes + 1):
skid = uid(f"s-{tid}-{asp}-{sc}")
scimg = f"{MINIO}/template-media/{tid}-{asp}-c{sc}.png" # per-scene rendered still
scimg = f"{MINIO}/template-media/{tid}-{asp}-c{sc}.png" # per-scene still
scdemo = f"{MINIO}/template-media/{tid}-{asp}-c{sc}-loop.mp4" if tid in LOOP_SCENES else pvideo
out.append(
"INSERT INTO content.scenes (id,project_id,key,title,image,snapshot_url,scene_color_svg,default_duration_sec,sort) VALUES ("
f"{q(skid)},{q(pid)},{q('c'+str(sc))},{q('صحنه '+str(sc))},{q(scimg)},{q(scimg)},{q(swatch_svg([accent,sec,bg,txt]))},{SCENE_SECONDS},{sc-1});")
"INSERT INTO content.scenes (id,project_id,key,title,image,snapshot_url,demo,scene_color_svg,default_duration_sec,sort) VALUES ("
f"{q(skid)},{q(pid)},{q('c'+str(sc))},{q('صحنه '+str(sc))},{q(scimg)},{q(scimg)},{q(scdemo)},{q(swatch_svg([accent,sec,bg,txt]))},{SCENE_SECONDS},{sc-1});")
for pos, (k, title, val) in enumerate(texts[(sc - 1) * 2: sc * 2]):
out.append(
"INSERT INTO content.scene_content_elements (id,scene_id,key,title,type,default_value,position_in_container,direction_layer_value) VALUES ("
f"{q(uid(f'ce-{tid}-{asp}-{k}'))},{q(skid)},{q(k)},{q(title)},'Text',{q(val)},{pos},1);")
else:
scdemo = f"{MINIO}/template-media/{tid}-{asp}-loop.mp4" if tid in LOOP_SCENES else pvideo
out.append(
"INSERT INTO content.scenes (id,project_id,key,title,image,snapshot_url,scene_color_svg,default_duration_sec,sort) VALUES ("
f"{q(sid)},{q(pid)},'c1','صحنه ۱',{q(thumb)},{q(thumb)},{q(swatch_svg([accent,sec,bg,txt]))},{dur},0);")
"INSERT INTO content.scenes (id,project_id,key,title,image,snapshot_url,demo,scene_color_svg,default_duration_sec,sort) VALUES ("
f"{q(sid)},{q(pid)},'c1','صحنه ۱',{q(thumb)},{q(thumb)},{q(scdemo)},{q(swatch_svg([accent,sec,bg,txt]))},{dur},0);")
for pos, (k, title, val) in enumerate(texts):
out.append(
"INSERT INTO content.scene_content_elements (id,scene_id,key,title,type,default_value,position_in_container,direction_layer_value) VALUES ("