feat(seed): add Instagram channel-promo template to FlatRender (local)

- scripts/seed_instagram_promo.py — dedicated seeder for the 5-scene FlexStory IG
  promo (the generic seeder only handles CharacterStory's 2-text-per-scene shape).
  Scenes keyed `<BlockId>__<n>` (render decodes the block from the key); each scene's
  content-elements are that block's real fields (Text + Media); colours as shared.
  render_remotion_comp = FlexStory-<asp> so GetFlexStoryProps routes it.
- public/template-media/InstagramPromo* — thumbnails, per-scene stills, preview MP4.

Applied to local fr2-postgres + fr2-frontend: container fr-instagram-promo (3 aspects,
15 scenes, 138 fields), served by the gateway and the /templates detail page (200).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-25 09:56:40 +03:30
parent 38229185a7
commit 7725c13771
20 changed files with 96 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
# Seeds the Instagram channel-promo FlexStory template into content.* so it appears
# in the local FlatRender studio/templates, editable. Each scene's key is
# "<BlockId>__<n>" (the render boundary decodes the block from the key prefix); the
# scene's content-elements are that block's real fields. render_remotion_comp =
# FlexStory-<asp> so GetFlexStoryProps routes it (internal.go:327).
#
# Usage: python scripts/seed_instagram_promo.py | docker exec -i fr2-postgres psql -U postgres -d flatrender
import uuid
NS = uuid.UUID("11111111-2222-3333-4444-555555555555")
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": "رنگ متن"}
TID, SLUG = "InstagramPromo", "fr-instagram-promo"
NAME = "تبلیغ کانال اینستاگرام"
DESC = "تبلیغ حرفه‌ای صفحهٔ اینستاگرام با نمای واقعی پروفایل (تم روشن)؛ معرفی، نمایش پست‌ها (عکس و ویدیو)، آمار و دعوت به فالو. کاملاً قابل ویرایش و انعطاف‌پذیر."
COLORS = [("accentColor", "#dc2743"), ("secondaryColor", "#7c5cff"), ("backgroundColor", "#f7f4fa"), ("textColor", "#15151a")]
# (blockId, persian scene label, durationSec, [(key, title, default, type)])
T = "Text"; M = "Media"
SCENES = [
("IGIntro", "شروع (لوگوی اینستاگرام)", 3, [
("badge", "نشان", "اینستاگرام", T), ("headline", "تیتر", "صفحهٔ ما را دنبال کنید", T),
("subtitle", "زیرعنوان", "هر روز یک طرح تازه", T)]),
("IGProfile", "صفحهٔ اینستاگرام (واقعی)", 5, [
("headline", "تیتر بالای صفحه", "صفحهٔ ما را دنبال کنید", T), ("handle", "آیدی", "flat.studio", T),
("name", "نام صفحه", "استودیو فلت", T), ("category", "دسته‌بندی", "هنر و طراحی", T),
("bio1", "بیو — خط ۱", "هر روز یک طرح تازه ✨", T), ("bio2", "بیو — خط ۲", "آموزش، قالب و الهام برای طراحان", T),
("bio3", "بیو — خط ۳", "سفارش و دانلود 👇", T), ("link", "لینک", "flat.studio/shop", T),
("posts", "تعداد پست", "۳۲۰", T), ("followers", "دنبال‌کننده", "۲۴٫۸ هزار", T), ("following", "دنبال‌شده", "۱۸۰", T),
("hi1", "هایلایت ۱", "جدید", T), ("hi2", "هایلایت ۲", "قالب‌ها", T), ("hi3", "هایلایت ۳", "آموزش", T), ("hi4", "هایلایت ۴", "نمونه‌کار", T),
("followLabel", "متن دکمهٔ دنبال", "دنبال کردن", T), ("messageLabel", "متن دکمهٔ پیام", "پیام", T),
("avatar", "تصویر پروفایل", "", M),
("post1", "پست شبکه ۱ (عکس/ویدیو)", "", M), ("post2", "پست شبکه ۲ (عکس/ویدیو)", "", M), ("post3", "پست شبکه ۳ (عکس/ویدیو)", "", M),
("post4", "پست شبکه ۴ (عکس/ویدیو)", "", M), ("post5", "پست شبکه ۵ (عکس/ویدیو)", "", M), ("post6", "پست شبکه ۶ (عکس/ویدیو)", "", M)]),
("IGFeed", "نمایش محتوا (شبکهٔ پست‌ها)", 4, [
("caption", "عنوان بخش", "محتوای ما را ببینید", T),
("post1", "پست ۱ (عکس/ویدیو)", "", M), ("post2", "پست ۲ (عکس/ویدیو)", "", M), ("post3", "پست ۳ (عکس/ویدیو)", "", M),
("post4", "پست ۴ (عکس/ویدیو)", "", M), ("post5", "پست ۵ (عکس/ویدیو)", "", M), ("post6", "پست ۶ (عکس/ویدیو)", "", M)]),
("IGStats", "اعتمادسازی (آمار صفحه)", 3, [
("bigValue", "عدد اصلی", "۲۴۸۰۰", T), ("bigLabel", "برچسب عدد اصلی", "دنبال‌کننده", T),
("stat2Value", "آمار ۲ — مقدار", "۱۲۰۰۰۰۰", T), ("stat2Label", "آمار ۲ — برچسب", "پسند", T),
("stat3Value", "آمار ۳ — مقدار", "۳۲۰", T), ("stat3Label", "آمار ۳ — برچسب", "پست", T),
("proofLine", "جملهٔ اعتماد", "به جمع هزاران دنبال‌کنندهٔ ما بپیوندید", T)]),
("IGFollowCTA", "فراخوان دنبال‌کردن", 3, [
("headline", "تیتر فراخوان", "همین حالا دنبال کنید", T), ("handle", "آیدی صفحه", "@flat.studio", T),
("buttonLabel", "متن دکمه", "دنبال کردن", T), ("followedLabel", "متن بعد از دنبال", "دنبال شد", T),
("footer", "زیرنویس", "لینک در بایو 👆", T)]),
]
DUR = sum(s[2] for s in SCENES)
def swatch_svg(colors):
return f'<svg xmlns="http://www.w3.org/2000/svg" width="200" height="40">' + "".join(f'<rect x="{i*50}" y="0" width="50" height="40" fill="{c}"/>' for i, c in enumerate(colors)) + '</svg>'
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>'
out = ["BEGIN;", f"DELETE FROM content.project_containers WHERE slug = {q(SLUG)};"]
cid = uid("c-" + TID)
thumb16 = f"/template-media/{TID}-16x9.png"
preview = f"/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(NAME)},{q(DESC)},{q(thumb16)},{q(preview)},{q(preview)},{q(preview)},TRUE,FALSE,FALSE,'FLEXIBLE',2);")
for (asp, w, h, aspstr) in ASPECTS:
pid = uid(f"p-{TID}-{asp}")
thumb = f"/template-media/{TID}-{asp}.png"
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(preview)},{w},{h},{q(aspstr)},"
f"{DUR},30,'FLEXIBLE','FullHD','Remotion',{q('FlexStory-'+asp)},TRUE,0);")
for sidx, (block, label, dur, fields) in enumerate(SCENES, 1):
skid = uid(f"s-{TID}-{asp}-{sidx}")
skey = f"{block}__{sidx}"
scimg = f"/template-media/{TID}-{asp}-c{sidx}.png"
out.append(
"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(skey)},{q(label)},{q(scimg)},{q(scimg)},{q(preview)},{q(swatch_svg([c for _,c in COLORS]))},{dur},{sidx-1});")
for pos, (k, title, val, typ) 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}-{sidx}-{k}'))},{q(skid)},{q(k)},{q(title)},{q(typ)},{q(val)},{pos},{1 if typ=='Text' else 0});")
for si, (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)},{si});")
out.append("COMMIT;")
out.append(f"SELECT slug, name, primary_mode FROM content.project_containers WHERE slug = {q(SLUG)};")
print("\n".join(out))