f8ea9af3b6
Closes the render boundary so a user's scene list (order, per-scene content,
per-scene duration, theme) actually drives the FlexStory engine — the one gap the
scene-engine mapping found.
- render-svc GetFlexStoryProps (db.go): structured per-scene query that groups
saved_scene_contents BY scene (the flat GetRenderBindings union collides when
scenes share keys like "title"), recovers blockId from the scene key
("<BlockId>__<n>"), and emits the FlexStory props object
{scenes:[{blockId,durationSec,props}], accentColor, …}.
- render-svc Claim (internal.go): when the template is Remotion + comp starts with
"FlexStory", send that object as a single "__flexprops__" binding (no protocol
struct change).
- node-agent remotionProps (remotion.go): if "__flexprops__" is present, pass it
to `remotion render --props` verbatim (it's the complete props object).
- scripts/seed_flexstory.py: seeds the CharacterJourney template (7 scenes, theme
colours, FLEXIBLE) with blockId-encoded scene keys, so the studio's existing
CopyTemplateGraphAsync copies them into saved_scenes with zero studio-svc change.
Both Go services compile; template is live in the catalog (detail 200, per-aspect
previews). End-to-end render verification needs a live Remotion render node.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
6.4 KiB
Python
90 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
# Seeds FlexStory (scene-engine) templates into content.* so they appear on the
|
|
# site AND render through the new render-svc passthrough (GetFlexStoryProps).
|
|
#
|
|
# A FlexStory template = one container + 3 aspect projects (render_engine=Remotion,
|
|
# render_remotion_comp=FlexStory-<asp>, choose_mode=FLEXIBLE). Each scene's KEY
|
|
# encodes its block: "<BlockId>__<n>" — render-svc strips "__n" to recover blockId,
|
|
# and the per-scene content elements become that block's props. The studio's
|
|
# existing CopyTemplateGraphAsync copies key+contents+durations into saved_scenes,
|
|
# so no studio-svc change is needed.
|
|
#
|
|
# Usage: PYTHONIOENCODING=utf-8 python scripts/seed_flexstory.py | docker exec -i fr2-postgres psql -U postgres -d flatrender
|
|
import uuid
|
|
|
|
MINIO = "" # assets served from the Next app's public/ (relative URLs)
|
|
NS = uuid.UUID("11111111-2222-3333-4444-666666666666")
|
|
def uid(s): return str(uuid.uuid5(NS, s))
|
|
def q(s): return "'" + str(s).replace("'", "''") + "'"
|
|
|
|
ASPECTS = [("16x9", 1920, 1080, "16:9"), ("1x1", 1080, 1080, "1:1"), ("9x16", 1080, 1920, "9:16")]
|
|
CTITLES = {"accentColor": "رنگ اصلی", "secondaryColor": "رنگ دوم", "backgroundColor": "پسزمینه", "textColor": "رنگ متن"}
|
|
|
|
def icon_svg(hexv):
|
|
return f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="{hexv}"/></svg>'
|
|
|
|
# A FlexStory template: id, slug, name, desc, theme colors, and a scene list.
|
|
# Each scene: (blockId, default_sec, min_sec, max_sec, [(key, type, value)]).
|
|
TEMPLATES = [
|
|
{
|
|
"tid": "CharacterJourney", "slug": "fr-character-journey",
|
|
"name": "سفر شخصیت (موتور صحنهای)",
|
|
"desc": "قالب داستانگویی منعطف با شخصیت؛ صحنهها قابل افزودن، حذف، جابهجایی و تنظیم مدت. «از یک ایده تا واقعیت».",
|
|
"colors": ("#cf8a76", "#6f9d96", "#ece4d6", "#2b3a55"),
|
|
"scenes": [
|
|
("TitleCard", 4, 2, 8, [("kicker","Text","داستان شما"),("title","Text","از یک ایده تا واقعیت"),("subtitle","Text","چطور ایدهات را به یک ویدیوی حرفهای تبدیل میکنی")]),
|
|
("CharacterScene", 3, 1, 6, [("title","Text","یک ایده"),("caption","Text","همهچیز با یک جرقهٔ کوچک شروع شد"),("character","Media","illustrations/dicebear/openpeeps-04.svg"),("prop","Text","cup")]),
|
|
("CharacterScene", 3, 1, 6, [("title","Text","اما سخت بود"),("caption","Text","ساختن یک ویدیوی حرفهای پیچیده بهنظر میرسید"),("character","Media","illustrations/dicebear/openpeeps-11.svg"),("prop","Text","none")]),
|
|
("CharacterScene", 3, 1, 6, [("title","Text","تا اینکه…"),("caption","Text","با فلترندر آشنا شدم"),("character","Media","illustrations/dicebear/openpeeps-21.svg"),("prop","Text","laptop")]),
|
|
("Slideshow", 6, 3, 12, [("title","Text","چرا فلترندر؟"),("slide1","Text","ساخت در چند دقیقه"),("slide2","Text","هزینهٔ بسیار پایین"),("slide3","Text","کیفیت حرفهای"),("slide4","Text","")]),
|
|
("CharacterScene", 3, 1, 6, [("title","Text","و حالا…"),("caption","Text","داستان خودم را میسازم"),("character","Media","illustrations/dicebear/openpeeps-27.svg"),("prop","Text","plant")]),
|
|
("OutroCTA", 4, 2, 8, [("brandText","Text","فلترندر"),("tagline","Text","همین حالا داستان خود را بساز"),("cta","Text","رایگان شروع کن")]),
|
|
],
|
|
},
|
|
]
|
|
|
|
out = ["BEGIN;"]
|
|
slugs = ",".join(q(t["slug"]) for t in TEMPLATES)
|
|
out.append(f"DELETE FROM content.project_containers WHERE slug IN ({slugs});")
|
|
|
|
for ti, tpl in enumerate(TEMPLATES):
|
|
tid, slug = tpl["tid"], tpl["slug"]
|
|
accent, sec, bg, txt = tpl["colors"]
|
|
colors = [("accentColor", accent), ("secondaryColor", sec), ("backgroundColor", bg), ("textColor", txt)]
|
|
dur_total = sum(s[1] for s in tpl["scenes"])
|
|
cid = uid("c-" + tid)
|
|
thumb16 = f"{MINIO}/template-media/{tid}-16x9.png"
|
|
preview = f"{MINIO}/template-media/{tid}.mp4"
|
|
out.append(
|
|
"INSERT INTO content.project_containers (id,tenant_id,slug,name,description,image,demo,full_demo,mini_demo,"
|
|
"is_published,is_premium,is_mockup,primary_mode,sort) VALUES ("
|
|
f"{q(cid)},NULL,{q(slug)},{q(tpl['name'])},{q(tpl['desc'])},{q(thumb16)},{q(preview)},{q(preview)},{q(preview)},"
|
|
f"TRUE,FALSE,FALSE,'FLEXIBLE',{ti});")
|
|
for (asp, w, h, aspstr) in ASPECTS:
|
|
pid = uid(f"p-{tid}-{asp}")
|
|
thumb = f"{MINIO}/template-media/{tid}-{asp}.png"
|
|
pvideo = f"{MINIO}/template-media/{tid}-{asp}.mp4"
|
|
out.append(
|
|
"INSERT INTO content.projects (id,container_id,name,image,full_demo,original_width,original_height,aspect,"
|
|
"project_duration_sec,free_fps,choose_mode,resolution,render_engine,render_remotion_comp,is_published,sort) VALUES ("
|
|
f"{q(pid)},{q(cid)},{q(aspstr)},{q(thumb)},{q(pvideo)},{w},{h},{q(aspstr)},"
|
|
f"{dur_total},30,'FLEXIBLE','FullHD','Remotion',{q('FlexStory-'+asp)},TRUE,0);")
|
|
for si, (blockId, dsec, dmin, dmax, fields) in enumerate(tpl["scenes"]):
|
|
skey = f"{blockId}__{si+1}" # blockId encoded in the unique key
|
|
sid = uid(f"s-{tid}-{asp}-{si}")
|
|
out.append(
|
|
"INSERT INTO content.scenes (id,project_id,key,title,default_duration_sec,min_duration_sec,max_duration_sec,sort) VALUES ("
|
|
f"{q(sid)},{q(pid)},{q(skey)},{q(blockId)},{dsec},{dmin},{dmax},{si});")
|
|
for pos, (k, typ, val) in enumerate(fields):
|
|
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}-{si}-{k}'))},{q(sid)},{q(k)},{q(k)},{q(typ)},{q(val)},{pos},1);")
|
|
for ci, (k, hexv) in enumerate(colors):
|
|
out.append(
|
|
"INSERT INTO content.shared_colors (id,project_id,element_key,title,icon,attr_value,default_color,sort) VALUES ("
|
|
f"{q(uid(f'sc-{tid}-{asp}-{k}'))},{q(pid)},{q(k)},{q(CTITLES[k])},{q(icon_svg(hexv))},'fill',{q(hexv)},{ci});")
|
|
|
|
out.append("COMMIT;")
|
|
out.append("SELECT count(*) AS flexstory_containers FROM content.project_containers WHERE slug LIKE 'fr-character-journey';")
|
|
print("\n".join(out))
|