feat(render+templates): Remotion engine, 16 branded templates (incl. 3D), seconds pricing, coming-soon
CI/CD / CI · Web (tsc) (push) Successful in 1m21s
CI/CD / Deploy · full stack (push) Failing after 20s

Render engine
- Add Remotion (code-based) as a 2nd render engine alongside After Effects.
  node-agent dispatches on Job.Engine; RunRemotion maps bindings -> --props,
  renders native then ffmpeg-scales to the quality tier (aspect-preserving).
- content.projects.render_engine + render_remotion_comp (migration 32);
  render-svc claim resolves engine and routes (skips .aep for Remotion).
- Admin TemplatesAdmin gains an engine picker + Remotion composition id field.

Template pack (services/remotion)
- 16 branded, Persian (Vazirmatn), color- and text-editable templates, each in
  3 aspects (16:9 / 1:1 / 9:16): LogoMotion, Opener, InstaPromo, YouTubeIntro,
  Slideshow, HappyBirthday, SalePromo, QuoteCard, EventInvite, Countdown,
  GlitterReveal (editable logo image), NowruzGreeting (animated characters),
  and 4 cinematic 3D templates via @remotion/three (Hero3D, Nowruz3D,
  Birthday3D, Promo3D) with reflections + bloom/DOF/vignette.
- scripts/seed_remotion_templates.py seeds containers/projects/scenes/colors.

Pricing
- Rewrite /pricing to the seconds-based model (charge = length x resolution),
  data-driven from /v1/plans, Toman, broker checkout.

Coming-soon
- Persian experimental-build overlay on all pages (launch date + countdown).

Fixes
- middleware matcher bypasses all static asset paths; catalog mapping passes
  cover image + preview video so real thumbnails render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-21 15:52:52 +03:30
parent b9b91397b0
commit 4f04f6bf75
137 changed files with 8942 additions and 135 deletions
@@ -176,6 +176,8 @@ public class TemplateService(ContentDbContext db)
ProjectDurationSec = req.ProjectDurationSec, MinDurationSec = req.MinDurationSec,
MaxDurationSec = req.MaxDurationSec, FreeFps = req.FreeFps, ChooseMode = chooseMode,
Resolution = resolution, VipFactor = req.VipFactor, RenderAepComp = req.RenderAepComp,
RenderEngine = string.IsNullOrWhiteSpace(req.RenderEngine) ? "AfterEffects" : req.RenderEngine,
RenderRemotionComp = req.RenderRemotionComp,
IsPublished = req.IsPublished, Sort = req.Sort
};
@@ -225,6 +227,7 @@ public class TemplateService(ContentDbContext db)
ProjectDurationSec = src.ProjectDurationSec, MinDurationSec = src.MinDurationSec,
MaxDurationSec = src.MaxDurationSec, FreeFps = src.FreeFps, ChooseMode = src.ChooseMode,
Resolution = resolution, VipFactor = src.VipFactor, RenderAepComp = src.RenderAepComp,
RenderEngine = src.RenderEngine, RenderRemotionComp = src.RenderRemotionComp,
SharedLayerImage = src.SharedLayerImage, SharedColorsSvg = src.SharedColorsSvg,
SharedColorPresetsSvg = src.SharedColorPresetsSvg,
IsPublished = false, Sort = src.Sort,
@@ -359,6 +362,8 @@ public class TemplateService(ContentDbContext db)
project.MinDurationSec = req.MinDurationSec; project.MaxDurationSec = req.MaxDurationSec;
project.FreeFps = req.FreeFps; project.ChooseMode = chooseMode; project.Resolution = resolution;
project.VipFactor = req.VipFactor; project.RenderAepComp = req.RenderAepComp;
if (!string.IsNullOrWhiteSpace(req.RenderEngine)) project.RenderEngine = req.RenderEngine;
if (req.RenderRemotionComp != null) project.RenderRemotionComp = req.RenderRemotionComp;
project.SharedLayerImage = req.SharedLayerImage; project.SharedColorsSvg = req.SharedColorsSvg;
project.SharedColorPresetsSvg = req.SharedColorPresetsSvg;
project.IsPublished = req.IsPublished; project.Sort = req.Sort;
@@ -402,6 +407,8 @@ public class TemplateService(ContentDbContext db)
if (req.Image != null) project.Image = req.Image;
if (req.FullDemo != null) project.FullDemo = req.FullDemo;
if (req.SharedColorsSvg != null) project.SharedColorsSvg = req.SharedColorsSvg;
if (!string.IsNullOrWhiteSpace(req.RenderEngine)) project.RenderEngine = req.RenderEngine;
if (req.RenderRemotionComp != null) project.RenderRemotionComp = req.RenderRemotionComp;
project.UpdatedAt = DateTime.UtcNow;
await db.SaveChangesAsync();
@@ -472,7 +479,8 @@ public class TemplateService(ContentDbContext db)
p.ProjectDurationSec, p.MinDurationSec, p.MaxDurationSec,
p.FreeFps, p.ChooseMode.ToString(), p.Resolution.ToString(),
p.IsPublished, p.Sort,
p.AepFileUrl, p.AepFileSizeBytes, p.RenderAepComp
p.AepFileUrl, p.AepFileSizeBytes, p.RenderAepComp,
p.RenderEngine, p.RenderRemotionComp
);
/// <summary>Browse/search all projects (template items) across containers.</summary>
@@ -489,7 +497,8 @@ public class TemplateService(ContentDbContext db)
.Skip((page - 1) * pageSize).Take(pageSize)
.Select(p => new ProjectListItemResponse(
p.Id, p.ContainerId, p.Container.Name, p.Container.Slug, p.Name, p.Image,
p.Aspect, p.Resolution.ToString(), p.AepFileUrl, p.RenderAepComp, p.IsPublished, p.Sort))
p.Aspect, p.Resolution.ToString(), p.AepFileUrl, p.RenderAepComp, p.IsPublished, p.Sort,
p.RenderEngine, p.RenderRemotionComp))
.ToListAsync();
return new PagedResponse<ProjectListItemResponse>(items,
new PaginationMeta(page, pageSize, total, (int)Math.Ceiling((double)total / pageSize)));
@@ -529,6 +538,8 @@ public class TemplateService(ContentDbContext db)
if (req.AepFileMd5 != null) project.AepFileMd5 = req.AepFileMd5;
if (req.AepFileSizeBytes.HasValue) project.AepFileSizeBytes = req.AepFileSizeBytes;
if (!string.IsNullOrWhiteSpace(req.RenderAepComp)) project.RenderAepComp = req.RenderAepComp;
if (!string.IsNullOrWhiteSpace(req.RenderEngine)) project.RenderEngine = req.RenderEngine;
if (req.RenderRemotionComp != null) project.RenderRemotionComp = req.RenderRemotionComp;
if (req.Folder != null) project.Folder = req.Folder;
project.AepUploadedAt = DateTime.UtcNow;
project.UpdatedAt = DateTime.UtcNow;
@@ -541,6 +552,7 @@ public class TemplateService(ContentDbContext db)
p.OriginalWidth, p.OriginalHeight, p.Aspect,
p.ProjectDurationSec, p.MinDurationSec, p.MaxDurationSec,
p.FreeFps, p.ChooseMode.ToString(), p.Resolution.ToString(), p.VipFactor, p.RenderAepComp,
p.RenderEngine, p.RenderRemotionComp,
p.SharedLayerImage, p.IsPublished, p.Sort,
p.Scenes.Select(MapScene).ToList(),
p.SharedColors.Select(sc => new SharedColorResponse(sc.Id, sc.ElementKey, sc.Title, sc.Icon, sc.AttrValue.ToString(), sc.DefaultColor, sc.Sort)).ToList(),