863b9503b3
- Template detail page now shows the render matching the SELECTED aspect (poster +
preview video) instead of the 16:9 cover cropped into a 9:16/1:1 box. TemplateVariant
carries per-aspect image/previewVideo; fetchTemplateVariants + the detail page wire them.
- AppShowcase3D ships a distinct preview video per aspect (seed PERASPECT_VIDEO).
- Frontend Dockerfile: Alpine -> node:20-slim (glibc). Fixes next-swc ("ld-linux..."
load failure that broke `next build` once libc6-compat was removed) AND the original
CI Alpine-CDN issue. Healthcheck switched to node (slim has no wget).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
126 lines
12 KiB
Python
126 lines
12 KiB
Python
#!/usr/bin/env python3
|
||
# Generates SQL to seed the 10 branded Remotion templates into content.* so they
|
||
# appear on the site. Each template -> 1 container + 3 aspect projects, each with
|
||
# a scene, Persian text content-elements (bindable -> Remotion props) and shared
|
||
# colours (bindable -> colour props) + a per-scene colour-swatch SVG.
|
||
#
|
||
# Usage: python scripts/seed_remotion_templates.py | docker exec -i fr2-postgres psql -U postgres -d flatrender
|
||
import uuid
|
||
|
||
# Assets are served from the Next app's public/ folder (relative URLs), so this
|
||
# works regardless of MinIO/object-store availability.
|
||
MINIO = ""
|
||
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": "رنگ متن"}
|
||
|
||
# id, slug, name(fa), desc(fa), dur, [(textKey,title,value)], (accent,secondary,bg)
|
||
T = [
|
||
("LogoMotion","fr-logo-motion","موشن لوگو","نمایش حرفهای لوگو و نام برند با درخشش و حرکت",5,
|
||
[("brandText","متن لوگو","فلترندر"),("tagline","شعار","موشن، ساده و حرفهای")],("#3ba7ff","#a855f7","#04060f")),
|
||
("Opener","fr-opener","تیتراژ آغازین","شروع سینمایی برای ویدیو با عنوان و زیرعنوان",5,
|
||
[("kicker","پیشمتن","تقدیم میکند"),("title","عنوان","یک شروع تازه"),("subtitle","زیرعنوان","داستان شما از همینجا آغاز میشود")],("#22d3ee","#6366f1","#0a0a12")),
|
||
("InstaPromo","fr-insta-promo","تبلیغ پیج اینستاگرام","معرفی و تبلیغ صفحهٔ اینستاگرام با دعوت به فالو",5,
|
||
[("handle","آیدی پیج","@flatrender"),("headline","عنوان","پیج ما را دنبال کنید"),("subtext","توضیح","هر روز محتوای تازه و الهامبخش"),("cta","دکمه","فالو کنید")],("#fb7185","#f59e0b","#140a12")),
|
||
("YouTubeIntro","fr-youtube-intro","اینترو کانال یوتیوب","اینترو حرفهای کانال یوتیوب با دکمهٔ سابسکرایب",5,
|
||
[("channelName","نام کانال","کانال فلترندر"),("subtitle","زیرعنوان","آموزش، ترفند و انگیزه"),("cta","دکمه","سابسکرایب کنید")],("#ff4d4d","#a855f7","#0c0810")),
|
||
("Slideshow","fr-slideshow","اسلایدشو","نمایش پشتسرهم چند پیام یا ویژگی بهصورت اسلاید",9,
|
||
[("title","عنوان","چرا فلترندر؟"),("slide1","اسلاید ۱","ساخت ویدیو در چند دقیقه"),("slide2","اسلاید ۲","بدون نیاز به دانش فنی"),("slide3","اسلاید ۳","خروجی با کیفیت حرفهای")],("#34d399","#3b82f6","#060b0a")),
|
||
("HappyBirthday","fr-happy-birthday","تولدت مبارک","کارت تبریک تولد با کاغذرنگی و نام شخص",6,
|
||
[("greeting","تبریک","تولدت مبارک"),("name","نام","سارا"),("message","پیام","بهترینها را برایت آرزومندیم 🎉")],("#fb7185","#fde047","#140a18")),
|
||
("SalePromo","fr-sale-promo","فروش ویژه","بنر تبلیغاتی فروش و تخفیف با دعوت به خرید",5,
|
||
[("badge","نشان تخفیف","۵۰٪ تخفیف"),("headline","عنوان","فروش ویژهٔ پایان فصل"),("subtext","توضیح","فقط تا پایان همین هفته"),("cta","دکمه","همین حالا خرید کنید")],("#f59e0b","#fb7185","#120a08")),
|
||
("QuoteCard","fr-quote-card","کارت نقلقول","نمایش جملهٔ انگیزشی یا نقلقول با نام گوینده",6,
|
||
[("quote","نقلقول","موفقیت، مجموع تلاشهای کوچکِ هر روز است."),("author","گوینده","فلترندر")],("#22d3ee","#6366f1","#0a0a12")),
|
||
("EventInvite","fr-event-invite","دعوتنامهٔ رویداد","دعوتنامهٔ شیک برای رویداد با تاریخ و مکان",6,
|
||
[("kicker","پیشمتن","دعوتنامه"),("eventTitle","عنوان رویداد","همایش سالانهٔ نوآوری"),("date","تاریخ","۱۵ مهر ۱۴۰۳"),("location","مکان","تهران، سالن همایشها"),("cta","دکمه","ثبتنام کنید")],("#a855f7","#3ba7ff","#0a0814")),
|
||
("Countdown","fr-countdown","شمارش معکوس","شمارش معکوس هیجانانگیز برای شروع یک رویداد",8,
|
||
[("title","عنوان","شروع رویداد تا"),("startNumber","عدد شروع","5"),("goText","متن پایان","شروع!"),("subtitle","زیرعنوان","آمادهاید؟")],("#3ba7ff","#22d3ee","#04060f")),
|
||
("GlitterReveal","fr-glitter-reveal","نمایش لوگو با غبار درخشان","نمایش جادویی لوگو با ذرات درخشان؛ لوگو و متن قابل ویرایش",6,
|
||
[("brandText","نام برند","فلترندر"),("tagline","شعار","موشن، ساده و حرفهای")],("#3ba7ff","#a855f7","#05040e")),
|
||
("NowruzGreeting","fr-nowruz","تبریک نوروز","صحنهٔ بهاری نوروز با شخصیتهای متحرک؛ حاجیفیروز، ماهی قرمز و سبزه",8,
|
||
[("greeting","متن تبریک","نوروز مبارک"),("subtitle","زیرعنوان","سال نو پیروز و شادمان"),("message","پیام / سال","۱۴۰۶")],("#f5b942","#e23b3b","#1fb6b0")),
|
||
("Hero3D","fr-hero-3d","نمایش سهبعدی برند","نمایش حرفهای و سهبعدی لوگو و برند با نورپردازی و جلوههای واقعی",6,
|
||
[("brandText","نام برند","فلترندر"),("tagline","شعار","موشن، ساده و حرفهای")],("#3ba7ff","#a855f7","#04060f")),
|
||
("Nowruz3D","fr-nowruz-3d","تبریک نوروز سهبعدی","صحنهٔ سهبعدی نوروز با حاجیفیروز، سفرهٔ هفتسین و نورپردازی سینمایی",7,
|
||
[("greeting","متن تبریک","نوروز مبارک"),("subtitle","زیرعنوان","سال نو پیروز و شادمان"),("message","پیام / سال","۱۴۰۶")],("#f5c542","#e23b3b","#1a1228")),
|
||
("Birthday3D","fr-birthday-3d","تولد سهبعدی","صحنهٔ سهبعدی تولد با کیک و شمعهای روشن، بادکنک و کاغذرنگی",6,
|
||
[("greeting","تبریک","تولدت مبارک"),("name","نام","سارا"),("message","پیام","بهترینها را برایت آرزومندیم 🎉")],("#fb7185","#a855f7","#1a1226")),
|
||
("Promo3D","fr-promo-3d","فروش ویژه سهبعدی","تبلیغ سهبعدی فروش و تخفیف با جعبههای هدیه و نورپردازی سینمایی",6,
|
||
[("badge","نشان تخفیف","۵۰٪ تخفیف"),("headline","عنوان","فروش ویژهٔ پایان فصل"),("subtext","توضیح","فقط تا پایان همین هفته"),("cta","دکمه","همین حالا خرید کنید")],("#f59e0b","#fb7185","#140e1f")),
|
||
("AppShowcase3D","fr-app-showcase","معرفی اپلیکیشن سهبعدی","نمایش سهبعدی و حرفهای اپلیکیشن روی گوشی پرچمدار با نورپردازی استودیویی",6,
|
||
[("appName","نام اپلیکیشن","اپلیکیشن شما"),("tagline","شعار","تجربهای روان، سریع و زیبا"),("cta","دکمه","همین حالا دانلود کنید")],("#3b82f6","#8b5cf6","#f4f5f7")),
|
||
]
|
||
|
||
# Optional Media (image) content elements per template — these surface in the
|
||
# studio as upload/replace fields. key = the Remotion prop the image binds to.
|
||
MEDIA = {
|
||
"GlitterReveal": [("logoUrl", "لوگو (تصویر دلخواه)")],
|
||
"AppShowcase3D": [("screenUrl", "تصویر اپلیکیشن (اسکرینشات)")],
|
||
}
|
||
|
||
# Per-template text colour (default white for dark backgrounds; dark for light studios).
|
||
TEXTCOLORS = {
|
||
"AppShowcase3D": "#0f172a",
|
||
}
|
||
|
||
# Templates that ship a distinct preview video PER aspect (so the detail page shows
|
||
# the matching render, not the 16:9 cropped). Others reuse the single 16:9 preview.
|
||
PERASPECT_VIDEO = {"AppShowcase3D"}
|
||
|
||
def swatch_svg(colors):
|
||
rects = "".join(f'<rect x="{i*50}" y="0" width="50" height="40" fill="{c}"/>' for i, c in enumerate(colors))
|
||
return f'<svg xmlns="http://www.w3.org/2000/svg" width="200" height="40">{rects}</svg>'
|
||
|
||
def icon_svg(hex):
|
||
return f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="{hex}"/></svg>'
|
||
|
||
out = []
|
||
out.append("BEGIN;")
|
||
slugs = ",".join(q(t[1]) for t in T)
|
||
out.append(f"DELETE FROM content.project_containers WHERE slug IN ({slugs});")
|
||
|
||
for idx, (tid, slug, name, desc, dur, texts, (accent, sec, bg)) in enumerate(T):
|
||
cid = uid("c-" + tid)
|
||
thumb16 = f"{MINIO}/template-media/{tid}-16x9.png"
|
||
preview = f"{MINIO}/template-media/{tid}.mp4"
|
||
txt = TEXTCOLORS.get(tid, "#ffffff")
|
||
colors = [("accentColor", accent), ("secondaryColor", sec), ("backgroundColor", bg), ("textColor", txt)]
|
||
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)},"
|
||
f"TRUE,FALSE,FALSE,'FLEXIBLE',{idx});")
|
||
for (asp, w, h, aspstr) in ASPECTS:
|
||
pid = uid(f"p-{tid}-{asp}")
|
||
sid = uid(f"s-{tid}-{asp}")
|
||
thumb = f"{MINIO}/template-media/{tid}-{asp}.png"
|
||
pvideo = f"{MINIO}/template-media/{tid}-{asp}.mp4" if tid in PERASPECT_VIDEO else preview
|
||
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},30,'FLEXIBLE','FullHD','Remotion',{q(tid+'-'+asp)},TRUE,0);")
|
||
out.append(
|
||
"INSERT INTO content.scenes (id,project_id,key,title,scene_color_svg,default_duration_sec,sort) VALUES ("
|
||
f"{q(sid)},{q(pid)},'c1','صحنه ۱',{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 ("
|
||
f"{q(uid(f'ce-{tid}-{asp}-{k}'))},{q(sid)},{q(k)},{q(title)},'Text',{q(val)},{pos},1);")
|
||
for mpos, (k, title) in enumerate(MEDIA.get(tid, [])):
|
||
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(sid)},{q(k)},{q(title)},'Media','',{len(texts)+mpos},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("SELECT count(*) AS containers FROM content.project_containers WHERE slug LIKE 'fr-%';")
|
||
print("\n".join(out))
|