<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Creative Automation – IMG.LY Blog</title><description>Posts tagged Creative Automation on the IMG.LY blog.</description><link>https://img.ly/blog/tag/creative-automation/</link><language>en-us</language><image><url>https://img.ly/apple-touch-icon.png</url><title>Creative Automation – IMG.LY Blog</title><link>https://img.ly/blog/tag/creative-automation/</link></image><atom:link href="https://img.ly/blog/tag/creative-automation/rss.xml" rel="self" type="application/rss+xml"/><generator>Astro</generator><lastBuildDate>Tue, 09 Jun 2026 09:48:35 GMT</lastBuildDate><ttl>60</ttl><item><title>AI Design Agents and Creative Automation: How to Ship a Full Campaign Without a Designer</title><link>https://img.ly/blog/ai-design-agents-and-creative-automation-how-to-ship-a-full-campaign-without-a-designer/</link><guid isPermaLink="true">https://img.ly/blog/ai-design-agents-and-creative-automation-how-to-ship-a-full-campaign-without-a-designer/</guid><description>Most marketing workflows are fully automated now - except one. Design is still a handoff, a wait, a revision loop, and another wait. Here&apos;s how AI design agents are closing that gap, and how to run a full campaign production session without leaving a single conversation.</description><pubDate>Wed, 01 Apr 2026 10:14:03 GMT</pubDate><content:encoded>&lt;p&gt;You have your files, hooks, Google Ads data but still can’t actually launch a campaign because you have to go to a designer for the final assets.&lt;/p&gt;
&lt;p&gt;That gap is exactly where campaign momentum dies. Not at the strategy stage or in copy but at the last mile, when everything is ready except the thing people will actually see.&lt;/p&gt;
&lt;p&gt;Luckily, it no longer has to.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://img.ly/blog/content/media/2026/05/AI-first.mp4&quot; controls&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-ai-marketing-stack-has-a-design-shaped-hole-in-it&quot;&gt;The AI Marketing Stack Has a Design-Shaped Hole in It&lt;/h2&gt;
&lt;p&gt;Most marketing workflows have been quietly transformed over the last two years. Copy generation, audience segmentation, keyword research, performance analysis - all of it runs faster now, with less manual input. A solo marketer can do what used to require a team. Except for one part.&lt;/p&gt;
&lt;p&gt;Design hasn’t moved. The workflow is still: write a brief, hand it to a designer, wait, review, revise, wait again. Then do the same thing in three more formats because the 1:1 you approved doesn’t fit Stories, Display, or LinkedIn. That loop can take days. And it doesn’t matter how good your AI-generated copy is if it’s sitting in a doc waiting for someone to have bandwidth.&lt;/p&gt;
&lt;p&gt;This isn’t a resourcing problem. Hiring more designers doesn’t fix the structural issue; it just adds capacity to a fundamentally slow process. The problem is that the tools built for design weren’t designed for the workflow a modern marketer actually runs. They expect a designer at the keyboard. They don’t expect a marketer with a campaign brief and a conversation window.&lt;/p&gt;
&lt;p&gt;The result: campaigns that are otherwise fully automated still stall before they ship. The bottleneck moved from copy to creative. And most teams haven’t noticed yet, because design delays feel normal. They’ve always been there.&lt;/p&gt;
&lt;h2 id=&quot;what-an-ai-design-agent-actually-changes&quot;&gt;What an AI Design Agent Actually Changes&lt;/h2&gt;
&lt;p&gt;An AI design agent is an autonomous AI system, not a prompt-response tool. It plans, reasons, and executes design tasks independently. Given a goal, it breaks that goal into steps, uses the tools available to it, retains context across the session, and can self-correct when the output isn’t right. That’s the category: a system that drives the workflow rather than waiting for instruction at each step.&lt;/p&gt;
&lt;p&gt;Most design agents deliver speed. The handoff that used to take days can happen in minutes. But the output is still a deliverable: a file you receive, use as-is, or move somewhere else. Most operate inside existing tools or generate assets you work with elsewhere. The speed is real, the editability usually isn’t. If something needs to change, you’re back to prompting from scratch.&lt;/p&gt;
&lt;p&gt;CoDesign sits differently. It doesn’t hand you a finished asset. It gives you a working starting point on a real canvas. Layers, text boxes, placeholders - real assets you can continue to work with, in the same conversation, without leaving the session.&lt;/p&gt;
&lt;p&gt;Brand consistency gets handled at the foundation. Load your brand kit once, including colors, fonts, logo, and layout rules, and every output that session respects those constraints. You’re not eyeballing hex codes or hoping the font looks right. The rules are applied from the start, not checked at the end.&lt;/p&gt;
&lt;p&gt;Multi-format adaptation is where the time savings become concrete. A campaign that runs across Instagram, Google Display, LinkedIn, and print doesn’t produce four separate briefs and four separate rounds of designer revisions. You describe the formats you need, and the agent adapts the work. The campaign stays consistent across all of them.&lt;/p&gt;
&lt;p&gt;The biggest shift isn’t speed, though that’s real. It’s that you stay in creative control throughout. There’s no handoff moment where you lose the thread and have to re-explain the brief to someone else. The context lives in the conversation.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://img.ly/blog/content/media/2026/05/01-delegate-catalogue-design--1-.mp4&quot; controls&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&quot;how-to-run-a-campaign-production-session-with-codesign&quot;&gt;How to Run a Campaign Production Session with CoDesign&lt;/h2&gt;
&lt;p&gt;This is the actual sequence. Walk through it once and the workflow becomes repeatable.&lt;/p&gt;
&lt;p&gt;1.&lt;strong&gt;Start with the brief.&lt;/strong&gt; Open CoDesign and describe the campaign in plain language. Audience, objective, platform, tone, any constraints. The more specific you are here, the less iteration you’ll need later. Treat it like briefing a senior designer who hasn’t worked with your brand before.&lt;/p&gt;
&lt;p&gt;2.&lt;strong&gt;Generate copy variations before you open the canvas.&lt;/strong&gt; Use whichever AI writing tool you already work with — ChatGPT, Claude, or whatever is in your stack — to produce multiple copy directions: headline hooks, body copy, CTAs. Get four or five versions per element. Copy and design are separate steps that feed into each other. Having real options ready before you start the design session means CoDesign has something specific to work with, not a blank brief waiting to be interpreted.&lt;/p&gt;
&lt;p&gt;3.&lt;strong&gt;Feed the brief to the AI design companion.&lt;/strong&gt; With copy variations ready, ask CoDesign to generate initial ad designs. Describe the format, the hierarchy you want, any layout preferences. You’ll get structured, editable designs on the canvas. Not a rendered image, but a working starting point with real layers.&lt;/p&gt;
&lt;p&gt;4.&lt;strong&gt;Apply your brand kit.&lt;/strong&gt; If you haven’t already, load your brand assets: logo, color palette, type system, approved imagery. The agent applies these rules to the designs. Every output from this point respects your brand standards automatically.&lt;/p&gt;
&lt;p&gt;5.&lt;strong&gt;Adapt across formats.&lt;/strong&gt; Tell the agent which formats you need. The social variant, the display variant, the vertical for Stories, the square for feed. Watch the layouts adapt to each context, maintaining the campaign idea and brand consistency across dimensions. If the hierarchy needs adjusting for a specific format, describe what’s not working and the agent fixes it in conversation.&lt;/p&gt;
&lt;p&gt;6.&lt;strong&gt;Refine in conversation.&lt;/strong&gt; This is where the canvas-based approach earns its value. You’re not generating new versions from scratch. You’re iterating on what’s already there, in the same session. “Move the logo to the bottom right. Try the headline in the lighter weight. Swap this layout for something with more white space.” Each exchange builds on the last, so the conversation stays grounded in what’s on the canvas rather than starting over from a new prompt.&lt;/p&gt;
&lt;p&gt;7.&lt;strong&gt;Export and ship.&lt;/strong&gt; When the designs are approved, export in the formats your media plan requires. The session context lives with the file, so if something needs to change post-launch, you’re not starting from zero.&lt;/p&gt;
&lt;p&gt;One honest note: the quality of the output is proportional to the quality of the brief. Vague prompts produce generic starting points. Teams that invest two minutes in a specific, structured brief consistently get more usable first outputs than teams that describe the campaign in one sentence and expect the agent to fill in the gaps.&lt;/p&gt;
&lt;h2 id=&quot;this-is-what-closing-the-loop-actually-looks-like&quot;&gt;This Is What Closing the Loop Actually Looks Like&lt;/h2&gt;
&lt;p&gt;Design agents don’t replace designers. They remove the bottleneck that sits between strategy and execution.&lt;/p&gt;
&lt;p&gt;The marketer who used to wait three days for ad assets can now produce a full campaign set in a single session. The designer who spent half their week on format resizes and small-copy tweaks can spend that time on the work that genuinely requires their judgment. That means brand-defining creative, campaign concepts, and anything where taste and experience are the actual input.&lt;/p&gt;
&lt;p&gt;It was never about willingness to collaborate. The tools just didn’t allow for anything else. Every design change, no matter how small, had to go through a handoff. A headline adjustment on a banner resize does not need a creative director. A resize from 1:1 to 9:16 does not need a brief, a Slack message, and a 48-hour turnaround.&lt;/p&gt;
&lt;p&gt;Thanks to design agents conversation shifts. It moves from “can you make this” to “how should this look.” And that’s a way more interesting conversation.&lt;/p&gt;
&lt;p&gt;Interested in trying IMG.LY CoDesign? &lt;a href=&quot;https://img.ly/forms/contact-sales&quot;&gt;Reach out&lt;/a&gt; to our team.&lt;/p&gt;</content:encoded><dc:creator>Klaudia</dc:creator><media:content url="https://blog.img.ly/2026/04/page-1-export.png" medium="image"/><category>AI</category><category>Insights</category><category>Creative Workflows</category><category>Creative Automation</category></item><item><title>How to Generate 10,000 Localized Ad Variants with CE.SDK Engine API</title><link>https://img.ly/blog/generate-ten-thousand-localized-ad-variants-cesdk/</link><guid isPermaLink="true">https://img.ly/blog/generate-ten-thousand-localized-ad-variants-cesdk/</guid><description>This guide walks through a production-ready pipeline for generating 10,000 localized ad variants using CE.SDK’s Engine API - covering templates, localization, batch rendering, and scalable infrastructure used by real performance marketing teams.</description><pubDate>Tue, 20 Jan 2026 11:22:18 GMT</pubDate><content:encoded>&lt;p&gt;You’re launching a global campaign across 50 markets, 5 languages, and 40 product categories. That’s 10,000 unique ad variants—and you need them ready by Monday. Your design team can’t produce that volume manually, outsourcing to agencies would take weeks and cost a fortune, and copy-pasting templates in Photoshop isn’t a realistic option.&lt;/p&gt;
&lt;p&gt;This is the reality of performance marketing at scale. The modern approach treats creative generation as infrastructure: designers create one master template with intelligence built in, your systems inject localized data programmatically, and automation renders thousands of perfect variants in hours instead of weeks. You’re not replacing creative work—you’re separating template design (what humans do best) from variant generation (what automation does best).&lt;/p&gt;
&lt;p&gt;In this guide, we’ll build a complete pipeline for generating 10,000 localized ad variants using &lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;CE.SDK’s Engine API&lt;/a&gt;. You’ll see the full architecture, production-ready code for batch processing, localization handling across languages and markets, performance optimization strategies, and how to deliver finished assets to your ad platforms. This isn’t a toy example—it’s the real infrastructure teams like &lt;a href=&quot;https://img.ly/case-studies/plai&quot;&gt;Plai use to generate 30,000+ ad creatives monthly&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This guide is written for engineers and technical teams building high-volume creative automation pipelines, and assumes familiarity with Node.js, APIs, and backend infrastructure.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you’re new to CE.SDK, start with our &lt;a href=&quot;https://img.ly/blog/ce-sdk-explained-embedded-editor-and-automation-engine-in-one-sdk/&quot;&gt;explainer on how the unified engine works&lt;/a&gt; to understand the platform. For strategic context on why automation infrastructure matters, see our article on &lt;a href=&quot;https://img.ly/blog/editor-or-api-why-modern-creative-automation-needs-both/&quot;&gt;creative automation infrastructure&lt;/a&gt;. This guide assumes you’re ready to implement and want the technical details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A quick note on CE.SDK templates:&lt;/strong&gt; CE.SDK templates are the same artifacts used by both the visual editor and the Engine API, which is what allows designers and automation pipelines to share a single source of truth.&lt;/p&gt;
&lt;h2 id=&quot;what-youll-build&quot;&gt;What You’ll Build&lt;/h2&gt;
&lt;p&gt;By the end of this guide, you’ll have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A production-ready batch rendering pipeline for 10,000+ variants&lt;/li&gt;
&lt;li&gt;A localization-safe template design strategy&lt;/li&gt;
&lt;li&gt;GPU-accelerated rendering with horizontal scaling&lt;/li&gt;
&lt;li&gt;Automated output delivery to ad platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;system-architecture-from-template-to-10000-variants&quot;&gt;System Architecture: From Template to 10,000 Variants&lt;/h2&gt;
&lt;p&gt;Before writing code, let’s map the complete system architecture. You need four layers working together: template design, data management, generation engine, and output delivery.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Template Design Layer&lt;/strong&gt; is where your designers create the master ad template using CE.SDK’s visual editor. They define the layout, set up text variables for headlines and CTAs, configure image placeholders for product photos, lock brand elements that shouldn’t change, and test with sample data to ensure the template handles different content lengths. This template becomes the source of truth for all 10,000 variants.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data Management Layer&lt;/strong&gt; holds three datasets: your product catalog (40 products with names, prices, images, and features translated into 5 languages), market configurations (50 markets with language, currency, locale, and platform specifications), and creative variants (different headlines and CTAs you want to A/B test). The generation engine combines these datasets to produce unique outputs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generation Engine&lt;/strong&gt; is CE.SDK’s Node.js renderer running on your servers. It loads the template, loops through your data combinations, injects each variant’s specific data, renders the creative, and exports in your target formats. This runs as batch jobs—either scheduled overnight or triggered by campaign launches.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Output Management&lt;/strong&gt; takes the rendered assets and delivers them where they need to go: uploads to your CDN with organized paths for easy retrieval, integration with ad platform APIs (Facebook Marketing API, Google Ads API), manifest generation for tracking which variants deployed where, and analytics tagging for performance measurement.&lt;/p&gt;
&lt;p&gt;The data flow looks like this: Template (one master design) + Product Data (40 items × 5 languages) + Market Data (50 configurations) + Creative Variants (5 headline/CTA combinations) = 10,000 unique assets ready for deployment.&lt;/p&gt;
&lt;p&gt;From an infrastructure perspective, you’ll need a Node.js server environment (version 16 or higher recommended), rendering nodes (GPU-enabled for optimal performance), storage for templates, source data, and rendered outputs (S3, Google Cloud Storage, or equivalent), a CDN for asset delivery to ad platforms, and optionally Redis for caching frequently used data and a queue system like RabbitMQ or AWS SQS for managing batch jobs across multiple rendering nodes.&lt;/p&gt;
&lt;h3 id=&quot;why-this-architecture-scales&quot;&gt;Why This Architecture Scales&lt;/h3&gt;
&lt;p&gt;Separation of concerns is the key to maintainability. Designers work in the visual editor without touching code. Engineers manage the data pipeline and rendering infrastructure. Marketers configure campaigns and trigger generation. Each team works in their domain, connected by the template format.&lt;/p&gt;
&lt;p&gt;Horizontal scaling means adding more rendering nodes increases throughput linearly. One GPU node might handle 100 variants per second. Four nodes handle 400 per second. Ten nodes handle 1,000 per second. Your 10,000-variant job scales from 100 seconds down to 10 seconds just by adding capacity.&lt;/p&gt;
&lt;p&gt;Caching strategies make repeated renders faster. Load the template once and duplicate it for each variant instead of reloading from disk every time. Pre-cache fonts used across all variants. Pre-load common assets into memory. These optimizations can significantly reduce initial load time and prevent redundant network requests.&lt;/p&gt;
&lt;p&gt;Batch processing efficiency comes from parallel execution. Instead of processing one variant at a time, process 100 simultaneously (memory permitting), then the next 100, and so on. This keeps your GPUs saturated and maximizes throughput.&lt;/p&gt;
&lt;p&gt;For the deep technical details on GPU-accelerated rendering and server deployment, see our &lt;a href=&quot;https://img.ly/blog/ce-sdk-renderer-creative-automation/&quot;&gt;CE.SDK Renderer deep-dive&lt;/a&gt;. For this guide, we’ll focus on the generation pipeline itself.&lt;/p&gt;
&lt;h2 id=&quot;designing-templates-that-work-across-50-markets&quot;&gt;Designing Templates That Work Across 50 Markets&lt;/h2&gt;
&lt;p&gt;Template design is where most multi-market campaigns fail. A template that works perfectly in English might break spectacularly in German (30% longer text), Japanese (different character density), or Arabic (right-to-left layout). Get this foundation right or waste time debugging broken renders at scale.&lt;/p&gt;
&lt;h3 id=&quot;text-overflow-handling&quot;&gt;Text Overflow Handling&lt;/h3&gt;
&lt;p&gt;Different languages have wildly different text lengths for the same content. “Buy Now” in English becomes “Jetzt Kaufen” in German (33% longer), “今すぐ購入” in Japanese (compact characters), or “اشتر الآن” in Arabic (with different text flow). Your template needs to handle this automatically without manual adjustment per language.&lt;/p&gt;
&lt;p&gt;CE.SDK’s auto-formatting handles overflow intelligently. You configure text blocks with minimum and maximum font sizes, and the engine scales text to fit. If content is too long, it reduces font size within your defined range. If it’s still too long, it wraps to multiple lines (if allowed) or truncates with ellipses (if single-line is required).&lt;/p&gt;
&lt;p&gt;Here’s how to configure a text block for flexible sizing:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Configure headline text block with overflow handling&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setFloat&lt;/span&gt;&lt;span&gt;(headlineBlock, &lt;/span&gt;&lt;span&gt;&apos;text/fontSize&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;32&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setFloat&lt;/span&gt;&lt;span&gt;(headlineBlock, &lt;/span&gt;&lt;span&gt;&apos;text/minAutomaticFontSize&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;24&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setBool&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  headlineBlock,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;text/automaticFontSizeEnabled&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  headlineBlock,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;text/horizontalAlignment&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;center&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(headlineBlock, &lt;/span&gt;&lt;span&gt;&apos;text/verticalAlignment&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;center&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration says: “Start at 32pt font. If text doesn’t fit, scale down to 24pt. If it still doesn’t fit, allow the text block to grow vertically (add lines). Center the text horizontally and vertically within its bounds.”&lt;/p&gt;
&lt;p&gt;Test your template with the longest and shortest language variants before automating. German and Finnish tend to be longest for European languages. Japanese and Korean are typically shortest due to character density. If your template works with German headlines and Japanese CTAs, it’ll probably work everywhere.&lt;/p&gt;
&lt;h3 id=&quot;layout-flexibility-and-safe-zones&quot;&gt;Layout Flexibility and Safe Zones&lt;/h3&gt;
&lt;p&gt;Fixed layouts break when content exceeds expected lengths. Flexible layouts adapt. Define safe zones where critical elements (product images, logos, legal disclaimers) must remain visible regardless of text length. Allow text blocks to expand within defined boundaries that don’t overlap these zones.&lt;/p&gt;
&lt;p&gt;Note: CE.SDK uses manual positioning rather than automatic constraint-based layouts. For dynamic content, you’ll need to programmatically adjust element positions when text length varies. Consider using fixed layouts with sufficient padding, or implementing resize detection to reflow elements as needed.&lt;/p&gt;
&lt;h3 id=&quot;right-to-left-rtl-language-support&quot;&gt;Right-to-Left (RTL) Language Support&lt;/h3&gt;
&lt;p&gt;Arabic and Hebrew read right-to-left, which means your entire layout needs to mirror. Text aligns to the right, element ordering reverses, and directional indicators (arrows, chevrons) flip horizontally. You can’t just change text alignment—you need full layout transformation.&lt;/p&gt;
&lt;p&gt;Detect RTL markets in your market configuration data and apply layout transforms programmatically:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Check if market requires RTL layout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (market.textDirection &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;rtl&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Mirror the entire scene horizontally&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setFloat&lt;/span&gt;&lt;span&gt;(sceneBlock, &lt;/span&gt;&lt;span&gt;&apos;transform/rotation&apos;&lt;/span&gt;&lt;span&gt;, Math.&lt;/span&gt;&lt;span&gt;PI&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setFloat&lt;/span&gt;&lt;span&gt;(sceneBlock, &lt;/span&gt;&lt;span&gt;&apos;transform/scaleX&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Right-align text blocks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    headlineBlock,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &apos;text/horizontalAlignment&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &apos;right&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(bodyBlock, &lt;/span&gt;&lt;span&gt;&apos;text/horizontalAlignment&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;right&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, design separate RTL and LTR template variants if the layout differences are significant. Load the appropriate template based on market configuration.&lt;/p&gt;
&lt;h3 id=&quot;font-management-across-languages&quot;&gt;Font Management Across Languages&lt;/h3&gt;
&lt;p&gt;Latin fonts (Roboto, Open Sans, Arial) don’t include glyphs for Chinese, Japanese, or Korean characters. Load those fonts and you’ll see empty boxes instead of text. You need font families with appropriate character coverage for each language.&lt;/p&gt;
&lt;p&gt;Google’s Noto font family is your friend here—it has variants covering virtually every writing system. Map languages to appropriate fonts:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; fontMapping&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  en: &lt;/span&gt;&lt;span&gt;&apos;Roboto&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  es: &lt;/span&gt;&lt;span&gt;&apos;Roboto&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  fr: &lt;/span&gt;&lt;span&gt;&apos;Roboto&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  de: &lt;/span&gt;&lt;span&gt;&apos;Roboto&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ja: &lt;/span&gt;&lt;span&gt;&apos;Noto Sans JP&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ko: &lt;/span&gt;&lt;span&gt;&apos;Noto Sans KR&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  zh: &lt;/span&gt;&lt;span&gt;&apos;Noto Sans SC&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ar: &lt;/span&gt;&lt;span&gt;&apos;Noto Sans Arabic&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  he: &lt;/span&gt;&lt;span&gt;&apos;Noto Sans Hebrew&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  th: &lt;/span&gt;&lt;span&gt;&apos;Noto Sans Thai&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; applyFontForLanguage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;engine&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;textBlock&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;language&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; fontFamily&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fontMapping[language] &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;Roboto&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(textBlock, &lt;/span&gt;&lt;span&gt;&apos;text/fontFamily&apos;&lt;/span&gt;&lt;span&gt;, fontFamily);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pre-load all required fonts before batch processing starts. Font loading has initialization overhead—doing it once per batch instead of per variant saves significant time.&lt;/p&gt;
&lt;h3 id=&quot;cultural-and-visual-considerations&quot;&gt;Cultural and Visual Considerations&lt;/h3&gt;
&lt;p&gt;Colors carry different meanings across cultures. White signals purity and weddings in Western markets but represents mourning in parts of East Asia. Red is lucky in China but signals danger or alerts elsewhere. Your template should allow market-specific color overrides if needed.&lt;/p&gt;
&lt;p&gt;Imagery needs localization too. Stock photos featuring Western models might not resonate in Asian markets. Lifestyle imagery showing specific products (food, clothing) needs cultural adaptation. Build flexibility into your template for swapping background images or graphic elements per market.&lt;/p&gt;
&lt;p&gt;Date formats, currency symbols, and number formatting vary by locale. Don’t hardcode “12/25/2024” (US format) or “$99.99” (dollar sign). Use market configuration to format correctly: “25.12.2024” and “99,99 €” for European markets, “2024年12月25日” and “¥11,000” for Japan.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/case-studies/digitas&quot;&gt;&lt;strong&gt;Digitas&lt;/strong&gt;&lt;/a&gt; built a centralized campaign portal for 600+ car dealers across 9-10 automotive brands with automated multilingual asset generation in German, French, and Italian. Their 140 active campaigns generate thousands of assets with self-service creation—adapting campaigns from days to seconds while maintaining brand compliance across all stakeholders.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Martin Röhr, Senior Project Manager: &lt;em&gt;“We shipped a much better product than initially planned. Building something like this ourselves would have taken years or ten times the budget.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;template-design-checklist&quot;&gt;Template Design Checklist&lt;/h3&gt;
&lt;p&gt;Before moving to code, validate your template:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Test with longest language variant (usually German or Finnish)&lt;/li&gt;
&lt;li&gt;Test with shortest variant (often Japanese due to character density)&lt;/li&gt;
&lt;li&gt;Verify RTL layout if supporting Arabic or Hebrew markets&lt;/li&gt;
&lt;li&gt;Confirm font coverage for all target languages (no missing glyphs)&lt;/li&gt;
&lt;li&gt;Leave flexible space for text expansion (30% buffer minimum)&lt;/li&gt;
&lt;li&gt;Define safe zones for critical brand elements&lt;/li&gt;
&lt;li&gt;Test all CTA button texts to ensure they fit&lt;/li&gt;
&lt;li&gt;Validate currency and number formatting across locales&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Get the template right and automation becomes straightforward. Get it wrong and you’ll debug layout issues across 10,000 variants.&lt;/p&gt;
&lt;h2 id=&quot;organizing-data-for-10000-variants&quot;&gt;Organizing Data for 10,000 Variants&lt;/h2&gt;
&lt;p&gt;Data structure determines how efficiently you generate at scale. Three layers of data combine to produce your variants: product information, market configurations, and creative variations. Structure them correctly and your generation code becomes clean. Structure them poorly and you’ll fight data transformation bugs.&lt;/p&gt;
&lt;h3 id=&quot;product-data-structure&quot;&gt;Product Data Structure&lt;/h3&gt;
&lt;p&gt;Each product needs information in all target languages plus market-specific details:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;productId&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;PRD-001&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;category&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;electronics&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;name&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;en&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Wireless Noise-Cancelling Headphones&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;es&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Auriculares Inalámbricos con Cancelación de Ruido&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;fr&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Écouteurs Sans Fil à Réduction de Bruit&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;de&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Kabellose Kopfhörer mit Geräuschunterdrückung&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;ja&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ワイヤレスノイズキャンセリングヘッドホン&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;price&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;USD&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;299.99&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;EUR&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;279.99&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;GBP&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;249.99&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;JPY&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;33000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;images&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;product&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://cdn.example.com/products/prd-001-main.jpg&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;lifestyle&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://cdn.example.com/products/prd-001-lifestyle.jpg&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;detail&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://cdn.example.com/products/prd-001-detail.jpg&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;features&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;en&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;Active Noise Cancellation&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;40-Hour Battery Life&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;Premium Sound Quality&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;es&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;Cancelación Activa de Ruido&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;40 Horas de Batería&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;Calidad de Sonido Premium&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;fr&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;Réduction Active du Bruit&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;Autonomie 40 Heures&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;Qualité Audio Premium&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;de&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;Aktive Geräuschunterdrückung&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;40 Stunden Akkulaufzeit&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;Premium-Klangqualität&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;ja&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;アクティブノイズキャンセリング&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;40時間バッテリー&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;プレミアムサウンド&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This structure supports language-specific content (names, features) and market-specific pricing. Store products in a database (PostgreSQL, MongoDB) or JSON files depending on your infrastructure and update frequency.&lt;/p&gt;
&lt;h3 id=&quot;market-configuration&quot;&gt;Market Configuration&lt;/h3&gt;
&lt;p&gt;Markets define language, currency, locale, and platform-specific requirements:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;marketId&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;US&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;country&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;United States&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;language&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;en&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;locale&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;en-US&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;currency&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;USD&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;currencySymbol&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;$&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;currencyPosition&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;before&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;textDirection&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ltr&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;dateFormat&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;MM/DD/YYYY&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;numberFormat&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;decimal&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;thousands&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;targetPlatforms&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;facebook&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;instagram&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;google&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;adSpecs&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;facebook&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;feed&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;width&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;height&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;format&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;jpg&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;story&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;width&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;height&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1920&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;format&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;jpg&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;instagram&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;square&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;width&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;height&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;format&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;jpg&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;portrait&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;width&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;height&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1350&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;format&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;jpg&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Market configurations centralize localization logic. When rendering for the German market, you load its configuration and apply language, currency, number formatting, and platform specs automatically.&lt;/p&gt;
&lt;h3 id=&quot;creative-variant-configuration&quot;&gt;Creative Variant Configuration&lt;/h3&gt;
&lt;p&gt;Different headlines and CTAs for A/B testing:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;variantId&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;VAR-001&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Value-Focused&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;headline&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;en&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Premium Quality at an Unbeatable Price&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;es&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Calidad Premium a un Precio Inmejorable&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;fr&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Qualité Premium à Prix Imbattable&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;de&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Premium-Qualität zum unschlagbaren Preis&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;ja&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;プレミアム品質、比類なき価格&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;cta&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;en&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Shop Now&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;es&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Comprar Ahora&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;fr&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Acheter Maintenant&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;de&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Jetzt Kaufen&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;ja&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;今すぐ購入&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;background&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;gradient-blue&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;layout&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;product-focus&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creative variants let you test messaging strategies across markets. Same product, same market, different headline/CTA combinations to find what resonates best.&lt;/p&gt;
&lt;h3 id=&quot;combining-data-layers&quot;&gt;Combining Data Layers&lt;/h3&gt;
&lt;p&gt;The Cartesian product of these layers creates your 10,000 variants:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;40 products&lt;/li&gt;
&lt;li&gt;× 50 markets&lt;/li&gt;
&lt;li&gt;× 5 creative variants&lt;/li&gt;
&lt;li&gt;= 10,000 unique combinations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each combination needs a unique render with the right product, localized for the right market, using the right creative messaging. Your generation code loops through these combinations systematically. &lt;strong&gt;As your variant dimensions grow, treat generation as a queued system rather than a single batch job to avoid runaway combinatorics.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For efficient processing, organize batches strategically. Group by market if you want to process all products for one market sequentially (better for CDN upload batching). Group by product if you want to process all markets for one product in parallel (better for data loading efficiency). Choose based on your downstream workflows.&lt;/p&gt;
&lt;h2 id=&quot;building-the-generation-engine&quot;&gt;Building the Generation Engine&lt;/h2&gt;
&lt;p&gt;Now for the code that actually generates 10,000 variants. This is production-ready implementation with error handling, performance optimization, and progress tracking.&lt;/p&gt;
&lt;h3 id=&quot;step-1-initialize-the-rendering-engine&quot;&gt;Step 1: Initialize the Rendering Engine&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; CreativeEngine &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@cesdk/node&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; fs &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;fs/promises&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; path &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;path&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Initialize CE.SDK engine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; engine&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; CreativeEngine.&lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  license: process.env.&lt;/span&gt;&lt;span&gt;CESDK_LICENSE&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  baseURL: &lt;/span&gt;&lt;span&gt;&apos;https://cdn.img.ly/packages/imgly/cesdk-node/1.31.0/assets&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;✓ CreativeEngine initialized&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This initializes CE.SDK’s Node.js engine. Store your license key in environment variables for security.&lt;/p&gt;
&lt;h3 id=&quot;step-2-load-and-cache-the-template&quot;&gt;Step 2: Load and Cache the Template&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Load template once, reuse for all variants&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; templatePath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;./templates/product-ad-template.scene&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; sceneId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.scene.&lt;/span&gt;&lt;span&gt;loadFromArchiveURL&lt;/span&gt;&lt;span&gt;(templatePath);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`✓ Template loaded: ${&lt;/span&gt;&lt;span&gt;sceneId&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Get block IDs for elements we&apos;ll modify&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Strategy 1: Use findByType and filter by kind&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; allTextBlocks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;findByType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;text&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; blocks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  headline: allTextBlocks.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;getKind&lt;/span&gt;&lt;span&gt;(id) &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;headline&apos;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  productName: allTextBlocks.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;getKind&lt;/span&gt;&lt;span&gt;(id) &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;product-name&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ctaButton: allTextBlocks.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;getKind&lt;/span&gt;&lt;span&gt;(id) &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;cta-button&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  price: allTextBlocks.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;getKind&lt;/span&gt;&lt;span&gt;(id) &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;price&apos;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  feature1: allTextBlocks.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;getKind&lt;/span&gt;&lt;span&gt;(id) &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;feature-1&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  feature2: allTextBlocks.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;getKind&lt;/span&gt;&lt;span&gt;(id) &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;feature-2&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  feature3: allTextBlocks.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;getKind&lt;/span&gt;&lt;span&gt;(id) &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;feature-3&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Get image block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; allImageBlocks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;findByType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;graphic&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;blocks.productImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; allImageBlocks.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  (&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;getKind&lt;/span&gt;&lt;span&gt;(id) &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;product-image&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;✓ Template blocks mapped&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Loading the template once and reusing it for all variants is critical for performance. Reloading from disk for each of 10,000 variants would add hours to your batch job. The specific strategy for managing scenes and blocks depends on your workflow—see the generation function for implementation considerations.&lt;/p&gt;
&lt;h3 id=&quot;step-3-core-generation-function&quot;&gt;Step 3: Core Generation Function&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; generateAdVariant&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  engine&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  templateSceneId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  blocks&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  product&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  market&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  variant&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Note: For high-volume generation, consider your scene management strategy.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Option 1: Load template fresh each time (simpler, slower)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Option 2: Duplicate blocks and modify (more complex, faster)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Option 3: Modify blocks in place, export, then restore (fastest, requires careful state management)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // This example shows the modify-in-place approach with proper cleanup.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; lang&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; market.language;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Update headline with localized creative variant&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      blocks.headline,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &apos;text/text&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      variant.headline[lang]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Update product name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      blocks.productName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &apos;text/text&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      product.name[lang]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Format and update price with market-specific currency&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; formattedPrice&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; formatCurrency&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      product.price[market.currency],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      market.currencySymbol,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      market.currencyPosition,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      market.numberFormat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(blocks.price, &lt;/span&gt;&lt;span&gt;&apos;text/text&apos;&lt;/span&gt;&lt;span&gt;, formattedPrice);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Update CTA button&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      blocks.ctaButton,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &apos;text/text&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      variant.cta[lang]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Update product features (if product has features)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (product.features &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; product.features[lang]) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; features&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; product.features[lang];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (features[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(blocks.feature1, &lt;/span&gt;&lt;span&gt;&apos;text/text&apos;&lt;/span&gt;&lt;span&gt;, features[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (features[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(blocks.feature2, &lt;/span&gt;&lt;span&gt;&apos;text/text&apos;&lt;/span&gt;&lt;span&gt;, features[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (features[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(blocks.feature3, &lt;/span&gt;&lt;span&gt;&apos;text/text&apos;&lt;/span&gt;&lt;span&gt;, features[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Replace product image by setting the fill image URI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      blocks.productImage,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &apos;fill/image/imageFileURI&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      product.images.product&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Apply market-specific font for proper character support&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; font&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getFontForLanguage&lt;/span&gt;&lt;span&gt;(market.language);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(blocks.headline, &lt;/span&gt;&lt;span&gt;&apos;text/fontFamily&apos;&lt;/span&gt;&lt;span&gt;, font);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(blocks.productName, &lt;/span&gt;&lt;span&gt;&apos;text/fontFamily&apos;&lt;/span&gt;&lt;span&gt;, font);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Handle RTL layouts for Arabic/Hebrew markets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (market.textDirection &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;rtl&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        blocks.headline,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &apos;text/horizontalAlignment&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &apos;right&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        blocks.productName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &apos;text/horizontalAlignment&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &apos;right&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Generate filename: {marketId}_{productId}_{variantId}.png&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; filename&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `${&lt;/span&gt;&lt;span&gt;market&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;marketId&lt;/span&gt;&lt;span&gt;}_${&lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;}_${&lt;/span&gt;&lt;span&gt;variant&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;variantId&lt;/span&gt;&lt;span&gt;}.png`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; outputDir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `./output/${&lt;/span&gt;&lt;span&gt;market&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;marketId&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;category&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt;(outputDir, { recursive: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; outputPath&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; path.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(outputDir, filename);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Export the rendered variant using the scene block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; sceneBlock&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;findByType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;scene&apos;&lt;/span&gt;&lt;span&gt;)[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; blob&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt;(sceneBlock, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      mimeType: &lt;/span&gt;&lt;span&gt;&apos;image/png&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; buffer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Buffer.&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; blob.&lt;/span&gt;&lt;span&gt;arrayBuffer&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;writeFile&lt;/span&gt;&lt;span&gt;(outputPath, buffer);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; { success: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, path: outputPath, filename };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      success: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      error: error.message,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      product: product.productId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      market: market.marketId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      variant: variant.variantId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function handles a single variant: duplicates the template, injects all localized data, applies market-specific formatting, renders, exports, and cleans up. The &lt;code&gt;try/catch/finally&lt;/code&gt; pattern ensures memory cleanup even if rendering fails.&lt;/p&gt;
&lt;h3 id=&quot;step-4-orchestrating-the-batch&quot;&gt;Step 4: Orchestrating the Batch&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; generateAllVariants&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Load data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; products&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; loadProducts&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// Your function to load 40 products&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; markets&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; loadMarkets&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// Your function to load 50 markets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; variants&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; loadCreativeVariants&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// Your function to load 5 variants&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; startTime&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; completed &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; failed &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; total&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; products.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; markets.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt; variants.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; errors&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`Starting generation of ${&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;} variants...`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Build all combinations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; combinations&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; product&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; products) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; market&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; markets) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; variant&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; variants) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        combinations.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({ product, market, variant });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Process in parallel batches to manage memory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; batchSize&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// Tune based on your server&apos;s memory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; combinations.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; batchSize) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; batch&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; combinations.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(i, i &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; batchSize);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; batchStartTime&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Process batch in parallel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; results&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      batch.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(({ &lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;market&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;variant&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        generateAdVariant&lt;/span&gt;&lt;span&gt;(engine, sceneId, blocks, product, market, variant)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Track results&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; results) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (result.success) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        completed&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        failed&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        errors.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(result);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // Progress reporting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; batchDuration&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; batchStartTime) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; overallElapsed&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; startTime) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; rate&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; completed &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; overallElapsed;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; remaining&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (total &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; completed &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; failed) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; rate;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      `Batch ${&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ceil&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; batchSize&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ceil&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; batchSize&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}: `&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        `${&lt;/span&gt;&lt;span&gt;completed&lt;/span&gt;&lt;span&gt;} completed, ${&lt;/span&gt;&lt;span&gt;failed&lt;/span&gt;&lt;span&gt;} failed | `&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        `${&lt;/span&gt;&lt;span&gt;rate&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toFixed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;} variants/sec | `&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        `${&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ceil&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;remaining&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}s remaining`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Final summary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; totalDuration&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; startTime) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;=== Generation Complete ===&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    `✓ Completed: ${&lt;/span&gt;&lt;span&gt;completed&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;} (${&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;completed&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; total&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toFixed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}%)`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`✗ Failed: ${&lt;/span&gt;&lt;span&gt;failed&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`⏱ Duration: ${&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;totalDuration&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; 60&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toFixed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;} minutes`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    `⚡ Average rate: ${&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;completed&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; totalDuration&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toFixed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;} variants/sec`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Log errors for debugging&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (errors.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &gt;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;Failed variants:&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    errors.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        `  ${&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;market&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;variant&lt;/span&gt;&lt;span&gt;}: ${&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; { completed, failed, errors, duration: totalDuration };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Run the batch generation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; result&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; generateAllVariants&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This orchestration code processes all 10,000 variants in batches of 100 (tune this based on available memory). It tracks progress, estimates time remaining, handles errors gracefully, and provides a comprehensive summary when complete.&lt;/p&gt;
&lt;h2 id=&quot;performance-optimization-from-hours-to-minutes&quot;&gt;Performance Optimization: From Hours to Minutes&lt;/h2&gt;
&lt;p&gt;Default configurations won’t give you optimal performance. Here’s how to tune for speed and scale.&lt;/p&gt;
&lt;h3 id=&quot;gpu-acceleration-significant-performance-boost&quot;&gt;GPU Acceleration: Significant Performance Boost&lt;/h3&gt;
&lt;p&gt;CE.SDK Renderer leverages GPU acceleration for faster rendering compared to CPU-only processing. The engine automatically uses available GPU resources when properly configured on GPU-enabled infrastructure.&lt;/p&gt;
&lt;p&gt;Infrastructure requirements: Use GPU-enabled instances (AWS g4dn.xlarge or larger, Google Cloud n1-standard with GPU, or on-premise servers with NVIDIA GPUs). GPU instances cost more per hour but complete jobs faster, often resulting in lower total cost for batch workloads.&lt;/p&gt;
&lt;p&gt;See our &lt;a href=&quot;https://img.ly/blog/ce-sdk-renderer-creative-automation/&quot;&gt;CE.SDK Renderer technical guide&lt;/a&gt; for detailed GPU deployment instructions.&lt;/p&gt;
&lt;h3 id=&quot;template-and-asset-caching&quot;&gt;Template and Asset Caching&lt;/h3&gt;
&lt;p&gt;Loading templates and assets repeatedly is wasteful. Cache them:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Bad: Loading template for every variant (slow)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; combo&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; combinations) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; scene&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.scene.&lt;/span&gt;&lt;span&gt;loadFromArchiveURL&lt;/span&gt;&lt;span&gt;(templatePath); &lt;/span&gt;&lt;span&gt;// Repeated loading&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // ... render ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Good: Load once, modify blocks in place for each variant (fast)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; templateScene&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.scene.&lt;/span&gt;&lt;span&gt;loadFromArchiveURL&lt;/span&gt;&lt;span&gt;(templatePath);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Get references to blocks once&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; allTextBlocks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;findByType&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;text&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; blocks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  headline: allTextBlocks.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;getKind&lt;/span&gt;&lt;span&gt;(id) &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;headline&apos;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  productName: allTextBlocks.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    (&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;getKind&lt;/span&gt;&lt;span&gt;(id) &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;product-name&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // ... etc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Then modify these same blocks for each variant, exporting between changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; combo&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; combinations) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Modify block properties&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(blocks.headline, &lt;/span&gt;&lt;span&gt;&apos;text/text&apos;&lt;/span&gt;&lt;span&gt;, combo.headline);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // ... other modifications ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Export&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; blob&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt;(sceneBlock, { mimeType: &lt;/span&gt;&lt;span&gt;&apos;image/png&apos;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Continue to next variant&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note on font pre-loading:&lt;/strong&gt; While font caching can improve performance, the specific font loading API varies by implementation. Consult the &lt;a href=&quot;https://img.ly/docs/cesdk/&quot;&gt;CE.SDK documentation&lt;/a&gt; for current best practices on font management and caching strategies.&lt;/p&gt;
&lt;h3 id=&quot;batch-size-tuning&quot;&gt;Batch Size Tuning&lt;/h3&gt;
&lt;p&gt;Batch size affects throughput and memory usage. Too small (10-20) means you’re not maximizing parallelism. Too large (500+) risks running out of memory and crashing mid-batch.&lt;/p&gt;
&lt;p&gt;Find your optimal batch size empirically:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; testCombinations&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; combinations.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; batchSizes&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;150&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; size&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; batchSizes) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; start&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Process test batch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; testCombinations.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;; i &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; size) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; batch&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; testCombinations.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(i, i &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; size);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await&lt;/span&gt;&lt;span&gt; Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;(batch.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; generateAdVariant&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; duration&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Date.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; start;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; rate&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; (duration &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`Batch size ${&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;}: ${&lt;/span&gt;&lt;span&gt;rate&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toFixed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;} variants/sec`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Typical sweet spot: 100-200 for servers with 16-32GB RAM.&lt;/p&gt;
&lt;h3 id=&quot;horizontal-scaling-with-multiple-rendering-nodes&quot;&gt;Horizontal Scaling with Multiple Rendering Nodes&lt;/h3&gt;
&lt;p&gt;One server hitting limits? Add more nodes and distribute work via a queue system:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Simplified queue-based architecture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Queue &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;bull&apos;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// Or AWS SQS, RabbitMQ, etc.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; renderQueue&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Queue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;ad-generation&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Producer: Add jobs to queue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; combo&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; combinations) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; renderQueue.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(combo);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Consumer (runs on each rendering node):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;renderQueue.&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;job&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;market&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;variant&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; job.data;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; generateAdVariant&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    engine,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    sceneId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    blocks,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    product,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    market,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    variant&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With 4 rendering nodes, you get 4x throughput. Your 10,000-variant job that took 25 minutes on one node completes in ~6 minutes with four nodes.&lt;/p&gt;
&lt;h3 id=&quot;performance-benchmarks&quot;&gt;Performance Benchmarks&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;These are estimated performance ranges.&lt;/strong&gt; Real-world performance varies significantly based on template complexity, image sizes, effects applied, and infrastructure configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single CPU core:&lt;/strong&gt; ~5-10 variants/second&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single GPU (NVIDIA T4):&lt;/strong&gt; ~50-100 variants/second&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4 GPU nodes:&lt;/strong&gt; ~200-400 variants/second&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;10,000 variants:&lt;/strong&gt; Approximately 25 seconds to 30 minutes depending on setup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example estimate: AWS g4dn.xlarge (1 GPU) @ $0.526/hour might generate ~100 variants/sec = 10,000 in ~100 seconds. Estimated total cost: ~$0.015 per batch. Scaling to 4 nodes could reduce time to ~25 seconds with 4x the hourly cost but similar total spend due to shorter duration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Always benchmark with your specific templates and infrastructure before committing to production architecture.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;output-management-and-delivery-to-ad-platforms&quot;&gt;Output Management and Delivery to Ad Platforms&lt;/h2&gt;
&lt;p&gt;Generated assets need organized storage and delivery to ad platforms. Here’s the complete workflow.&lt;/p&gt;
&lt;h3 id=&quot;cdn-upload-and-organization&quot;&gt;CDN Upload and Organization&lt;/h3&gt;
&lt;p&gt;Upload rendered assets to S3 (or equivalent) with organized paths:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; AWS &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;aws-sdk&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; s3&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; AWS&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;S3&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; uploadToS3&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;localPath&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;market&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;variant&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Organized path: ads/{marketId}/{category}/{filename}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `ads/${&lt;/span&gt;&lt;span&gt;market&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;marketId&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;category&lt;/span&gt;&lt;span&gt;}/${&lt;/span&gt;&lt;span&gt;market&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;marketId&lt;/span&gt;&lt;span&gt;}_${&lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;}_${&lt;/span&gt;&lt;span&gt;variant&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;variantId&lt;/span&gt;&lt;span&gt;}.png`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; fileContent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;readFile&lt;/span&gt;&lt;span&gt;(localPath);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; s3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;putObject&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Bucket: process.env.&lt;/span&gt;&lt;span&gt;S3_BUCKET&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Key: key,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Body: fileContent,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ContentType: &lt;/span&gt;&lt;span&gt;&apos;image/png&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      CacheControl: &lt;/span&gt;&lt;span&gt;&apos;public, max-age=31536000&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// 1 year cache&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Metadata: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        productId: product.productId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        marketId: market.marketId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        variantId: variant.variantId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        category: product.category,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        generatedAt: &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Date&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toISOString&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span&gt;promise&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; cdnUrl&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `https://cdn.example.com/${&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; cdnUrl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Metadata tagging enables analytics tracking and asset management downstream.&lt;/p&gt;
&lt;h3 id=&quot;ad-platform-integration&quot;&gt;Ad Platform Integration&lt;/h3&gt;
&lt;p&gt;Upload assets to Facebook/Instagram via Marketing API:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  FacebookAdsApi,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  AdAccount,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  AdImage,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;facebook-nodejs-business-sdk&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; uploadToFacebookAdLibrary&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;imagePath&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;market&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; account&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; AdAccount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`act_${&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FB_AD_ACCOUNT_ID&lt;/span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; image&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; AdImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, process.env.&lt;/span&gt;&lt;span&gt;FB_AD_ACCOUNT_ID&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  image.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    filename: path.&lt;/span&gt;&lt;span&gt;basename&lt;/span&gt;&lt;span&gt;(imagePath),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    bytes: &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;readFile&lt;/span&gt;&lt;span&gt;(imagePath),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Tag with metadata for campaign organization&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; imageHash&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; image.hash;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    hash: imageHash,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    url: image.url,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    productId: product.productId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    marketId: market.marketId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar integrations exist for Google Ads API, TikTok Ads, LinkedIn Ads, and programmatic platforms.&lt;/p&gt;
&lt;h3 id=&quot;manifest-generation&quot;&gt;Manifest Generation&lt;/h3&gt;
&lt;p&gt;Create a manifest file tracking all generated variants:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; manifest&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  generatedAt: &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; Date&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toISOString&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  totalVariants: &lt;/span&gt;&lt;span&gt;10000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  variants: [],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Add each variant to manifest during generation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;manifest.variants.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  filename: &lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;market&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;marketId&lt;/span&gt;&lt;span&gt;}_${&lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;}_${&lt;/span&gt;&lt;span&gt;variant&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;variantId&lt;/span&gt;&lt;span&gt;}.png`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  cdnUrl: &lt;/span&gt;&lt;span&gt;&apos;https://cdn.example.com/ads/US/electronics/US_PRD001_VAR1.png&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  productId: product.productId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  productName: product.name.en,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  marketId: market.marketId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  language: market.language,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  variantId: variant.variantId,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  headline: variant.headline[market.language],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  platforms: market.targetPlatforms,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; fs.&lt;/span&gt;&lt;span&gt;writeFile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;./output/manifest.json&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(manifest, &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This manifest feeds your campaign management tools, analytics dashboards, and reporting systems.&lt;/p&gt;
&lt;h2 id=&quot;troubleshooting-common-issues&quot;&gt;Troubleshooting Common Issues&lt;/h2&gt;
&lt;p&gt;Even with solid code, you’ll hit edge cases. Here’s how to debug them.&lt;/p&gt;
&lt;h3 id=&quot;issue-text-overflow-in-german-headlines&quot;&gt;Issue: Text Overflow in German Headlines&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; German headlines exceed layout bounds, causing truncation or broken layouts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Configure text blocks with proper overflow handling and test with German content during template design:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setFloat&lt;/span&gt;&lt;span&gt;(headlineBlock, &lt;/span&gt;&lt;span&gt;&apos;text/fontSize&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;32&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setFloat&lt;/span&gt;&lt;span&gt;(headlineBlock, &lt;/span&gt;&lt;span&gt;&apos;text/minAutomaticFontSize&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setBool&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  headlineBlock,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;text/automaticFontSizeEnabled&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Always test templates with longest-language variants before automating thousands of renders.&lt;/p&gt;
&lt;h3 id=&quot;issue-memory-leaks-during-batch-processing&quot;&gt;Issue: Memory Leaks During Batch Processing&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Memory usage grows continuously, eventually crashing the process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Ensure proper resource cleanup and consider your scene management strategy:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Load or configure scene for variant&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // ... rendering ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // ... export ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;finally&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Clean up blocks if needed using engine.block.destroy()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // Specific cleanup depends on your implementation approach&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Monitor memory with &lt;code&gt;process.memoryUsage()&lt;/code&gt; and tune batch sizes accordingly.&lt;/p&gt;
&lt;h3 id=&quot;issue-inconsistent-font-rendering&quot;&gt;Issue: Inconsistent Font Rendering&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Some variants show correct fonts, others show fallbacks or missing glyphs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Ensure all required fonts are available and properly configured before batch processing. Verify font files are accessible to the rendering engine (either bundled or loaded from URLs). Consult the &lt;a href=&quot;https://img.ly/docs/cesdk/&quot;&gt;CE.SDK documentation&lt;/a&gt; for specific font management approaches for your use case.&lt;/p&gt;
&lt;h3 id=&quot;issue-slow-first-batch-then-fast&quot;&gt;Issue: Slow First Batch, Then Fast&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; First 100 variants take 5 minutes, subsequent batches take 1 minute.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explanation:&lt;/strong&gt; Template loading, font initialization, and GPU warmup happen on first batch. This is normal overhead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Run a small warmup batch (10-20 variants) before starting production batches, or accept the first-batch penalty as initialization cost.&lt;/p&gt;
&lt;h3 id=&quot;issue-special-characters-rendering-as-boxes&quot;&gt;Issue: Special Characters Rendering as Boxes&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Japanese, Arabic, or Thai characters show as empty boxes (□).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Verify fonts support required character sets. Use Noto Sans font family for comprehensive Unicode coverage:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Good: Noto Sans supports extensive character sets&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(textBlock, &lt;/span&gt;&lt;span&gt;&apos;text/fontFamily&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Noto Sans JP&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Bad: Roboto doesn&apos;t include CJK characters&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(textBlock, &lt;/span&gt;&lt;span&gt;&apos;text/fontFamily&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;Roboto&apos;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// Will show boxes for Japanese&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;debugging-best-practices&quot;&gt;Debugging Best Practices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Start with a single variant to isolate template issues before running full batches&lt;/li&gt;
&lt;li&gt;Log each data injection step to catch malformed data&lt;/li&gt;
&lt;li&gt;Validate data completeness before sending to the engine (check for null/undefined values)&lt;/li&gt;
&lt;li&gt;Keep a test dataset small (10-20 variants) during development&lt;/li&gt;
&lt;li&gt;Export one variant manually through the visual editor to verify template correctness&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;from-proof-of-concept-to-production&quot;&gt;From Proof of Concept to Production&lt;/h2&gt;
&lt;p&gt;You now have a complete pipeline for generating 10,000 localized ad variants programmatically. The architecture separates template design (visual editor), data management (structured JSON), generation (batch rendering), and delivery (CDN and ad platforms). This same pattern scales to millions of variants by adding rendering nodes horizontally.&lt;/p&gt;
&lt;p&gt;The approach isn’t limited to ads. Apply it to email personalization (thousands of unique email headers), social media content (automated post generation for multiple accounts), product catalogs (e-commerce banners for entire inventory), or video campaigns (personalized video at scale). The template-plus-data model works wherever you need high-volume creative generation.&lt;/p&gt;
&lt;p&gt;Production considerations you’ll want to add: monitoring and alerting for failed generation jobs (integrate with Datadog, CloudWatch, or similar), versioning for templates and data (Git for templates, database migrations for product data), A/B testing integration to track which creative variants perform best, and compliance checks to ensure all outputs meet brand guidelines and legal requirements.&lt;/p&gt;
&lt;p&gt;Next steps for implementation: start with a small test batch (100 variants) to validate your complete pipeline end-to-end, optimize batch size and parallelization based on your infrastructure capacity, integrate with your existing marketing automation workflows, add quality gates and approval workflows for high-stakes campaigns, and set up monitoring dashboards to track generation performance and error rates.&lt;/p&gt;
&lt;p&gt;For more background, see our &lt;a href=&quot;https://img.ly/blog/ce-sdk-explained-embedded-editor-and-automation-engine-in-one-sdk/&quot;&gt;CE.SDK explainer&lt;/a&gt; for understanding the visual editor and engine API capabilities, our &lt;a href=&quot;https://img.ly/blog/editor-or-api-why-modern-creative-automation-needs-both/&quot;&gt;creative automation infrastructure guide&lt;/a&gt; for strategic context on why this architecture matters, and our &lt;a href=&quot;https://img.ly/blog/ce-sdk-renderer-creative-automation/&quot;&gt;CE.SDK Renderer technical details&lt;/a&gt; for GPU rendering optimization and server deployment best practices.&lt;/p&gt;
&lt;p&gt;Ready to test this yourself? &lt;a href=&quot;https://img.ly/forms/free-trial&quot;&gt;Try CE.SDK&lt;/a&gt; to experiment with template creation and rendering workflows.&lt;/p&gt;</content:encoded><dc:creator>Klaudia</dc:creator><media:content url="https://blog.img.ly/2026/01/generate-ad-variants-api-translate-ads-localize-2.jpg" medium="image"/><category>Insights</category><category>Creative Automation</category></item><item><title>Automating Video Creation at Scale: Why Templates, Timelines, and Rendering Matter</title><link>https://img.ly/blog/automating-video-creation-at-scale-templates-timelines-rendering/</link><guid isPermaLink="true">https://img.ly/blog/automating-video-creation-at-scale-templates-timelines-rendering/</guid><description>Video automation is fundamentally different from image automation. In this guide we go dive into timelines, rendering performance, and template design; and determine whether video automation scales or breaks in production.</description><pubDate>Mon, 19 Jan 2026 10:46:07 GMT</pubDate><content:encoded>&lt;p&gt;You’ve automated image generation—your marketing team can produce thousands of product banners, social posts, and email headers without touching Photoshop. But when someone asks, “Can we do this for video?” the conversation gets complicated fast.&lt;/p&gt;
&lt;p&gt;Video automation isn’t just “image automation with a timeline.” It’s fundamentally different. A static banner renders in 100 milliseconds. A 30-second video takes 10-30 seconds to render. Your 10MB image template becomes a 50MB video file. And that’s before you factor in audio tracks, transitions, platform-specific encoding requirements, and the reality that “vertical for TikTok” means different specs than “vertical for Instagram Stories.”&lt;/p&gt;
&lt;p&gt;Most teams discover these challenges the hard way—after they’ve already committed to video automation and hit unexpected walls. The template that looked perfect in the editor breaks when the headline is 30% longer in German. The rendering pipeline that handled images fine collapses under video workloads. The licensing you thought you had doesn’t cover H.264 distribution.&lt;/p&gt;
&lt;p&gt;Video automation is solvable. But it requires understanding what makes video different, designing templates that can handle variability, and building infrastructure that respects the complexity.&lt;/p&gt;
&lt;p&gt;In this article we will cover:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why video automation is different&lt;/li&gt;
&lt;li&gt;Designing templates that scale&lt;/li&gt;
&lt;li&gt;Architecture patterns for generation&lt;/li&gt;
&lt;li&gt;Platform constraints &amp;#x26; encoding&lt;/li&gt;
&lt;li&gt;Production pitfalls &amp;#x26; monitoring&lt;/li&gt;
&lt;li&gt;When automation does (and doesn’t) make sense&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-video-automation-is-fundamentally-different&quot;&gt;Why Video Automation Is Fundamentally Different&lt;/h2&gt;
&lt;p&gt;If you’ve built image automation pipelines, video introduces layers of complexity that change how you think about templates, rendering, and infrastructure. The core differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Timeline complexity:&lt;/strong&gt; Video adds time as a dimension—every element has duration, entry/exit timing, and potential animation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-asset coordination:&lt;/strong&gt; Dozens of assets (video clips, audio, overlays) must stay synchronized across time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rendering performance:&lt;/strong&gt; 100x slower than images (30-second video = 900 frames at 30fps)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File size:&lt;/strong&gt; 10MB PNG becomes 50MB MP4, impacting storage and distribution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Platform requirements:&lt;/strong&gt; Stricter, more varied specs (aspect ratios, duration limits, codec preferences)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;timeline-complexity-vs-static-composition&quot;&gt;Timeline Complexity vs. Static Composition&lt;/h3&gt;
&lt;p&gt;Images are spatial—elements positioned on a 2D canvas with fixed relationships. Video adds time. Every element now has duration, timing, and animation across the timeline.&lt;/p&gt;
&lt;p&gt;When you generate 10,000 image variants with different text lengths, the layout adjusts spatially. With video, text length affects duration—do you extend the scene to accommodate longer copy, or speed up the animation? Neither is automatic.&lt;/p&gt;
&lt;p&gt;A timeline-based engine like &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;CE.SDK’s video editor&lt;/a&gt; handles this through track systems similar to TikTok or Instagram: &lt;strong&gt;foreground tracks&lt;/strong&gt; for video clips and audio, &lt;strong&gt;background tracks&lt;/strong&gt; that define overall video length. Video clips can be trimmed, repositioned, and arranged. Audio strips sync with video timing but remain independent—you can adjust one without breaking the other.&lt;/p&gt;
&lt;p&gt;The automation challenge: when you inject variable data (product names, customer messages, CTAs), how does that affect the timeline? Template design needs to account for temporal variability, not just spatial.&lt;/p&gt;
&lt;h3 id=&quot;multi-asset-coordination-and-audio-sync&quot;&gt;Multi-Asset Coordination and Audio Sync&lt;/h3&gt;
&lt;p&gt;A single image template might reference 5-10 assets. A video template coordinates dozens: multiple video clips, background music, sound effects, overlay images, animated text, transitions. Each has its own timing, and they must stay synchronized when you generate variations.&lt;/p&gt;
&lt;p&gt;Audio is particularly tricky:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Background music needs to match video duration (fade cleanly, not cut mid-phrase)&lt;/li&gt;
&lt;li&gt;Voiceovers sync with specific visual moments&lt;/li&gt;
&lt;li&gt;Sound effects trigger at precise timestamps&lt;/li&gt;
&lt;li&gt;When automation extends duration (German translation longer), does audio stretch or stay fixed?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Timeline-based engines support audio playback with independent positioning controls. Audio strips appear in the timeline (not on canvas), and you can trim, reposition, and adjust timing independently of video tracks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; Video templates aren’t just asset collections—they’re choreographed sequences where timing relationships matter as much as visual composition.&lt;/p&gt;
&lt;h3 id=&quot;rendering-performance-and-infrastructure-implications&quot;&gt;Rendering Performance and Infrastructure Implications&lt;/h3&gt;
&lt;p&gt;Static image rendering is near-instantaneous. Video rendering is fundamentally slower—every frame is a separate render. A 30-second video at 30fps is 900 frames.&lt;/p&gt;
&lt;p&gt;Infrastructure implications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GPU acceleration isn’t optional&lt;/strong&gt; (CPU-only rendering is prohibitively slow)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batch processing requires queuing systems&lt;/strong&gt; (can’t render 1,000 videos synchronously)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Progress monitoring becomes essential&lt;/strong&gt; (users need status, not guessing)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Export failures are costlier&lt;/strong&gt; (failed image = 100ms wasted; failed video = 30 seconds)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most video export APIs provide progress callbacks for real-time status:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; videoBlob&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;exportVideo&lt;/span&gt;&lt;span&gt;(page, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  mimeType: &lt;/span&gt;&lt;span&gt;&apos;video/mp4&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  onProgress&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;rendered&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;encoded&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`Progress: ${&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;round&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;encoded&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; total&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}%`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;designing-templates-that-scale&quot;&gt;Designing Templates That Scale&lt;/h2&gt;
&lt;p&gt;Once you understand why video is different, the next challenge is designing templates that handle variability without manual intervention. Template design determines whether automation breaks or scales.&lt;/p&gt;
&lt;h3 id=&quot;scene-based-modular-structure&quot;&gt;Scene-Based Modular Structure&lt;/h3&gt;
&lt;p&gt;Instead of a single 30-second video, think in scenes: 5-second intro, 15-second product showcase, 5-second CTA, 5-second outro. Each scene is self-contained.&lt;/p&gt;
&lt;p&gt;Benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Variable duration:&lt;/strong&gt; Extend the product scene by 3 seconds for longer German translations without affecting intro/outro&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scene reordering:&lt;/strong&gt; A/B test different sequences (CTA-first vs. product-first)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conditional scenes:&lt;/strong&gt; Include/exclude based on data (show “Limited Time Offer” scene only for promotions)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reusable components:&lt;/strong&gt; Same intro across multiple video types, swap middle scenes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The template creator defines scene boundaries, timing, and which elements automation can modify. Automation code loads the template and injects data without worrying about timeline complexity.&lt;/p&gt;
&lt;h3 id=&quot;text-duration-and-audio-sync-strategies&quot;&gt;Text Duration and Audio Sync Strategies&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Text management approaches:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Extend scene duration:&lt;/strong&gt; Add seconds based on character count (&lt;code&gt;Math.ceil(text.length / 40)&lt;/code&gt; seconds)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduce text:&lt;/strong&gt; Pass pre-truncated strings that fit fixed timeline&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-line display:&lt;/strong&gt; Let text wrap across frames (works for some designs, breaks others)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Audio sync strategies:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fixed-duration music:&lt;/strong&gt; Choose tracks matching video length exactly (less flexible)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loopable music:&lt;/strong&gt; Tracks designed to loop seamlessly—calculate loops needed, fade out at end (most reliable)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adaptive music:&lt;/strong&gt; Music that adapts to duration changes (complex but professional)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For automation, loopable music with fade-out is safest. Calculate final video duration after data injection, loop the audio track to cover that duration, apply 1-2 second fade at end.&lt;/p&gt;
&lt;h3 id=&quot;video-clip-placeholders-and-safe-zones&quot;&gt;Video Clip Placeholders and Safe Zones&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Video placeholder logic:&lt;/strong&gt;&lt;br&gt;
Your template might designate a 5-second product demo slot, but actual product videos are 3, 7, or 12 seconds. Options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Trim to fit:&lt;/strong&gt; Longer clips trimmed to match; shorter clips looped (keeps consistent duration, may cut content)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extend the scene:&lt;/strong&gt; Adjust placeholder to match clip length (preserves content, variable duration)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed adjustment:&lt;/strong&gt; Speed up/slow down clip to fit (works 0.75x-1.5x, extreme adjustments look awkward)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Safe zones for multi-format export:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Text safe zone:&lt;/strong&gt; Keep all text 10% from edges (device overscan, UI overlays)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Critical content zone:&lt;/strong&gt; Logos, faces, key products within central 70% (survives aspect ratio cropping)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Action-safe zone:&lt;/strong&gt; Don’t place CTAs in corners where platform UI might cover them&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When designing templates that export to multiple aspect ratios (16:9, 9:16, 1:1), test how cropping affects composition.&lt;/p&gt;
&lt;h2 id=&quot;architecture-patterns-for-video-generation-pipelines&quot;&gt;Architecture Patterns for Video Generation Pipelines&lt;/h2&gt;
&lt;p&gt;Once your templates can handle variability, the next challenge is scale: how do you actually generate thousands of videos reliably? Three proven patterns:&lt;/p&gt;
&lt;h3 id=&quot;pattern-1-template-based-batch-rendering&quot;&gt;Pattern 1: Template-Based Batch Rendering&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Use case:&lt;/strong&gt; Marketing team uploads CSV with 500 products. Overnight, system generates 500 personalized video ads.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;High-level workflow:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Frontend upload interface (CSV, database query, API)&lt;/li&gt;
&lt;li&gt;Job queue (Redis, RabbitMQ, AWS SQS) holds generation requests&lt;/li&gt;
&lt;li&gt;Worker nodes pull jobs, generate videos, upload to storage&lt;/li&gt;
&lt;li&gt;Notification system alerts when batch completes&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Batch jobs don’t need real-time feedback. Optimize for throughput over latency—process 100 videos in parallel across 10 worker nodes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Implementation pattern (simplified):&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Worker node processing one batch item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; engine&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; CreativeEditorSDK.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;#cesdk_container&apos;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  license: process.env.&lt;/span&gt;&lt;span&gt;CESDK_LICENSE&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; scene&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.scene.&lt;/span&gt;&lt;span&gt;loadFromURL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;templates/product-ad-video.scene&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// Inject product data via Variables API&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.variable.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;productName&apos;&lt;/span&gt;&lt;span&gt;, product.name);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.variable.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;productPrice&apos;&lt;/span&gt;&lt;span&gt;, product.price);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; videoBlob&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;exportVideo&lt;/span&gt;&lt;span&gt;(scene, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  mimeType: &lt;/span&gt;&lt;span&gt;&apos;video/mp4&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  h264Profile: &lt;/span&gt;&lt;span&gt;77&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  framerate: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  onProgress&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;span&gt;rendered&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;encoded&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    updateJobProgress&lt;/span&gt;&lt;span&gt;(jobId, encoded &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; total);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; uploadToS3&lt;/span&gt;&lt;span&gt;(videoBlob, &lt;/span&gt;&lt;span&gt;`output/${&lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}.mp4`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Scaling:&lt;/strong&gt; Add more worker nodes horizontally. With GPU-enabled instances, one node can process 10-20 videos per minute depending on complexity.&lt;/p&gt;
&lt;p&gt;For large batches (10,000+ videos), use server-side rendering via Docker for maximum throughput. The &lt;a href=&quot;https://img.ly/docs/cesdk/renderer/cesdk-renderer-overview-7f3e9a/&quot;&gt;CE.SDK Renderer&lt;/a&gt; handles GPU acceleration automatically and exports to your specified output directory.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Licensing note:&lt;/strong&gt; For production deployments, use licensed Renderer variants (not open-source) to ensure proper H.264/H.265 codec coverage.&lt;/p&gt;
&lt;h3 id=&quot;pattern-2-real-time-personalization&quot;&gt;Pattern 2: Real-Time Personalization&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Use case:&lt;/strong&gt; User signs up for service. Within 30 seconds, they receive personalized welcome video with their name and company logo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;High-level workflow:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User action (signup, purchase, milestone) fires event&lt;/li&gt;
&lt;li&gt;Serverless function receives event data&lt;/li&gt;
&lt;li&gt;Engine generates video in real-time (typically 10-30 seconds)&lt;/li&gt;
&lt;li&gt;Video URL returned or sent via email/notification&lt;/li&gt;
&lt;li&gt;Cache generated videos if multiple users might trigger identical content&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Real-time generation provides immediate personalization. Latency (10-30 seconds for a 30-second video) is acceptable when delivered asynchronously.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Video duration is short (under 60 seconds)&lt;/li&gt;
&lt;li&gt;User data is simple (text substitution, not complex asset swapping)&lt;/li&gt;
&lt;li&gt;Delivery is asynchronous (email, notification—not immediate playback)&lt;/li&gt;
&lt;li&gt;Volume is moderate (hundreds per hour, not thousands per minute)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For higher volumes, shift to Pattern 1 (batching) or Pattern 3 (hybrid).&lt;/p&gt;
&lt;h3 id=&quot;pattern-3-hybrid-ui--automation&quot;&gt;Pattern 3: Hybrid UI + Automation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Use case:&lt;/strong&gt; Marketing manager designs campaign video template in visual editor. System then automatically generates 50 variants for different audience segments, products, or A/B testing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;High-level workflow:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Designer uses visual editor to create master template with variables&lt;/li&gt;
&lt;li&gt;Saved template becomes reusable asset&lt;/li&gt;
&lt;li&gt;Marketing team specifies which data to inject (audience segments, products)&lt;/li&gt;
&lt;li&gt;Backend loads template, injects data, generates variants&lt;/li&gt;
&lt;li&gt;Generated videos go through approval workflow before deployment&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Separates creative work (design) from scale (automation). Designers don’t write code. Automation doesn’t require developer involvement for template updates.&lt;/p&gt;
&lt;p&gt;Timeline-based engines like &lt;a href=&quot;https://img.ly/blog/ce-sdk-explained-embedded-editor-and-automation-engine-in-one-sdk/&quot;&gt;CE.SDK are explicitly designed for this pattern&lt;/a&gt;—unified workflow between editor and automation. The same template works in both contexts. What the designer sees in the visual editor is exactly what automation generates. No conversion, no drift, perfect consistency.&lt;/p&gt;
&lt;p&gt;This pattern is covered in detail in our &lt;a href=&quot;https://img.ly/blog/editor-or-api-why-modern-creative-automation-needs-both/&quot;&gt;creative automation infrastructure guide&lt;/a&gt;, which explains why teams need both editor and API capabilities within unified workflows.&lt;/p&gt;
&lt;h2 id=&quot;platform-constraints-and-encoding-optimization&quot;&gt;Platform Constraints and Encoding Optimization&lt;/h2&gt;
&lt;p&gt;Generating videos is only half the problem—distribution platforms impose constraints that directly affect how you export.&lt;/p&gt;
&lt;h3 id=&quot;h264-profiles-levels-and-trade-offs&quot;&gt;H.264 Profiles, Levels, and Trade-offs&lt;/h3&gt;
&lt;p&gt;Video export engines support H.264 encoding with configurable parameters affecting quality, compatibility, and file size:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;H.264 Profile:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Baseline (66):&lt;/strong&gt; Broadest device support, lower compression efficiency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Main (77):&lt;/strong&gt; Good balance (default), works on most modern devices&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High (100):&lt;/strong&gt; Best quality and compression, requires recent devices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;H.264 Level (multiply by 10):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Level 3.1 (31) = Up to 720p at 30fps&lt;/li&gt;
&lt;li&gt;Level 4.1 (41) = Up to 1080p at 30fps&lt;/li&gt;
&lt;li&gt;Level 5.2 (52) = Up to 4K at 60fps&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Recommended settings by distribution channel:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Social media ads (Instagram, TikTok, Facebook):&lt;/strong&gt; Main profile (77), Level 4.1 (41), 5-8 Mbps bitrate&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;YouTube videos:&lt;/strong&gt; High profile (100), Level 5.2 (52), 8-12 Mbps bitrate&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maximum compatibility (email, older devices):&lt;/strong&gt; Baseline profile (66), Level 3.1 (31), 2-4 Mbps bitrate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Critical constraint:&lt;/strong&gt; H.264 doesn’t support transparency. Transparent areas render with black backgrounds. Handle this at the template level (solid backgrounds or pre-composited elements), not during export.&lt;/p&gt;
&lt;h3 id=&quot;multi-format-export-for-social-platforms&quot;&gt;Multi-Format Export for Social Platforms&lt;/h3&gt;
&lt;p&gt;Social platforms prefer specific aspect ratios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;9:16 (Vertical):&lt;/strong&gt; TikTok, Instagram Reels, YouTube Shorts, Stories&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1:1 (Square):&lt;/strong&gt; Instagram Feed, Facebook Feed, LinkedIn Feed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;16:9 (Landscape):&lt;/strong&gt; YouTube, Facebook Watch, LinkedIn Video&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4:5 (Portrait):&lt;/strong&gt; Instagram Feed alternative, Pinterest&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your automation pipeline needs to export multiple formats from a single template:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; formats&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  { name: &lt;/span&gt;&lt;span&gt;&apos;tiktok&apos;&lt;/span&gt;&lt;span&gt;, width: &lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt;, height: &lt;/span&gt;&lt;span&gt;1920&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;// 9:16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  { name: &lt;/span&gt;&lt;span&gt;&apos;instagram&apos;&lt;/span&gt;&lt;span&gt;, width: &lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt;, height: &lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;// 1:1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  { name: &lt;/span&gt;&lt;span&gt;&apos;youtube&apos;&lt;/span&gt;&lt;span&gt;, width: &lt;/span&gt;&lt;span&gt;1920&lt;/span&gt;&lt;span&gt;, height: &lt;/span&gt;&lt;span&gt;1080&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;// 16:9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; format&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; formats) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; videoBlob&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;exportVideo&lt;/span&gt;&lt;span&gt;(page, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    mimeType: &lt;/span&gt;&lt;span&gt;&apos;video/mp4&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    targetWidth: format.width,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    targetHeight: format.height,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    framerate: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  await&lt;/span&gt;&lt;span&gt; saveToStorage&lt;/span&gt;&lt;span&gt;(videoBlob, &lt;/span&gt;&lt;span&gt;`${&lt;/span&gt;&lt;span&gt;product&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}-${&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}.mp4`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Template design consideration:&lt;/strong&gt; Design for the narrowest format (9:16 vertical) as your base. Ensure critical content stays within the central “safe zone” that remains visible when cropped to 1:1 or extended to 16:9.&lt;/p&gt;
&lt;h3 id=&quot;browser-vs-server-side-rendering-trade-offs&quot;&gt;Browser vs. Server-Side Rendering Trade-offs&lt;/h3&gt;
&lt;p&gt;Timeline-based video engines support two rendering environments:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Browser rendering (client-side):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pros: No server infrastructure, renders on user’s device, immediate preview&lt;/li&gt;
&lt;li&gt;Cons: Limited by device performance, requires modern web codecs, &lt;strong&gt;mobile web not supported&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Best for: Interactive editing, single video exports, preview generation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Server-side rendering (Docker):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pros: GPU acceleration, consistent performance, handles high volumes, licensed codecs&lt;/li&gt;
&lt;li&gt;Cons: Requires infrastructure setup, Docker deployment, GPU-enabled instances&lt;/li&gt;
&lt;li&gt;Best for: Batch processing, automation pipelines, production deployments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;When to use which:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Client-side:&lt;/strong&gt; User-initiated exports, preview generation, low volumes (&amp;#x3C; 10 videos/hour)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server-side:&lt;/strong&gt; Automated generation, batch jobs, high volumes (100+ videos/hour), production reliability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For hybrid workflows, use client-side rendering for template creation and preview, then switch to server-side for automation and scale.&lt;/p&gt;
&lt;h2 id=&quot;production-pitfalls-and-monitoring&quot;&gt;Production Pitfalls and Monitoring&lt;/h2&gt;
&lt;p&gt;You’ve built a prototype that generates a few test videos. Moving to production requires thinking through edge cases, failure modes, and operational concerns that don’t surface during development.&lt;/p&gt;
&lt;h3 id=&quot;error-handling-and-recovery&quot;&gt;Error Handling and Recovery&lt;/h3&gt;
&lt;p&gt;Video rendering can fail: corrupted assets, invalid template structure, insufficient memory, encoding errors, network timeouts. Your automation needs to handle these gracefully.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Common failure modes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Asset loading failure:&lt;/strong&gt; Verify asset URLs before generation, use fallback assets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory exhaustion:&lt;/strong&gt; Reduce batch size, add memory monitoring, restart workers periodically&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Template validation:&lt;/strong&gt; Check template structure before generation, catch invalid variable names early&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Encoding errors:&lt;/strong&gt; Log exact error messages, validate H.264 profile compatibility, check for transparency issues&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Failure recovery pattern (simplified):&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; generateVideoWithRetry&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;job&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;maxRetries&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 3&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; attempt &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;; attempt &lt;/span&gt;&lt;span&gt;&amp;#x3C;=&lt;/span&gt;&lt;span&gt; maxRetries; attempt&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; videoBlob&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;exportVideo&lt;/span&gt;&lt;span&gt;(scene, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        mimeType: &lt;/span&gt;&lt;span&gt;&apos;video/mp4&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        framerate: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; { success: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, blob: videoBlob };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (attempt &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; maxRetries) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        await&lt;/span&gt;&lt;span&gt; logFailedJob&lt;/span&gt;&lt;span&gt;(job, error);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; { success: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, error: error.message };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      await&lt;/span&gt;&lt;span&gt; sleep&lt;/span&gt;&lt;span&gt;(Math.&lt;/span&gt;&lt;span&gt;pow&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, attempt) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 1000&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// Exponential backoff&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;monitoring-and-observability&quot;&gt;Monitoring and Observability&lt;/h3&gt;
&lt;p&gt;Production video automation needs visibility:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key metrics to track:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generation rate (videos per minute/hour)&lt;/li&gt;
&lt;li&gt;Success rate (percentage completing successfully)&lt;/li&gt;
&lt;li&gt;Average render time (detect performance degradation)&lt;/li&gt;
&lt;li&gt;Queue depth (identify backlogs early)&lt;/li&gt;
&lt;li&gt;Storage usage (monitor file growth and costs)&lt;/li&gt;
&lt;li&gt;Error types (group by category: asset loading, encoding, timeout)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Alert thresholds:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Success rate drops below 95%&lt;/li&gt;
&lt;li&gt;Average render time increases by 50%&lt;/li&gt;
&lt;li&gt;Queue depth exceeds 1,000 pending jobs&lt;/li&gt;
&lt;li&gt;Error rate for specific template exceeds threshold&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;scaling-and-cost-optimization&quot;&gt;Scaling and Cost Optimization&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Horizontal scaling (more worker nodes):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Queue depth consistently high&lt;/li&gt;
&lt;li&gt;Generation rate needs to double or more&lt;/li&gt;
&lt;li&gt;Batch job completion times exceed acceptable limits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Vertical scaling (bigger instances):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Individual video rendering is slow&lt;/li&gt;
&lt;li&gt;Memory constraints causing failures&lt;/li&gt;
&lt;li&gt;GPU utilization low (CPU bottleneck)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cost optimization:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use spot instances for batch processing (significant savings, acceptable interruption risk)&lt;/li&gt;
&lt;li&gt;Pre-warm worker nodes during off-peak hours for predictable demand spikes&lt;/li&gt;
&lt;li&gt;Archive old generated videos to cold storage after 90 days (S3 Glacier)&lt;/li&gt;
&lt;li&gt;Cache frequently used templates and assets in memory&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-most-teams-dont-build-video-automation-in-house&quot;&gt;Why Most Teams Don’t Build Video Automation In-House&lt;/h2&gt;
&lt;p&gt;Before committing to custom development, understand what you’re actually building:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timeline engines are hard to maintain:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Frame-accurate editing across web, mobile, desktop platforms&lt;/li&gt;
&lt;li&gt;Synchronization between video tracks, audio, overlays, effects&lt;/li&gt;
&lt;li&gt;Undo/redo systems that work across temporal changes&lt;/li&gt;
&lt;li&gt;Template systems with variable timing and duration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Codec licensing is non-trivial:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;H.264/H.265 require patent licenses for distribution&lt;/li&gt;
&lt;li&gt;Open-source codecs lack broad device compatibility&lt;/li&gt;
&lt;li&gt;Licensing costs and compliance complexity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;GPU rendering infrastructure requires expertise:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker orchestration with GPU passthrough&lt;/li&gt;
&lt;li&gt;NVIDIA Container Toolkit configuration&lt;/li&gt;
&lt;li&gt;Performance optimization for parallel rendering&lt;/li&gt;
&lt;li&gt;Cross-platform consistency (web, server, mobile)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Editor + automation parity is rare:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What you design in the editor must match what automation generates&lt;/li&gt;
&lt;li&gt;Same rendering engine, same output quality, no conversion drift&lt;/li&gt;
&lt;li&gt;Template sharing between interactive and headless contexts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Timeline-based engines like &lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;CE.SDK&lt;/a&gt; already solved these problems and expose them via both UI and API. Most teams building video automation use existing engines rather than building from scratch.&lt;/p&gt;
&lt;h2 id=&quot;real-world-use-cases&quot;&gt;Real-World Use Cases&lt;/h2&gt;
&lt;h3 id=&quot;personalized-video-campaigns-at-scale&quot;&gt;Personalized Video Campaigns at Scale&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Financial services company sends 10,000 customers personalized year-end investment summary videos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Template design:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;40-second video: 5s intro → 20s portfolio visualization → 10s personalized message → 5s CTA&lt;/li&gt;
&lt;li&gt;Variables: &lt;code&gt;customerName&lt;/code&gt;, &lt;code&gt;portfolioReturn&lt;/code&gt;, &lt;code&gt;topHolding1/2/3&lt;/code&gt;, &lt;code&gt;advisorName&lt;/code&gt;, &lt;code&gt;advisorMessage&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Results:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;10,000 videos generated overnight across 50 worker nodes&lt;/li&gt;
&lt;li&gt;Average generation time: 15 seconds per video&lt;/li&gt;
&lt;li&gt;Total processing time: ~3 hours with parallelization&lt;/li&gt;
&lt;li&gt;Delivery: Videos embedded in personalized emails&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This mirrors the use case in our &lt;a href=&quot;https://img.ly/blog/ce-sdk-renderer-creative-automation/&quot;&gt;CE.SDK Renderer article&lt;/a&gt;, where a customer generated “up to 100,000 unique versions” of personalized videos from demographic targeting data.&lt;/p&gt;
&lt;h3 id=&quot;social-media-content-automation&quot;&gt;Social Media Content Automation&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; E-commerce brand launches 200 new products. Marketing needs video ads for each product across TikTok (9:16), Instagram Feed (1:1), and YouTube Shorts (9:16) with different CTA variations for A/B testing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Math:&lt;/strong&gt; 200 products × 3 formats × 2 CTA variants = 1,200 videos&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Distribution:&lt;/strong&gt; Videos automatically upload to Facebook Marketing API and Google Ads API with campaign tags, ready for activation.&lt;/p&gt;
&lt;h2 id=&quot;when-video-automation-makes-sense-and-when-it-doesnt&quot;&gt;When Video Automation Makes Sense (and When It Doesn’t)&lt;/h2&gt;
&lt;p&gt;Video automation isn’t right for every situation.&lt;/p&gt;
&lt;h3 id=&quot;good-fit-high-volume-template-driven-use-cases&quot;&gt;Good Fit: High-Volume, Template-Driven Use Cases&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Video automation works well when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High volume:&lt;/strong&gt; Dozens, hundreds, or thousands of videos regularly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repetitive structure:&lt;/strong&gt; Videos follow consistent patterns with variable content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data-driven:&lt;/strong&gt; Content from databases, APIs, or structured data sources&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time-sensitive:&lt;/strong&gt; Manual production can’t meet speed requirements&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost-prohibitive:&lt;/strong&gt; Hiring video editors or agencies for this volume isn’t viable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt; Product videos for e-commerce catalogs, personalized marketing campaigns, social media ads with A/B testing, localized versions (same video in 10+ languages)&lt;/p&gt;
&lt;h3 id=&quot;poor-fit-creative-intensive-one-off-productions&quot;&gt;Poor Fit: Creative-Intensive, One-Off Productions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Video automation struggles when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Low volume:&lt;/strong&gt; 1-5 videos per month (manual production is faster)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High creative variation:&lt;/strong&gt; Each video needs unique storytelling or cinematography&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex motion graphics:&lt;/strong&gt; Advanced animation that templates can’t capture&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Live-action footage:&lt;/strong&gt; Real people, locations, scenarios that vary significantly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artistic direction:&lt;/strong&gt; Videos requiring subjective creative decisions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt; Brand commercials with custom concepts, documentary-style content, event recap videos, testimonial videos&lt;/p&gt;
&lt;h3 id=&quot;the-middle-ground-hybrid-approaches&quot;&gt;The Middle Ground: Hybrid Approaches&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Automated foundation + manual refinement:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate base videos automatically&lt;/li&gt;
&lt;li&gt;Creative team polishes specific versions for key campaigns&lt;/li&gt;
&lt;li&gt;Use automation for scale, manual work for flagship content&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Manual template creation + automated generation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Designer creates polished templates once&lt;/li&gt;
&lt;li&gt;Automation generates thousands of variants&lt;/li&gt;
&lt;li&gt;No manual work after template is finalized&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-to-do-next&quot;&gt;What to Do Next&lt;/h2&gt;
&lt;p&gt;Before committing to video automation infrastructure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Evaluate if your use case is template-driven&lt;/strong&gt; (repetitive structure with variable data)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Identify which parts must be automated vs. manual&lt;/strong&gt; (scale vs. creative direction)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test one template end-to-end with real data&lt;/strong&gt; (uncover edge cases early)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Measure render time, cost, and failure rates&lt;/strong&gt; (validate assumptions with real numbers)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;video-automation-solvable-but-not-simple&quot;&gt;Video Automation: Solvable, But Not Simple&lt;/h2&gt;
&lt;p&gt;Video automation is harder than image automation. The timeline adds complexity. Rendering takes 100x longer. File sizes are larger. Platform requirements are stricter. Audio coordination matters. There’s no magic solution that makes these challenges disappear.&lt;/p&gt;
&lt;p&gt;But video automation is solvable when you respect the complexity and build accordingly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Design templates that handle variability&lt;/strong&gt; (text length changes, scene duration flexibility, multi-asset coordination)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Choose the right architecture&lt;/strong&gt; (batch processing for volume, real-time for immediacy, hybrid for collaboration)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimize infrastructure&lt;/strong&gt; (GPU acceleration, server-side rendering, proper codec licensing)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan for production&lt;/strong&gt; (error handling, monitoring, template versioning, cost management)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The teams that succeed with video automation don’t try to make it simple. They acknowledge it’s complex, design systems that handle that complexity, and automate what can be automated while keeping humans involved where judgment matters.&lt;/p&gt;
&lt;p&gt;Automation doesn’t eliminate work; it shifts work from repetitive execution to thoughtful system design.&lt;/p&gt;
&lt;p&gt;For more context on building creative automation systems that combine human creativity with programmatic scale, see our article on &lt;a href=&quot;https://img.ly/blog/editor-or-api-why-modern-creative-automation-needs-both/&quot;&gt;creative automation infrastructure&lt;/a&gt;. And if you’re ready to explore how timeline-based engines handle video automation specifically, check out the &lt;a href=&quot;https://img.ly/docs/cesdk/js/prebuilt-solutions/automated-video-generation-31187c/&quot;&gt;automated video generation documentation&lt;/a&gt; and &lt;a href=&quot;https://img.ly/docs/cesdk/js/create-video/timeline-editor-912252/&quot;&gt;video timeline editor guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Video automation at scale is possible. It just requires understanding what makes video different and building systems that respect those differences.&lt;/p&gt;
&lt;p&gt;Ready to test it? Start a &lt;a href=&quot;https://img.ly/forms/free-trial&quot;&gt;free trial&lt;/a&gt; or &lt;a href=&quot;https://img.ly/forms/contact-sales&quot;&gt;speak with our sales team&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Klaudia</dc:creator><media:content url="https://blog.img.ly/2026/01/automating-video-content-creation-1.jpg" medium="image"/><category>Insights</category><category>Creative Automation</category><category>Video Editing</category></item><item><title>Editor or API? Why Modern Creative Automation Needs Both</title><link>https://img.ly/blog/editor-or-api-why-modern-creative-automation-needs-both/</link><guid isPermaLink="true">https://img.ly/blog/editor-or-api-why-modern-creative-automation-needs-both/</guid><description>Evaluating creative automation at scale where designers, marketers, and engineers all work on the same system without blocking each other? You&apos;re in the right place.</description><pubDate>Fri, 16 Jan 2026 09:48:51 GMT</pubDate><content:encoded>&lt;p&gt;Here’s a scenario we see all the time: a marketing team designs beautiful campaign templates in a design software, hands them off to engineering, and then waits while developers painstakingly recreate everything using an API. When the design needs tweaking? Start the whole cycle over again. The result is double work, version drift, and frustrated teams on both sides.&lt;/p&gt;
&lt;p&gt;The creative automation market has created a false binary: choose “visual editor” platforms that let non-technical users design templates but can’t scale beyond manual work, or choose “automation API” services that generate assets programmatically but require developers for every template change.&lt;br&gt;
The truth is, modern creative automation isn’t a single-purpose tool decision—it’s an infrastructure decision. Teams need systems where humans design templates visually, systems generate variations at scale automatically, and humans can jump back in to refine outputs when needed. That requires both a visual editor UI and a headless engine API working from the same rendering foundation.&lt;/p&gt;
&lt;p&gt;In this article, we’ll explore why editor-only and API-only solutions each solve half the problem, what hybrid creative automation infrastructure looks like in practice, and how to evaluate whether your current stack can support workflows that combine human creativity with automation scale. (If you’re already evaluating CE.SDK and want to understand the technical implementation of server-side rendering specifically, see our &lt;a href=&quot;https://img.ly/blog/ce-sdk-renderer-creative-automation/&quot;&gt;deep-dive on the CE.SDK Renderer&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-editor-only-trap-when-manual-workflows-become-bottlenecks&quot;&gt;The Editor-Only Trap: When Manual Workflows Become Bottlenecks&lt;/h2&gt;
&lt;h3 id=&quot;what-editor-uis-do-well&quot;&gt;What Editor UIs Do Well&lt;/h3&gt;
&lt;p&gt;Visual template platforms excel at making creative work accessible. Designers and marketers can build templates without writing code, using familiar drag-and-drop interfaces and timeline editors. The learning curve is low, collaboration is intuitive, and stakeholders can preview exactly what they’re creating. For teams producing dozens of assets per week with moderate customization needs, these tools work beautifully.&lt;/p&gt;
&lt;h3 id=&quot;where-manual-workflows-break-down&quot;&gt;Where Manual Workflows Break Down&lt;/h3&gt;
&lt;p&gt;The problems start when volume scales beyond what humans can reasonably handle. An e-commerce brand launching 10,000 product banners with personalized messaging can’t manually create each variation in a visual editor. A social media team A/B testing 50 different audience segments needs programmatic generation, not copy-paste workflows. A print shop processing hundreds of customer orders daily can’t rely on manual template filling.&lt;/p&gt;
&lt;p&gt;Consider Swiss Post, whose customers create over a million personalized postcards each year. These aren’t cookie-cutter designs—they’re choosing from hundreds of customizable templates with personal photos, messages, and layout variations. No amount of manual work could handle that volume. Swiss Post needed a system where designers create templates once in a visual editor, defining variables for personalization, and then the backend automatically generates thousands of variations with GPU-accelerated server-side rendering. What you design is exactly what gets printed at scale—100% rendering consistency across a million outputs.&lt;/p&gt;
&lt;p&gt;The other challenge with editor-only platforms is pricing. When your business model depends on high-volume generation, per-export fees or usage-based pricing can make automation economically impractical. You’re essentially paying platform fees for work that should run as infrastructure.&lt;/p&gt;
&lt;p&gt;This doesn’t mean teams using editor-only platforms made the wrong choice. It means their needs evolved. What started as a template design problem became a creative automation infrastructure problem—and the tools didn’t evolve with them.&lt;/p&gt;
&lt;h2 id=&quot;the-automation-only-trap-when-you-cant-refine-the-output&quot;&gt;The Automation-Only Trap: When You Can’t Refine the Output&lt;/h2&gt;
&lt;h3 id=&quot;what-automation-apis-do-well&quot;&gt;What Automation APIs Do Well&lt;/h3&gt;
&lt;p&gt;On the opposite end, creative automation APIs deliver exactly what they promise: fast, scalable, programmatic asset generation. Developers integrate them into backend systems, connect them to databases, and generate thousands of variations automatically. For engineering-led teams with clear specifications and stable templates, this approach works efficiently.&lt;/p&gt;
&lt;h3 id=&quot;the-template-design-bottleneck&quot;&gt;The Template Design Bottleneck&lt;/h3&gt;
&lt;p&gt;The friction surfaces during template creation and iteration. Building templates through code means every design change requires developer time. Want to adjust font sizes, test a different layout, or preview how a template looks with real data? You’re writing code, deploying changes, and hoping the output matches expectations.&lt;/p&gt;
&lt;p&gt;This creates a dependency bottleneck. Marketing teams wait for engineering availability to update creative templates. Designers can’t prototype variations quickly because they need technical handoffs. Agency teams can’t show clients live previews during approval meetings—they’re locked into “request changes, wait for developer updates, review again” cycles.&lt;/p&gt;
&lt;h3 id=&quot;why-human-in-the-loop-matters&quot;&gt;Why ‘Human-in-the-Loop’ Matters&lt;/h3&gt;
&lt;p&gt;Even with fully automated generation, human oversight remains essential. AI-generated content needs review before publishing. Brand compliance requires manual checks. Client feedback demands quick adjustments. Research on human-in-the-loop workflows consistently shows they maintain quality while actually reducing total review time—when the infrastructure supports jumping between automation and manual refinement seamlessly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://img.ly/case-studies/imagebank&quot;&gt;ImageBank X&lt;/a&gt;&lt;/strong&gt;, a brand asset management platform, faced exactly this challenge. Their customers needed both automated brand compliance and the ability to make quick edits without breaking brand guidelines. By implementing in-browser editing with lockable template elements, they reduced presentation slide preparation from 15 minutes to 2 minutes while ensuring every output stayed on-brand. The key wasn’t eliminating human involvement—it was making human involvement fast and accessible when needed.&lt;/p&gt;
&lt;p&gt;When your creative automation API requires developers for every template adjustment, you’ve automated generation but created a new bottleneck in iteration. That’s the automation-only trap.&lt;/p&gt;
&lt;h2 id=&quot;how-modern-creative-automation-infrastructure-works&quot;&gt;How Modern Creative Automation Infrastructure Works&lt;/h2&gt;
&lt;h3 id=&quot;the-three-phases-of-hybrid-creative-automation&quot;&gt;The Three Phases of Hybrid Creative Automation&lt;/h3&gt;
&lt;p&gt;The solution isn’t choosing between visual editing and automation—it’s infrastructure that supports both within the same workflow. Modern creative automation follows a three-phase cycle:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Design Phase (Humans in Visual Editor)&lt;/strong&gt;&lt;br&gt;
Designers, marketers, or creative teams build templates using a visual editor. They define layouts, set typography, establish brand elements, and configure variables for personalization. Crucially, they’re working in a WYSIWYG environment where they see exactly what the final output will look like. No coding required, no technical handoffs, just creative work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Automation Phase (Systems via API)&lt;/strong&gt;&lt;br&gt;
Once templates are designed, backend systems take over. The same template definition gets processed through a headless engine API that generates hundreds, thousands, or millions of variations programmatically. Data comes from product databases, CRM systems, user inputs, or AI generation. The engine renders each variation server-side with the exact same output fidelity as the visual editor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Refinement Loop (Humans Review and Update)&lt;/strong&gt;&lt;br&gt;
When outputs need adjustment—because of new brand guidelines, A/B test results, seasonal campaigns, or client feedback—teams jump back into the visual editor, update the template, and automation immediately uses the new design. No code changes, no developer dependency, just iteration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI accelerates content creation in many of these workflows, but the core challenge remains the same: how humans and automation share the same rendering and template foundation.&lt;/strong&gt; The infrastructure needs to support both, regardless of whether your data comes from traditional databases or generative AI.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A simple way to think about hybrid creative automation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Editors define what can change&lt;/li&gt;
&lt;li&gt;APIs decide when and how often it changes&lt;/li&gt;
&lt;li&gt;The rendering engine guarantees how it looks&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;real-world-hybrid-workflows&quot;&gt;Real-World Hybrid Workflows&lt;/h3&gt;
&lt;p&gt;This isn’t theoretical. Teams are running these workflows in production today.&lt;/p&gt;
&lt;p&gt;Take &lt;strong&gt;&lt;a href=&quot;https://img.ly/case-studies/omneky&quot;&gt;Omneky&lt;/a&gt;&lt;/strong&gt;, an AI-powered advertising platform. Their AI generates ads automatically, but customers still need to review and adjust before launch—editing copy, applying brand fonts, or adapting layouts. Rather than forcing users to export to external tools, Omneky uses &lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;CE.SDK&lt;/a&gt; to parse AI-generated ads into editable templates. Users make adjustments directly in the workflow, then deploy. The result? 10x monthly signup growth and faster time-to-publish. The hybrid model let them combine AI speed with human refinement in a single infrastructure. (For a complete explanation of how CE.SDK’s unified engine enables this workflow, see our &lt;a href=&quot;https://img.ly/blog/ce-sdk-explained-embedded-editor-and-automation-engine-in-one-sdk/&quot;&gt;CE.SDK technical explainer&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://img.ly/case-studies/plai&quot;&gt;Plai&lt;/a&gt;&lt;/strong&gt;, an AI advertising platform, generates 30,000 to 37,000 creatives monthly across 11 advertising platforms. Their workflow: AI generates initial assets, users refine via the integrated editor, then deploy. This eliminated $1,000 to $10,000 monthly design costs per client while doubling Plai’s annual revenue. They integrated the full system in one month. As founder Logan Welbaum put it: “Fast to launch, that’s the biggest thing.” The hybrid infrastructure meant non-technical users could manage high-volume creative production without sacrificing quality or speed.&lt;/p&gt;
&lt;p&gt;Back to Swiss Post: a designer creates a postcard template once in the visual editor, defining variables for customer photos, messages, and personalization. The backend system then generates thousands of variations automatically with 100% consistent rendering—what you design is exactly what gets printed at scale. Over a million personalized postcards per year, managed by a workflow that balances human creativity in template design with automated generation at volume.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://img.ly/case-studies/digitas&quot;&gt;Digitas&lt;/a&gt;&lt;/strong&gt; runs a similar hybrid workflow for automotive campaigns. Designers create templates centrally for 600+ car dealers across 9-10 brands. Dealers then generate localized assets themselves in German, French, and Italian—adapting campaigns from days to seconds without manual design work. With 140 active campaigns generating thousands of assets, stakeholder satisfaction has been unanimous. Martin Röhr, Senior Project Manager: &lt;em&gt;“In all meetings, we had no negative feedback—absolutely nothing. Dealers are downloading, managers are happy, and we’re already planning the next automation features.”&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-rendering-consistency-matters&quot;&gt;Why Rendering Consistency Matters&lt;/h3&gt;
&lt;p&gt;The critical technical requirement for hybrid workflows is rendering consistency. If what you design in the visual editor doesn’t match what the automation API generates, you’re back to double work and version drift. Teams need infrastructure where the same rendering engine powers both the UI and the API—so a template designed on Tuesday generates perfectly on Wednesday at 3 AM when your automation job runs.&lt;/p&gt;
&lt;p&gt;This also enables true collaboration across roles. Designers focus on creative quality without learning APIs. Engineers integrate automation without recreating templates in code. Marketers adjust campaigns in real-time without waiting for technical handoffs. Everyone works in their area of expertise, connected by shared infrastructure.&lt;/p&gt;
&lt;h2 id=&quot;key-capabilities-your-creative-automation-stack-needs&quot;&gt;Key Capabilities Your Creative Automation Stack Needs&lt;/h2&gt;
&lt;p&gt;If you’re evaluating creative automation infrastructure—or realizing your current tools can’t support hybrid workflows—here are the key capabilities to look for:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Visual Template Editor (For Non-Technical Users)&lt;/strong&gt;&lt;br&gt;
Your creative team needs a full-featured editing interface with timeline controls, WYSIWYG previews, layer management, and the ability to define template variables. Bonus points for role-based access controls so you can lock brand elements while allowing content customization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Headless Engine API (For Technical Integration)&lt;/strong&gt;&lt;br&gt;
Your engineering team needs programmatic access to the same rendering engine that powers the visual editor. That means server-side rendering, batch processing capabilities, and APIs for controlling every aspect of asset generation. Look for Node.js support for backend integration and GPU-acceleration for high-volume processing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Platform Consistency&lt;/strong&gt;&lt;br&gt;
The editor and API should use the exact same rendering engine under the hood. No output drift, no “it looked different in the editor” surprises. This also means cross-platform support—web, mobile, desktop, server—all producing identical outputs from the same template definitions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Template Intelligence&lt;/strong&gt;&lt;br&gt;
Templates should support dynamic text variables, image placeholders, auto-formatting rules, and preset configurations. This is what allows one template to generate thousands of variations intelligently. Look for features like text overflow handling, responsive layouts, and conditional element visibility.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Scalability Features&lt;/strong&gt;&lt;br&gt;
For video generation at scale or high-volume image processing, GPU acceleration is essential. Your infrastructure should handle batch jobs efficiently, support multiple output formats (web, print, video, social), and maintain performance as volumes grow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. Developer Experience&lt;/strong&gt;&lt;br&gt;
Comprehensive APIs (Scene API, Block API, Asset API, Event API), SDKs for your target platforms (JavaScript, React, Vue, Angular, iOS, Android, Flutter, React Native), clear documentation, and extensibility through plugins. Your engineering team shouldn’t fight the infrastructure—they should extend it.&lt;/p&gt;
&lt;p&gt;Your creative automation infrastructure should include all six capabilities, not force you to choose between them. Editor-focused platforms typically excel at #1 but lack #2, #3, and #5. API-focused services deliver #2 and #5 but miss #1, #4, and collaboration workflows. Unified infrastructure is purpose-built to deliver all six.&lt;/p&gt;
&lt;h2 id=&quot;how-cesdk-delivers-the-hybrid-model&quot;&gt;How CE.SDK Delivers the Hybrid Model&lt;/h2&gt;
&lt;h3 id=&quot;unified-rendering-engine&quot;&gt;Unified Rendering Engine&lt;/h3&gt;
&lt;p&gt;IMG.LY’s &lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;CreativeEditor SDK (CE.SDK)&lt;/a&gt; was built specifically for this hybrid infrastructure model. At its core is a single C++ rendering engine that powers both the visual editor UI and the headless CreativeEngine API. This isn’t two separate systems with syncing—it’s one engine with dual interfaces, guaranteeing 100% rendering consistency between what you design and what you automate.&lt;/p&gt;
&lt;p&gt;That architectural decision solves the version drift problem completely. When a designer updates a template in the visual editor on Monday, the automation pipeline generating variations on Tuesday uses the exact same rendering logic. No surprises, no output differences, no “works in the editor but breaks in production” issues.&lt;/p&gt;
&lt;h3 id=&quot;from-template-design-to-automated-generation&quot;&gt;From Template Design to Automated Generation&lt;/h3&gt;
&lt;p&gt;Here’s how the workflow actually works in practice:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Your design team opens CE.SDK’s visual editor in their browser. They build a template using the drag-and-drop interface, timeline controls for video, and layer management. They define text variables for personalization, set image placeholders, and configure which elements are locked versus editable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; The template is saved with all its logic—variables, placeholders, layout rules, brand elements. This template definition is what both the editor and the API reference.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Your engineering team connects the CreativeEngine API to your backend systems—product databases, CRM platforms, AI content generation, whatever data sources drive personalization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; When the automation pipeline runs (scheduled jobs, triggered events, API calls), the CreativeEngine processes the template with your data, renders outputs server-side with GPU acceleration, and exports in your target formats (PNG, JPG, MP4, PDF/X for print, etc.).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; When templates need updates, designers make changes in the visual editor. The automation pipeline immediately uses the updated template—no code changes, no redeployment, just instant iteration.&lt;/p&gt;
&lt;p&gt;For example, an e-commerce platform uses CE.SDK’s visual editor to create product banner templates with their design team. Their engineering team then connects those templates to their product database via the Node.js API, automatically generating thousands of personalized banners overnight. When the marketing team wants to test new creative approaches, designers update the templates in the editor—and the automation pipeline immediately uses the new designs without any code changes.&lt;/p&gt;
&lt;h3 id=&quot;platform-and-framework-support&quot;&gt;Platform and Framework Support&lt;/h3&gt;
&lt;p&gt;CE.SDK provides SDKs for every major platform and framework: Web (vanilla JavaScript, React, Angular, Vue, Svelte), iOS (native Swift), Android (native Kotlin), React Native, Flutter, and Node.js for server-side automation. The same templates work across all platforms because they’re all using the same rendering engine underneath.&lt;/p&gt;
&lt;p&gt;This means you can build templates once and deploy them everywhere—web app editors, mobile apps, desktop applications, and server-side generation—with identical output quality. For teams managing multi-platform creative workflows, this eliminates the “rebuild for each platform” problem entirely.&lt;/p&gt;
&lt;p&gt;On the automation side, CE.SDK’s headless mode gives you full API access without any UI overhead. Batch process thousands of assets, generate video at scale with timeline API control, output print-ready PDFs with PDF/X support, and customize the entire rendering pipeline through the plugin system. For technical teams implementing high-volume server-side automation, our &lt;a href=&quot;https://img.ly/blog/ce-sdk-renderer-creative-automation/&quot;&gt;CE.SDK Renderer delivers GPU-accelerated rendering with enterprise-grade codec licensing&lt;/a&gt;—eliminating the maintenance overhead of FFmpeg or headless browser approaches. You’re not locked into our opinions about how automation should work—you control the infrastructure.&lt;/p&gt;
&lt;h2 id=&quot;comparing-approaches-editor-only-vs-api-only-vs-unified-infrastructure&quot;&gt;Comparing Approaches: Editor-Only vs. API-Only vs. Unified Infrastructure&lt;/h2&gt;
&lt;p&gt;Let’s compare how different approaches handle the key requirements of modern creative automation:&lt;/p&gt;



















































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Capability&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Editor-Only Platforms&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;API-Only Services&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;CE.SDK (Unified)&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Template Design&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Visual, intuitive, non-technical&lt;/td&gt;&lt;td&gt;Code-based, requires developers&lt;/td&gt;&lt;td&gt;Visual editor + code access&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Automation Scale&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Limited, manual bottlenecks&lt;/td&gt;&lt;td&gt;Unlimited, programmatic&lt;/td&gt;&lt;td&gt;Unlimited, programmatic&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Non-Technical Access&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Full access&lt;/td&gt;&lt;td&gt;Typically none, developer-dependent&lt;/td&gt;&lt;td&gt;Full access&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Programmatic Control&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Typically limited or none&lt;/td&gt;&lt;td&gt;Full API access&lt;/td&gt;&lt;td&gt;Full API access&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Rendering Consistency&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Editor-only (no automation)&lt;/td&gt;&lt;td&gt;API-only (no editor preview)&lt;/td&gt;&lt;td&gt;100% consistent (same engine)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Human-in-Loop Workflows&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Manual only&lt;/td&gt;&lt;td&gt;Difficult, requires dev work&lt;/td&gt;&lt;td&gt;Native support&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Iteration Speed&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Fast for manual, slow at scale&lt;/td&gt;&lt;td&gt;Slow (requires code changes)&lt;/td&gt;&lt;td&gt;Fast (editor updates = instant API changes)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Platform Deployment&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Typically web-based only&lt;/td&gt;&lt;td&gt;Typically server-side only&lt;/td&gt;&lt;td&gt;Web, mobile, desktop, server&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Volume Pricing&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Often penalizes scale&lt;/td&gt;&lt;td&gt;Developer time costs&lt;/td&gt;&lt;td&gt;Infrastructure pricing&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Team Collaboration&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Non-technical teams only&lt;/td&gt;&lt;td&gt;Technical teams only&lt;/td&gt;&lt;td&gt;Cross-functional teams&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Output Formats&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Typically limited export options&lt;/td&gt;&lt;td&gt;Multiple formats&lt;/td&gt;&lt;td&gt;Multiple formats (image, video, print)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Proven Scale&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Manual bottleneck at volume&lt;/td&gt;&lt;td&gt;Code-driven, hard to iterate&lt;/td&gt;&lt;td&gt;1M+ postcards/year, 30K+ creatives/month&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;This isn’t about one approach being “better”—it’s about matching infrastructure to workflow needs. If you’re a small team producing a few dozen assets per month with no automation requirements, an editor-only platform might be perfect. If you’re a developer-led team with stable templates and pure automation needs, an API-only service could work efficiently.&lt;/p&gt;
&lt;p&gt;But if your workflow involves both human creativity and automation scale—designers creating templates, systems generating variations, marketers refining outputs—you need infrastructure purpose-built for hybrid workflows. That’s where unified platforms like CE.SDK deliver value that single-purpose tools can’t match.&lt;/p&gt;
&lt;h2 id=&quot;the-future-of-creative-automation-infrastructure&quot;&gt;The Future of Creative Automation Infrastructure&lt;/h2&gt;
&lt;p&gt;Creative automation is becoming an infrastructure decision, not a tool category. The market is moving beyond single-purpose solutions toward unified platforms that support end-to-end workflows—from template design to automated generation to human refinement.&lt;/p&gt;
&lt;p&gt;Teams that combine human creativity with automation scale will win. AI generation is accelerating content production, but human oversight remains essential for quality, brand compliance, and strategic direction. The hybrid workflow—design visually, automate programmatically, refine collaboratively—is becoming the standard approach across industries.&lt;/p&gt;
&lt;p&gt;This also changes how teams think about roles and collaboration. Instead of “creative team” versus “engineering team” with handoffs between them, modern creative automation infrastructure enables everyone to work in their area of expertise simultaneously. Designers focus on creative quality. Engineers focus on integration and scale. Marketers focus on campaign strategy and iteration. The infrastructure connects them without forcing anyone into unfamiliar workflows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If your current setup forces designers to wait for developers—or forces developers to reimplement designs in code—you’re already paying the cost of the wrong infrastructure.&lt;/strong&gt; The question isn’t whether to change, but whether you’re building on a foundation that can scale with your needs.&lt;/p&gt;
&lt;p&gt;If you’re building creative automation infrastructure that needs both visual template creation and API-driven generation, &lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;CE.SDK&lt;/a&gt; provides the unified engine to support both.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand the platform: Read our &lt;a href=&quot;https://img.ly/blog/ce-sdk-explained-embedded-editor-and-automation-engine-in-one-sdk/&quot;&gt;CE.SDK explainer&lt;/a&gt; to learn how the unified engine, visual editor, and automation API work together&lt;/li&gt;
&lt;li&gt;See it in action: Explore our &lt;a href=&quot;https://img.ly/use-cases/creative-automation&quot;&gt;creative automation use cases&lt;/a&gt; to see how teams implement hybrid workflows&lt;/li&gt;
&lt;li&gt;Try the platform: Test CE.SDK to experience the editor-to-API workflow firsthand with our &lt;a href=&quot;https://img.ly/showcases/cesdk&quot;&gt;demos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Check out our &lt;a href=&quot;https://img.ly/blog/imgly-impact-report/&quot;&gt;data report based on 600+ customers, 28 structured customer interviews, and a self-reported customer survey&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The question isn’t whether you need an editor or an API. It’s whether your infrastructure can support both when your workflows demand it.&lt;/p&gt;</content:encoded><dc:creator>Klaudia</dc:creator><media:content url="https://blog.img.ly/2026/01/creative-automation-infrastructure.jpg" medium="image"/><category>Insights</category><category>Creative Automation</category></item><item><title>Introducing CE.SDK Renderer: The Missing Piece of Creative Automation</title><link>https://img.ly/blog/ce-sdk-renderer-creative-automation/</link><guid isPermaLink="true">https://img.ly/blog/ce-sdk-renderer-creative-automation/</guid><description>The CE.SDK Renderer brings fast, reliable, fully licensed server-side rendering to your backend unlocking true end-to-end creative automation with GPU acceleration, perfect fidelity, and scalable image, PDF, and video export.</description><pubDate>Mon, 24 Nov 2025 11:05:15 GMT</pubDate><content:encoded>&lt;p&gt;For years, teams have used &lt;strong&gt;CE.SDK&lt;/strong&gt; to power rich, customizable editing experiences across web, mobile, and desktop. But one critical component of true end-to-end creative automation remained challenging: &lt;strong&gt;server-side rendering&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Until now, export workflows depended on client devices, brittle FFmpeg scripts, or slow headless browser setups. While these approaches worked, they often fell short for enterprise scale workflows, AI pipelines, or high-volume creative automation.&lt;/p&gt;
&lt;p&gt;Today, we’re excited to introduce the &lt;a href=&quot;https://img.ly/docs/cesdk/renderer/get-started/overview-e18f40/&quot;&gt;&lt;strong&gt;CE.SDK Renderer&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;,&lt;/strong&gt; the native, GPU-accelerated server-side rendering engine that completes the CE.SDK ecosystem.&lt;/p&gt;
&lt;p&gt;The Renderer brings CE.SDK’s design engine to your backend with &lt;strong&gt;fast, compliant, enterprise-ready&lt;/strong&gt; export for images, PDFs, and video, enabling organizations to generate media at scale with full fidelity and predictable performance.&lt;/p&gt;
&lt;h2 id=&quot;why-the-cesdk-renderer-matters&quot;&gt;&lt;strong&gt;Why the CE.SDK Renderer Matters&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id=&quot;rendering-has-been-the-last-barrier-to-full-automation&quot;&gt;&lt;strong&gt;Rendering Has Been the Last Barrier to Full Automation&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Teams could design and generate templates programmatically, but producing the final asset—especially video—was constrained by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lack of GPU acceleration&lt;/li&gt;
&lt;li&gt;No native CE.SDK export on servers&lt;/li&gt;
&lt;li&gt;Legal requirements for H.264/H.265&lt;/li&gt;
&lt;li&gt;Unstable browser virtualization&lt;/li&gt;
&lt;li&gt;Node.js memory and performance limits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The CE.SDK Renderer solves all of this in one fell swoop with a native Linux binary packaged as a Docker container, built explicitly for backend media generation.&lt;/p&gt;
&lt;p&gt;Many of our SaaS customers have told us the same thing: they want a safe, supported alternative to FFmpeg that they don’t have to maintain themselves.&lt;/p&gt;
&lt;h2 id=&quot;what-the-cesdk-renderer-delivers&quot;&gt;&lt;strong&gt;What the CE.SDK Renderer Delivers&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The CE.SDK Renderer fully unlocks its value in the &lt;a href=&quot;http://IMG.LY&quot;&gt;IMG.LY&lt;/a&gt; ecosystem by providing a creative automation infrastructure layer for the user facing portion of &lt;a href=&quot;http://IMG.LY&quot;&gt;IMG.LY&lt;/a&gt;’s editor SDK. It enables two core capabilities that modern creative automation depends on: &lt;strong&gt;scalable rendering&lt;/strong&gt; and &lt;strong&gt;fully compliant video export:&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;gpu-accelerated-export-performance&quot;&gt;&lt;strong&gt;GPU-Accelerated Export Performance&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Native code and GPU acceleration allow extremely fast rendering. Ideal for heavy scenes, 4K+ requests, and large batch pipelines.&lt;/p&gt;
&lt;h3 id=&quot;fully-licensed-video-on-the-backend-h264h265&quot;&gt;&lt;strong&gt;Fully Licensed Video on the Backend (H.264/H.265)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;You can render MP4 video entirely on your servers with full licensing compliance. Powered by &lt;a href=&quot;https://fluendo.com/&quot;&gt;Fluendo&lt;/a&gt;’s AV stack, the Renderer provides enterprise-grade legal protection for H.264/H.265, eliminating the risks associated with unlicensed or open-source encoding. This gives teams the confidence to run video-heavy pipelines at scale.&lt;/p&gt;
&lt;h3 id=&quot;built-for-scale&quot;&gt;&lt;strong&gt;Built for Scale&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Perfect for workloads generating hundreds or thousands of creative variations: ads, postcards, dynamic social assets, catalogs, and more on stable maintainable infrastructure supplanting FFmpeg or brittle pipelines based on headless browsers.&lt;/p&gt;
&lt;h3 id=&quot;perfect-visual-fidelity&quot;&gt;&lt;strong&gt;Perfect Visual Fidelity&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Because it uses the same CE.SDK engine that powers the editing experience, the Renderer produces output that matches exactly what users see in the editor. This fidelity is critical for automated pipelines, ensuring every exported asset is accurate, consistent, and visually correct.&lt;/p&gt;
&lt;p&gt;Together, these unlock a complete creative automation workflow: &lt;strong&gt;design → generate → render&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;1125&quot; src=&quot;https://img.ly/_astro/Serverside-rendering_--1-_Z12wLhR.webp&quot; srcset=&quot;/_astro/Serverside-rendering_--1-_2sjmMl.webp 640w, /_astro/Serverside-rendering_--1-_Z2iVc0.webp 750w, /_astro/Serverside-rendering_--1-_6sJi2.webp 828w, /_astro/Serverside-rendering_--1-_Zbw4HI.webp 1080w, /_astro/Serverside-rendering_--1-_WsUly.webp 1280w, /_astro/Serverside-rendering_--1-_ZPsEqN.webp 1668w, /_astro/Serverside-rendering_--1-_Z12wLhR.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;async-exports&quot;&gt;Async Exports&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;A CE.SDK scene is created via browser editor, mobile app, or Node.js.&lt;/li&gt;
&lt;li&gt;The scene is sent to the &lt;strong&gt;Renderer&lt;/strong&gt;, running in Docker.&lt;/li&gt;
&lt;li&gt;The Renderer outputs: PNG/JPEG, PDF, MP4 (H.264/H.265)&lt;/li&gt;
&lt;li&gt;The rendered asset is returned to your application or batch pipeline.&lt;/li&gt;
&lt;li&gt;Users can continue editing while export happens asynchronously.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This removes export load from the client entirely and opens the door for sophisticated backend media generation.&lt;/p&gt;
&lt;h3 id=&quot;batch-rendering&quot;&gt;Batch Rendering&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Multiple CE.SDK templates are prepared for automation.&lt;/li&gt;
&lt;li&gt;An external data source (CSV, JSON, API) is connected to the templates to generate all design variants.&lt;/li&gt;
&lt;li&gt;Each resulting scene is sent as a separate job to the &lt;strong&gt;Renderer&lt;/strong&gt; running in Docker.&lt;/li&gt;
&lt;li&gt;The Renderer outputs PNG/JPEG, PDF, or MP4 for every variant.&lt;/li&gt;
&lt;li&gt;All assets are returned to your backend or stored in your pipeline, enabling large-scale automated creative production.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;example-use-case-hyper-personalized-video-ads-at-massive-scale&quot;&gt;&lt;strong&gt;Example Use Case: Hyper-Personalized Video Ads at Massive Scale&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;One of our customers runs large demographic-targeted campaigns where a single master video must transform into tens of thousands of personalized variants. Instead of showing the same creative to everyone, they tailor each video to the viewer’s profile think &lt;em&gt;German, mid-30s, dog owner&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;From one base creative, their system dynamically generates up to &lt;strong&gt;100,000 unique versions&lt;/strong&gt;. Their backend assembles all scene variations in Node.js, then hands them off to the CE.SDK Renderer to produce the final video files.&lt;/p&gt;
&lt;p&gt;Before adopting CE.SDK Renderer, this workflow was painful: brittle FFmpeg scripts, unpredictable render results across machines, codec licensing concerns, and processing times that stretched over days.&lt;/p&gt;
&lt;p&gt;Maintaining this setup consumed engineering time that would have been better spent on improving the creative logic itself.&lt;/p&gt;
&lt;p&gt;With CE.SDK Renderer, the entire pipeline became both scalable and predictable. They now render thousands of variants with consistent results, fully licensed codecs, and throughput high enough to keep up with real-world campaign demands. Instead of fighting their rendering stack, their team can focus on creative automation and campaign performance.&lt;/p&gt;
&lt;p&gt;This is just one example of how CE.SDK Renderer enables production-grade, high-volume personalized media at scale something that previously required complex custom infrastructure.&lt;/p&gt;
&lt;h2 id=&quot;licensing-options&quot;&gt;&lt;strong&gt;Licensing Options&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id=&quot;open-source-renderer&quot;&gt;&lt;strong&gt;Open-Source Renderer&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;A lightweight version using open-source codecs. It’s fast, simple to run, and great for prototyping or early-stage products. Because it doesn’t include licensed H.264/H.265 encoding, it isn’t suitable for enterprise video production or legally compliant distribution.&lt;/p&gt;
&lt;h3 id=&quot;av-licensed-renderer-fluendo&quot;&gt;&lt;strong&gt;AV-Licensed Renderer (Fluendo)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;This edition includes fully licensed H.264/H.265 encoding powered by &lt;a href=&quot;https://fluendo.com/&quot;&gt;Fluendo&lt;/a&gt;, ensuring your video exports meet all patent and compliance requirements. It uses concurrency-based licensing and is designed for production workloads, regulated industries, and any platform generating video at scale.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;By bringing fast, compliant, and scalable rendering to the backend, the CE.SDK Renderer removes one of the biggest engineering hurdles in creative automation. Whether you’re generating hundreds or hundreds of thousands of assets, it gives you the tools to build stable, modern media pipelines without the maintenance burden. The simplest path to production-grade creative automation.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/forms/free-trial&quot;&gt;Start your free trial&lt;/a&gt; today and get started in minutes.&lt;/p&gt;</content:encoded><dc:creator>Jan</dc:creator><media:content url="https://blog.img.ly/2025/11/render-automation-nodejs-alternative-creative-sdk-imgly-export-video.jpg" medium="image"/><category>Creative Automation</category><category>Server-side Video</category></item><item><title>A Guide to Creative Automation with CE.SDK &amp; Javascript</title><link>https://img.ly/blog/a-guide-to-creative-automation-with-ce-sdk-javascript/</link><guid isPermaLink="true">https://img.ly/blog/a-guide-to-creative-automation-with-ce-sdk-javascript/</guid><description>Discover how to build end-to-end creative automation with CE.SDK and React. This guide shows you how to create templates with placeholders, connect them to external data, and automatically generate personalized assets at scale.</description><pubDate>Fri, 05 Sep 2025 11:14:31 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/embed/GtORNyzjOq0?feature=oembed&quot;&gt;https://www.youtube.com/embed/GtORNyzjOq0?feature=oembed&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-is-creative-automation&quot;&gt;What is Creative Automation?&lt;/h2&gt;
&lt;p&gt;Creative Automation is the process of generating visual content, images, videos, designs, programmatically, often in response to structured inputs like product data, user attributes, or campaign parameters.&lt;/p&gt;
&lt;p&gt;Instead of designing each asset manually, creative automation enables you to define templates and rules that the system uses to produce content at scale.&lt;/p&gt;
&lt;p&gt;This approach accelerates production workflows and enables personalization at scale, multivariate testing, and omni-channel consistency without stretching design resources.&lt;/p&gt;
&lt;h2 id=&quot;creative-automation--generative-ai&quot;&gt;Creative Automation &amp;#x26; Generative AI&lt;/h2&gt;
&lt;p&gt;Of course, you cannot talk about creative automation in this day and age without also discussing the role of generative AI. Gen AI introduces intelligent content generation into the pipeline making it easy to generate headlines, product descriptions and entire design elements like illustrations, videos or voiceovers and background music.&lt;/p&gt;
&lt;p&gt;When paired with a robust editing and rendering engine like &lt;strong&gt;CE.SDK&lt;/strong&gt;, generative AI becomes even more powerful. A design editor that sits at the intersection between the human decision maker and raw AI generated creatives ensures that those creatives can be curated, refined and deployed in an orderly fashion. This usually works by employing design templates that provide extension points for dynamic data through variables and placeholders and that AI generated content can be merged into for a brand-safe final creative.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/case-studies/omneky&quot;&gt;Our customer Omneky&lt;/a&gt; provides an excellent example of how these components can be orchestrated for maximum efficiency and productivity. Omneky is an ad tech platform that uses generative AI to automatically generate creatives and ad copy for a company based on their product and positioning, these are then interpolated into CE.SDK template to create ad variations. These variations are then tested across ad channels and the results used to inform further refinement and ad selection. This is a prime example of how to combine creative automation and editors to tame the chaotic fountain of AI creatives.&lt;/p&gt;
&lt;h2 id=&quot;how-cesdk-enables-creative-automation&quot;&gt;How CE.SDK Enables Creative Automation&lt;/h2&gt;
&lt;p&gt;Let us get into the meat and potatoes of creative automation and sketch out the requirements of a creative automation solution and an end to end example with CE.SDK. &lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;IMG.LY’s &lt;strong&gt;CreativeEditor SDK (CE.SDK)&lt;/strong&gt;&lt;/a&gt; offers both a user interface for design-time flexibility and a graphics processing engine API for runtime automation.&lt;/p&gt;
&lt;h3 id=&quot;design-once-automate-everywhere&quot;&gt;Design Once, Automate Everywhere&lt;/h3&gt;
&lt;p&gt;With CE.SDK, you create &lt;strong&gt;design templates&lt;/strong&gt; that define placeholders, variables, and lockable design elements. These templates ensure brand consistency while allowing dynamic content generation. Whether you’re building social ads, team cards, or product visuals, the structure remains stable while the content varies. The diagram below illustrates these components using a classical mail merge example. The user creates a design template within the editor and defines certain variables like &lt;code&gt;first_name&lt;/code&gt; and &lt;code&gt;address&lt;/code&gt;, a data source of address data can now be merged with this template using the engine API: &lt;code&gt;variable: setString&lt;/code&gt; to create an individualized postcard for every data point in the set:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2106px) 2106px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2106&quot; height=&quot;1142&quot; src=&quot;https://img.ly/_astro/Screenshot-2025-09-02-at-10.37.28_t8Kns.webp&quot; srcset=&quot;/_astro/Screenshot-2025-09-02-at-10.37.28_1dUVmO.webp 640w, /_astro/Screenshot-2025-09-02-at-10.37.28_Zk7OGd.webp 750w, /_astro/Screenshot-2025-09-02-at-10.37.28_1R5JUa.webp 828w, /_astro/Screenshot-2025-09-02-at-10.37.28_ZChl63.webp 1080w, /_astro/Screenshot-2025-09-02-at-10.37.28_1aY7if.webp 1280w, /_astro/Screenshot-2025-09-02-at-10.37.28_1hkYAr.webp 1668w, /_astro/Screenshot-2025-09-02-at-10.37.28_Z1X6d9H.webp 2048w, /_astro/Screenshot-2025-09-02-at-10.37.28_t8Kns.webp 2106w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;under-the-hood-cesdk-architecture&quot;&gt;Under the Hood: CE.SDK Architecture&lt;/h2&gt;
&lt;p&gt;In order to fully understand how to implement creative automation workflows with CE.SDK we need a basic mental model of its architecture. CE.SDK is built on two distinct layers, giving you total flexibility and control.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;UI Layer&lt;/strong&gt; is the customizable frontend allowing users to design and edit different media types from photos, design compositions such as collages, videos or mixed media.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Engine Layer&lt;/strong&gt; exposes the Creative Engine, CE.SDK’s core rendering and editing engine, through an API. The architecture of CE.SDK is built around a layered approach that separates concerns while ensuring cross-platform consistency:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**Engine Core (C++)**At the foundation lies the &lt;strong&gt;Creative Engine&lt;/strong&gt;, a high-performance rendering and editing core written in C++. This is where all processing, layouting, and rendering logic happens, guaranteeing precision and fidelity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Layer&lt;/strong&gt;On top of the core sits the &lt;strong&gt;API Layer&lt;/strong&gt;, which exposes the engine’s capabilities in a consistent, high-level interface. This abstraction allows developers to focus on creative logic without dealing directly with low-level rendering details.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-Platform Targets&lt;/strong&gt;The same engine and API are &lt;strong&gt;cross-compiled to iOS, Android, Web, and server environments&lt;/strong&gt;, ensuring that rendering results remain identical regardless of platform. This uniformity is critical for automation workflows, where assets may be produced or consumed across multiple devices and services.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Headless Mode&lt;/strong&gt;Beyond powering interactive UIs, the engine can also be run &lt;strong&gt;headlessly&lt;/strong&gt;. In this mode, CE.SDK operates without a frontend, enabling automated scenarios such as bulk rendering of images or videos, dynamic template generation, and data-driven personalization at scale.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This modular architecture ensures that whether you are building a &lt;strong&gt;custom editing UI&lt;/strong&gt; or running &lt;strong&gt;automated creative workflows on the server&lt;/strong&gt;, every output is backed by the same reliable, cross-platform engine.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1926px) 1926px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1926&quot; height=&quot;1880&quot; src=&quot;https://img.ly/_astro/CESDK-architecture_283XmL.webp&quot; srcset=&quot;/_astro/CESDK-architecture_Z27QaY4.webp 640w, /_astro/CESDK-architecture_1cBoGo.webp 750w, /_astro/CESDK-architecture_WALWv.webp 828w, /_astro/CESDK-architecture_zTc2L.webp 1080w, /_astro/CESDK-architecture_ZjscFX.webp 1280w, /_astro/CESDK-architecture_Zx5Uoe.webp 1668w, /_astro/CESDK-architecture_283XmL.webp 1926w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;how-to-integrate-creative-automation-into-your-workflow&quot;&gt;How to Integrate Creative Automation into Your Workflow&lt;/h2&gt;
&lt;h3 id=&quot;step-1-integrate-cesdk&quot;&gt;Step 1: Integrate CE.SDK&lt;/h3&gt;
&lt;p&gt;Even if you don’t plan to use the CE.SDK UI for end users, you still need to spin it up locally or within internal tooling to create templates. Let’s install CE.SDK and configure it to the advanced UI, in Creator mode.&lt;/p&gt;
&lt;p&gt;CE.SDK by default comes with two UI flavors. The design or default UI config exposes all the essentials for productive design editing by most users, ideal for adapting templates. An advanced UI fine grained editing controls and the ability to define placeholder elements and control what users can change.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The “Creator” role allows setting constraints on template elements, while the “Adopter” role is focused on adapting these elements.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creator: Set constraints and manage template settings.&lt;/li&gt;
&lt;li&gt;Adopter: Edit elements within the bounds set by the Creator.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Install CE.SDK following &lt;a href=&quot;https://img.ly/docs/cesdk/js/get-started/overview-e18f40/&quot;&gt;one of our guides&lt;/a&gt; and explore the entire &lt;a href=&quot;https://img.ly/showcases/cesdk/headless-design/web&quot;&gt;demo here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can find the complete demo here&lt;/p&gt;
&lt;h3 id=&quot;configuring-cesdk-for-template-creation&quot;&gt;Configuring CE.SDK for Template Creation&lt;/h3&gt;
&lt;p&gt;Below is a sample configuration. Notice how we set &lt;code&gt;role: &apos;Creator&apos;&lt;/code&gt; and &lt;code&gt;view: &apos;advanced&apos;&lt;/code&gt; to unlock template authoring features. We also configure an &lt;code&gt;onSave&lt;/code&gt; callback to export complete &lt;strong&gt;template archives&lt;/strong&gt; — these include all necessary resources (images, fonts, stickers, etc.) in a &lt;strong&gt;self-contained package&lt;/strong&gt;. Archives are what you’ll deliver to adopters or use for data-driven automation later.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  theme: &lt;/span&gt;&lt;span&gt;&apos;dark&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  role: &lt;/span&gt;&lt;span&gt;&apos;Creator&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  callbacks: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    onExport: &lt;/span&gt;&lt;span&gt;&apos;download&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    onUpload: &lt;/span&gt;&lt;span&gt;&apos;local&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    onSave&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sceneString&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; string&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        // assumes engine is available in scope&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; blob&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.scene.&lt;/span&gt;&lt;span&gt;saveToArchive&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; formData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; FormData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        formData.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;file&apos;&lt;/span&gt;&lt;span&gt;, blob);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        await&lt;/span&gt;&lt;span&gt; fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/upload&apos;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          method: &lt;/span&gt;&lt;span&gt;&apos;POST&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          body: formData,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        console.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Save failed&apos;&lt;/span&gt;&lt;span&gt;, error);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  ui: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    elements: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      view: &lt;/span&gt;&lt;span&gt;&apos;advanced&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      panels: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        inspector: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          show: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          position: &lt;/span&gt;&lt;span&gt;&apos;right&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      dock: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        iconSize: &lt;/span&gt;&lt;span&gt;&apos;normal&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        hideLabels: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      navigation: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        action: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          export: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            show: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            format: [&lt;/span&gt;&lt;span&gt;&apos;image/png&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;application/pdf&apos;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2-build-smart-templates&quot;&gt;Step 2: Build Smart Templates&lt;/h3&gt;
&lt;p&gt;With CE.SDK running in &lt;strong&gt;Creator mode&lt;/strong&gt;, we can now define &lt;strong&gt;editable templates&lt;/strong&gt;. These templates can include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Named blocks&lt;/strong&gt; → Easy-to-reference design regions (e.g., &lt;code&gt;PodcastCover&lt;/code&gt;, &lt;code&gt;PodcastBadge&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Variables&lt;/strong&gt; → Text placeholders enclosed in double curly braces (e.g., &lt;code&gt;{{Message}}&lt;/code&gt;, &lt;code&gt;{{PodcastName}}&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, here’s a &lt;strong&gt;podcast cover template&lt;/strong&gt; with two text variables and named blocks. The variables make it possible to inject data programmatically later.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Podcast template:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Variables: {{Message}}, {{PodcastName}}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- Named blocks: PodcastCover, PodcastBadge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we’re in Creator mode, we can also enforce &lt;strong&gt;brand constraints,&lt;/strong&gt; for instance, locking background colors, logos, or fonts so that adopters (or automated processes) don’t accidentally override brand-critical assets.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;1053&quot; src=&quot;https://img.ly/_astro/Screenshot-2025-09-04-at-13.01.25_Zk5tHC.webp&quot; srcset=&quot;/_astro/Screenshot-2025-09-04-at-13.01.25_1q0By1.webp 640w, /_astro/Screenshot-2025-09-04-at-13.01.25_ZPg61j.webp 750w, /_astro/Screenshot-2025-09-04-at-13.01.25_Z24JzkD.webp 828w, /_astro/Screenshot-2025-09-04-at-13.01.25_ZMaA2F.webp 1080w, /_astro/Screenshot-2025-09-04-at-13.01.25_ZnnIT1.webp 1280w, /_astro/Screenshot-2025-09-04-at-13.01.25_Z1HnUMn.webp 1668w, /_astro/Screenshot-2025-09-04-at-13.01.25_Zk5tHC.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-3-automate-asset-creation&quot;&gt;Step 3: Automate Asset Creation&lt;/h3&gt;
&lt;p&gt;Once you have a template, the next step is automation. Using the &lt;strong&gt;headless Creative Engine&lt;/strong&gt;, you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Populate placeholders with &lt;strong&gt;external data&lt;/strong&gt; (e.g., from APIs).&lt;/li&gt;
&lt;li&gt;Swap images, update text variables, or adjust styling dynamically.&lt;/li&gt;
&lt;li&gt;Export the results at scale into formats like PNG, PDF, or even video.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;example-dynamic-podcast-covers&quot;&gt;Example: Dynamic Podcast Covers&lt;/h3&gt;
&lt;p&gt;In this demo, we connect to the &lt;strong&gt;Apple iTunes API&lt;/strong&gt; to fetch podcast data. Whenever a podcast is selected (or user input changes), our &lt;code&gt;fillTemplate&lt;/code&gt; function updates the CE.SDK scene.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://blog.img.ly/2025/09/podcast-data-source.mov&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Key concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Blocks API&lt;/strong&gt; → Query template blocks by name (&lt;code&gt;engine.block.findByName(&apos;PodcastBadge&apos;)&lt;/code&gt;) and replace their fills.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fills&lt;/strong&gt; → Blocks can have images, solid colors, gradients, or videos. For podcast artwork, we set &lt;code&gt;imageFileURI&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Variables API&lt;/strong&gt; → Set text variables directly (&lt;code&gt;engine.variable.setString(&apos;Message&apos;, &apos;Hello World&apos;)&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; fillTemplate&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;engine&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; CreativeEngine&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; { r, g, b } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; backgroundColorRGBA;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    engine.block.&lt;/span&gt;&lt;span&gt;setColor&lt;/span&gt;&lt;span&gt;(page, &lt;/span&gt;&lt;span&gt;&apos;fill/solid/color&apos;&lt;/span&gt;&lt;span&gt;, { r, g, b, a: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (podcast) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; photoBlocks&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;findByName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;PodcastCover&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      photoBlocks.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;photoBlock&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; number&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; photoFillBlock&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;getFill&lt;/span&gt;&lt;span&gt;(photoBlock);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          photoFillBlock,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &apos;fill/image/imageFileURI&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          podcast.artworkUrl600&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; colorTheme&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getThemeColorFromBackgroundColor&lt;/span&gt;&lt;span&gt;(backgroundColorRGBA);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;badgeBlock&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;findByName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;PodcastBadge&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      engine.block.&lt;/span&gt;&lt;span&gt;getFill&lt;/span&gt;&lt;span&gt;(badgeBlock),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &apos;fill/image/imageFileURI&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      caseAssetPath&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        `/podcast-badge-${&lt;/span&gt;&lt;span&gt;colorTheme&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; &apos;light&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; &apos;black&apos;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &apos;white&apos;}.png`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // set text variables&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    engine.variable.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Message&apos;&lt;/span&gt;&lt;span&gt;, debouncedMessage &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &apos;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    engine.variable.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &apos;PodcastName&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      podcast &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; podcast.collectionName &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &apos;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      // set text colors based on background theme&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;messageBlock&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;findByName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Message &amp;#x26; Name&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; rgb&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      colorTheme &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &apos;dark&apos;&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; { r: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, g: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, b: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { r: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, g: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, b: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      engine.block.&lt;/span&gt;&lt;span&gt;setTextColor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      messageBlock,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;rgb, a: &lt;/span&gt;&lt;span&gt;0.75&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &quot;{{Message}}&quot;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    engine.block.&lt;/span&gt;&lt;span&gt;setTextColor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      messageBlock,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      { &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;rgb, a: &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       &quot;{{Message}}&quot;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  };&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, you’ve created a &lt;strong&gt;data-driven creative automation pipeline&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Templates authored with &lt;strong&gt;placeholders and constraints&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Dynamic population of text and image fields via external data.&lt;/li&gt;
&lt;li&gt;Export to &lt;strong&gt;high-quality assets&lt;/strong&gt; at scale.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This workflow is the foundation for &lt;strong&gt;personalized marketing campaigns, automated content production, and AI-driven design pipelines&lt;/strong&gt; with CE.SDK.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Creative Automation isn’t just about speed it’s about &lt;strong&gt;scalability, consistency, and customization&lt;/strong&gt;. &lt;a href=&quot;http://IMG.LY&quot;&gt;IMG.LY&lt;/a&gt;’s CE.SDK gives you the tools to build powerful, flexible automation workflows tailored to your brand and users.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/forms/free-trial&quot;&gt;Start you free trial today&lt;/a&gt; and integrate content automation into your application.&lt;/p&gt;</content:encoded><dc:creator>Jan</dc:creator><media:content url="https://blog.img.ly/2025/09/Creative-Automation-Cover.png" medium="image"/><category>Creative Automation</category></item><item><title>How To: Video Generation With Javascript</title><link>https://img.ly/blog/how-to-video-generation-with-javascript/</link><guid isPermaLink="true">https://img.ly/blog/how-to-video-generation-with-javascript/</guid><description>Learn how to programmatically create videos at scale with Javascript and CreativeEditor SDK.</description><pubDate>Wed, 22 Jan 2025 09:53:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Follow this tutorial to learn how to programmatically create videos in JavaScript directly in the browser.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In 2025 video is firmly established as powerhouse for engagement, gaining even more traction with the rise of short-form videos. After all, as humans, we naturally resonate with video content more than any other type of media.&lt;/p&gt;
&lt;p&gt;The problem is that video generation is challenging—and doing it within the browser through JavaScript makes things even more complex. Fortunately, production-grade solutions like &lt;a href=&quot;https://img.ly/docs/cesdk/js/prebuilt-solutions/video-editor-9e533a/&quot;&gt;CreativeEditor SDK (CE.SDK)&lt;/a&gt; make the process not only possible but also easy to implement.&lt;/p&gt;
&lt;p&gt;In this guide, you will learn how to programmatically generate videos in the browser via JavaScript using CreativeEditor SDK. We will cover various applications, including merging videos, adding audio tracks, and even integrating AI features.&lt;/p&gt;
&lt;p&gt;Let’s dive in!&lt;/p&gt;
&lt;h2 id=&quot;why-programmatic-video-generation-matters&quot;&gt;Why Programmatic Video Generation Matters&lt;/h2&gt;
&lt;p&gt;Videos are dominating social media, marketing, and online platforms such as e-commerce platforms (e.g., to showcase products). Numerous studies have proven that &lt;a href=&quot;https://thesocialshepherd.com/blog/video-marketing-statistics&quot;&gt;video is the most effective form of media&lt;/a&gt; for marketing, as it resonates deeply with us.&lt;/p&gt;
&lt;p&gt;As the demand for personalized and dynamic content increases, traditional video production methods are becoming inefficient. The proliferation of channels with different demands on size and video quality as well as the need to create personalized videos at scale means that automated video creation is becoming indispensable. Most existing solutions run batch processing of videos on the server after gathering input data from various source. However, while server-side processing quickly becomes costly and introduces significant communication overhead client devices have become more powerful than ever.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;CreativeEditor SDK&lt;/a&gt; bridges that gap by simplifying programmatic video generation in JavaScript within modern browsers. With CE.SDK, you can automate repetitive tasks and define scalable, personalized, and engaging video creation workflows, ensuring each generated video resonates with its target audience.&lt;/p&gt;
&lt;h2 id=&quot;get-started-with-cesdk-for-javascript&quot;&gt;Get Started with CE.SDK for JavaScript&lt;/h2&gt;
&lt;p&gt;Now that we established why programmatic video generation is important, you are ready to get started with the tool designed for it: CreativeEditor SDK.&lt;/p&gt;
&lt;p&gt;Learn how to integrate CE.SDK for video editing into a vanilla JavaScript application!&lt;/p&gt;
&lt;p&gt;You can access the code for this tutorial on &lt;a href=&quot;https://github.com/imgly/video-generation-js&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;requirements&quot;&gt;Requirements&lt;/h3&gt;
&lt;p&gt;The only prerequisites to follow this tutorial are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A modern browser&lt;/strong&gt;: CreativeEditor SDK runs directly in your browser’s JavaScript engine, so no additional setup is required. Any &lt;a href=&quot;https://caniuse.com/wasm&quot;&gt;browser supporting WASM&lt;/a&gt; is enough.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A CreativeEditor SDK license&lt;/strong&gt;: If you do not have one yet, &lt;a href=&quot;https://img.ly/forms/free-trial&quot;&gt;sign up for a free trial&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your web application uses Node.js, ensure you have the &lt;a href=&quot;https://nodejs.org/en/download&quot;&gt;latest stable versions of both Node.js and NPM installed&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;import-the-library&quot;&gt;Import the library&lt;/h3&gt;
&lt;p&gt;In a vanilla JavaScript application, create a JavaScript module file (e.g., video-editor.js) and import the CreativeEditor SDK engine library inside it:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;jsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; CreativeEngine &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&amp;#x3C;https://cdn.img.ly/packages/imgly/cesdk-engine/1.42.0/index.js&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: In this example, the SDK is served from our CDN for convenience. In a production environment, it is recommended to host all &lt;a href=&quot;https://img.ly/docs/cesdk/js/serve-assets-b0827c/&quot;&gt;assets and libraries on your own servers&lt;/a&gt; for improved control and performance.&lt;/p&gt;
&lt;p&gt;To use the video-editor.js module in an HTML page, import it with this line:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;module&quot;&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;video-editor.js&quot;&lt;/span&gt;&lt;span&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules&quot;&gt;type=“module”&lt;/a&gt; attribute tells the browser to treat the script as an ES6 module, so that you can use import and export statements.&lt;/p&gt;
&lt;p&gt;Alternatively, if you are using a module bundler like Webpack, Rollup, Parcel, or Vite, add the &lt;a href=&quot;https://www.npmjs.com/package/@cesdk/engine&quot;&gt;@cesdk/engine&lt;/a&gt; npm package to your project’s dependencies:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; @cesdk/cesdk-js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, you can then import the headless SDK into your JavaScript code as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;jsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; CreativeEngine &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;@cesdk/engine&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well done!&lt;/p&gt;
&lt;h2 id=&quot;setting-up-your-environment&quot;&gt;&lt;strong&gt;Setting Up Your Environment&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In your JavaScript module, right below the library import, initialize the &lt;a href=&quot;https://img.ly/docs/cesdk/node/get-started/overview-e18f40/&quot;&gt;CreativeEditor SDK headless engine&lt;/a&gt; using the following code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;jsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// src/video-editor.js&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; CreativeEngine &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&amp;#x3C;https://cdn.img.ly/packages/imgly/cesdk-engine/1.42.0/index.js&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// your CE.SDK license and user configs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  license: &lt;/span&gt;&lt;span&gt;&apos;&amp;#x3C;YOUR_LICENSE&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CreativeEngine.&lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt;(config).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;engine&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // attach the engine canvas to the DOM&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;cesdk_container&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(engine.element);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // do something with your instance of CreativeEditor SDK...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  // detach the engine and clean up its resource&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  engine.element.&lt;/span&gt;&lt;span&gt;remove&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  engine.&lt;/span&gt;&lt;span&gt;dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;&amp;#x3C;YOUR_LICENSE&gt;&lt;/code&gt; with the license key provided in your CreativeEditor SDK subscription.&lt;/p&gt;
&lt;p&gt;Ensure that the HTML page importing &lt;code&gt;video-editor.js&lt;/code&gt; contains the following &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt; element with the &lt;code&gt;cesdk_container&lt;/code&gt; ID:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;cesdk_container&quot;&lt;/span&gt;&lt;span&gt; style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;width: 100%; height: 100vh;&quot;&lt;/span&gt;&lt;span&gt;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://img.ly/docs/cesdk/js/get-started/overview-e18f40/&quot;&gt;&lt;code&gt;CreativeEngine.init()&lt;/code&gt;&lt;/a&gt; method will mount the editor engine component within this div.&lt;/p&gt;
&lt;p&gt;Congratulations! You have successfully integrated the video editing engine.&lt;/p&gt;
&lt;h2 id=&quot;load-a-video&quot;&gt;&lt;strong&gt;Load a Video&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Use the logic below inside the body of the CreativeEndine.init() callback function to load a video in CE.SDK:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;jsx&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// initialize a new video scene&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; scene&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.scene.&lt;/span&gt;&lt;span&gt;createVideo&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// create a page block and attach it to the scene&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; page&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;page&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;appendChild&lt;/span&gt;&lt;span&gt;(scene, page);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// set the dimensions of the video page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setWidth&lt;/span&gt;&lt;span&gt;(page, &lt;/span&gt;&lt;span&gt;720&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setHeight&lt;/span&gt;&lt;span&gt;(page, &lt;/span&gt;&lt;span&gt;1280&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// create a graphic block to hold the video content&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// and set its shape to a rectangle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; video&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;graphic&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setShape&lt;/span&gt;&lt;span&gt;(video, engine.block.&lt;/span&gt;&lt;span&gt;createShape&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;rect&apos;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// create a fill type for the video and set the video file URI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; videoFill&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;createFill&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;video&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  videoFill,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;fill/video/fileURI&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;&amp;#x3C;https://cdn.img.ly/assets/demo/v2/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// apply the video fill to the video graphic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setFill&lt;/span&gt;&lt;span&gt;(video, videoFill);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// create a track block to manage the video timeline&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// and attache the track to the page and the video graphic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// to the track&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; track&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;track&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;appendChild&lt;/span&gt;&lt;span&gt;(page, track);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;appendChild&lt;/span&gt;&lt;span&gt;(track, video);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// make the track cover the parent block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;fillParent&lt;/span&gt;&lt;span&gt;(track);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This initializes a video scene using &lt;a href=&quot;https://img.ly/docs/cesdk/js/concepts/scenes-e8596d/&quot;&gt;&lt;code&gt;createVideo()&lt;/code&gt;&lt;/a&gt; and creates a &lt;code&gt;&quot;graphic&quot;&lt;/code&gt; block to display the video in a rectangle, adding it to the page. Next, it loads a video from a remote resource and appends it as a track to the parent block.&lt;/p&gt;
&lt;p&gt;For a complete explanation of how the above code works, &lt;a href=&quot;https://img.ly/docs/cesdk/js/create-video-c41a08/&quot;&gt;refer to the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you comment out the cleanup part, this is what you should see in your browser application:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 608px) 608px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;608&quot; height=&quot;697&quot; src=&quot;https://img.ly/_astro/JS-video-generation-video-fill_Z1LSqkq.webp&quot; srcset=&quot;/_astro/JS-video-generation-video-fill_Z1LSqkq.webp 608w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Notice how the video from the remote URI has been loaded into the &lt;/p&gt;&lt;div&gt; element where CreativeEditor SDK is attached to the page’s DOM.Time to explore more programmatic video generation features!&lt;p&gt;&lt;/p&gt;
&lt;h2 id=&quot;programmatic-video-generation-features&quot;&gt;Programmatic Video Generation Features&lt;/h2&gt;
&lt;p&gt;Follow the use cases below to see how CE.SDK makes automated video generation in JavaScript easier.&lt;strong&gt;Generate Video in Different Formats&lt;/strong&gt; CE.SDK supports video export in &lt;a href=&quot;https://img.ly/docs/cesdk/js/file-format-support-3c4b2a/&quot;&gt;MP4 format&lt;/a&gt; &lt;a href=&quot;https://img.ly/docs/cesdk/faq/video-support/?platform=node&quot;&gt;&lt;/a&gt;(with MP3 audio) with different resolutions and aspect ratios.To export a video with a specific aspect ratio, first adjust the width and height of the parent block to which the video block is attached:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// specify an aspect ratio of 9:16 (ideal for vertical videos)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setWidth&lt;/span&gt;&lt;span&gt;(page, &lt;/span&gt;&lt;span&gt;720&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setHeight&lt;/span&gt;&lt;span&gt;(page, &lt;/span&gt;&lt;span&gt;1280&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the video will be automatically adjusted to fit the specified dimensions. Horizontal videos will be cropped to match the defined vertical outline of the block, while vertical videos will be rendered as they are within the specified block.&lt;/p&gt;
&lt;p&gt;Next, you can export the video with the configured 9:16 aspect ratio and a given resolution with:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// define the MIME type for the video export (MP4 format)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; mimeType&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &apos;video/mp4&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// define a callback function to track the progress of video rendering and encoding&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; progressCallback&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;renderedFrames&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;encodedFrames&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;totalFrames&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &apos;Rendered&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// log the number of rendered frames&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    renderedFrames,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &apos;frames and encoded&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// log the number of encoded frames&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    encodedFrames,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &apos;frames out of&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// log the total frames to be processed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    totalFrames&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// define the video export options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; videoOptions&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  duration: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// video duration in seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  framerate: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// video framerate (frames per second)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  targetWidth: &lt;/span&gt;&lt;span&gt;480&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// target video width in pixels (for the resolution)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  targetHeight: &lt;/span&gt;&lt;span&gt;852&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// target video height in pixels (for the resolution)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// export the page as an MP4 video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; blob&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;exportVideo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  page, &lt;/span&gt;&lt;span&gt;// the page containing the design video block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  mimeType, &lt;/span&gt;&lt;span&gt;// the MIME type for the video (MP4)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  progressCallback, &lt;/span&gt;&lt;span&gt;// the callback to track video rendering progress&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  videoOptions &lt;/span&gt;&lt;span&gt;// the options for the video export&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// create an anchor element to trigger the video download&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; anchor&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;createElement&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;a&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;anchor.href &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createObjectURL&lt;/span&gt;&lt;span&gt;(blob);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;anchor.download &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;video.mp4&apos;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// output video name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;anchor.&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://img.ly/docs/cesdk/js/export-save-publish/export/overview-9ed3a8/&quot;&gt;&lt;code&gt;exportVideo()&lt;/code&gt;&lt;/a&gt; function produces a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Blob&quot;&gt;&lt;code&gt;blob video file&lt;/code&gt;&lt;/a&gt; of the specified MIME type. Note that the export occurs over multiple iterations of the update loop, with a frame being encoded in each iteration. &lt;/p&gt;
&lt;p&gt;The &lt;code&gt;targetWidth&lt;/code&gt; and &lt;code&gt;targetHeight&lt;/code&gt; options are optional. If used, the video will be resized to fit the target dimensions while maintaining its aspect ratio. This is useful for setting a specific resolution for the output video while preserving the parent block’s width and height on the output video. If omitted, the produced video will match the resolution of the width and height specified with &lt;code&gt;setWidth()&lt;/code&gt; and &lt;code&gt;setHeight()&lt;/code&gt;, respectively.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;progressCallback()&lt;/code&gt; function will track and display the video generation progress in the browser’s console:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1066px) 1066px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1066&quot; height=&quot;295&quot; src=&quot;https://img.ly/_astro/video-generation-output_2ubce0.webp&quot; srcset=&quot;/_astro/video-generation-output_Z1tfnLU.webp 640w, /_astro/video-generation-output_Z1WemnE.webp 750w, /_astro/video-generation-output_Z2n2XVo.webp 828w, /_astro/video-generation-output_2ubce0.webp 1066w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The final lines of the above code snippet trigger the download operation, mimicking the action of clicking a link to download a resource. This is a common JavaScript technique to programmatically trigger a file download.&lt;/p&gt;
&lt;p&gt;Once the export process completes, the output video file (video.mp4) will be automatically downloaded to the browser’s download directory.  Open its properties and this is what you will see:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 404px) 404px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;404&quot; height=&quot;512&quot; src=&quot;https://img.ly/_astro/js-video-generation-file_HN2N0.webp&quot; srcset=&quot;/_astro/js-video-generation-file_HN2N0.webp 404w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Note the video file’s resolution and the fact that its duration is set to 5 seconds, as defined by the &lt;code&gt;duration&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;These options enable you to easily create clips with different lengths, aspect ratios, and resolutions, optimized for various audiences and &lt;a href=&quot;https://img.ly/industries/social-media&quot;&gt;social media platforms like LinkedIn, Instagram, Facebook, X, TikTok, YouTube, and more&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;add-audio-tracks&quot;&gt;Add Audio Tracks&lt;/h3&gt;
&lt;p&gt;Adding an audio track for narration or background music to your clip is straightforward. First, create an &lt;a href=&quot;https://img.ly/docs/cesdk/js/create-video-c41a08/&quot;&gt;“audio” block&lt;/a&gt; and add your audio resource to it:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; audio&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;audio&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;appendChild&lt;/span&gt;&lt;span&gt;(page, audio);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  audio,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;audio/fileURI&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;https://cdn.img.ly/assets/demo/v1/ly.img.audio/audios/far_from_home.m4a&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, you can easily adjust the volume, and apply fade-in and fade-out effects as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// set the volume level to 80%&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setVolume&lt;/span&gt;&lt;span&gt;(audio, &lt;/span&gt;&lt;span&gt;0.8&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// start the audio after 1 second of playback&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setTimeOffset&lt;/span&gt;&lt;span&gt;(audio, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// set the audio block&apos;s duration to 5 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setDuration&lt;/span&gt;&lt;span&gt;(audio, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is breakdown of the functions used above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://img.ly/docs/cesdk/js/create-video/control-daba54/&quot;&gt;&lt;strong&gt;&lt;code&gt;setVolume()&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Adjusts the audio volume of the block, with a range from 0 (0%) to 1 (100%).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;setTimeOffset()&lt;/code&gt;&lt;/strong&gt;: Sets the time offset of the block relative to its parent. This determines when the block starts playing in the timeline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;setDuration()&lt;/code&gt;&lt;/strong&gt;: Sets the playback duration of the audio block in seconds, defining how long the block will play during the scene.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this example, the audio starts at the 1-second mark and plays for 5 seconds (from 1s to 6s), with the volume set to 80% of the original.&lt;/p&gt;
&lt;h3 id=&quot;merge-videos&quot;&gt;&lt;strong&gt;Merge Videos&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;To stitch multiple video clips together in CE.SDK, you first need to import and create separate video blocks:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// create the first video block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; video1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;graphic&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setShape&lt;/span&gt;&lt;span&gt;(video1, engine.block.&lt;/span&gt;&lt;span&gt;createShape&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;rect&apos;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; videoFill1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;createFill&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;video&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  videoFill1,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;fill/video/fileURI&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;https://cdn.img.ly/assets/demo/v2/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setFill&lt;/span&gt;&lt;span&gt;(video1, videoFill1);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// create the second video block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; video2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;graphic&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setShape&lt;/span&gt;&lt;span&gt;(video2, engine.block.&lt;/span&gt;&lt;span&gt;createShape&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;rect&apos;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; videoFill2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; engine.block.&lt;/span&gt;&lt;span&gt;createFill&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;video&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  videoFill2,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;fill/video/fileURI&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &apos;https://cdn.img.ly/assets/demo/v2/ly.img.video/videos/pexels-kampus-production-8154913.mp4&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setFill&lt;/span&gt;&lt;span&gt;(video2, videoFill2);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, you have two different video clips loaded from separate video files.&lt;/p&gt;
&lt;p&gt;Now, assume you want to produce a 10-second video that includes both clips. Start by setting the duration of the page block to 10 seconds with the &lt;a href=&quot;https://img.ly/docs/cesdk/js/create-video/control-daba54/&quot;&gt;&lt;code&gt;setDuration()&lt;/code&gt;&lt;/a&gt; method:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.&lt;/span&gt;&lt;span&gt;setDuration&lt;/span&gt;&lt;span&gt;(page, &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, suppose you want the first clip (video1) to play for 7 seconds, and the second clip (video2) to fade in after that and play for 3 seconds (from second 1 to second 4 of the original second clip). This is how you can set that up:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// set the duration of the first and second video clips&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.setDuration(video1, 7); // video1 plays for 7 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.setDuration(video2, 3); // video2 plays for 3 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// wait for the second video to be fully loaded before applying the trim options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await engine.block.forceLoadAVResource(videoFill2);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// trim the second video to start at 1 second and play up to second 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.setTrimOffset(videoFill2, 1);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.setTrimLength(videoFill2, 4);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// add a fade-in animation to the second video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const fadeInAnimation = engine.block.createAnimation(&quot;fade&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.setInAnimation(video2, fadeInAnimation);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are the methods used above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;setDuration()&lt;/code&gt;&lt;/strong&gt;: Defines the duration (in seconds) that a video block will be active during playback.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;**forceLoadAVResource()**&lt;/code&gt;: Ensures that the audio or video resource (in this case, &lt;code&gt;video2&lt;/code&gt;) is fully loaded before applying trim operations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;setTrimOffset()&lt;/code&gt;&lt;/strong&gt;: Sets the start point (in seconds) within the video where playback begins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;setTrimLength()&lt;/code&gt;&lt;/strong&gt;: Defines the length of the video to be played from the trim offset.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://img.ly/docs/cesdk/js/animation/overview-6a2ef2/&quot;&gt;&lt;strong&gt;&lt;code&gt;createAnimation()&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Creates a fade-in animation for the second video (&lt;code&gt;video2&lt;/code&gt;) to smoothly transition into the clip.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, add both video blocks to the track block for playback:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const track = engine.block.create(&quot;track&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.appendChild(page, track);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.appendChild(track, video1); // add the first video to the track&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.appendChild(track, video2); // add the second video to the track&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.fillParent(track);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result will be:&lt;/p&gt;
&lt;figure class=&quot;kg-card kg-embed-card&quot;&gt;&lt;iframe width=&quot;225&quot; height=&quot;400&quot; src=&quot;https://blog.img.ly/2025/01/video-1.mp4&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;&lt;/figure&gt;
&lt;p&gt;Note how the second clip fades in after 7 seconds of the first clip, lasting for 3 seconds as specified.&lt;/p&gt;
&lt;h3 id=&quot;incorporate-text-variables&quot;&gt;&lt;a href=&quot;https://img.ly/blog/how-to-video-generation-with-javascript//#Incorporate-Text-Variables&quot;&gt;&lt;/a&gt;Incorporate Text Variables&lt;/h3&gt;
&lt;p&gt;Now, suppose you want to display cool words or messages in your videos. You can easily achieve this by adding a &lt;a href=&quot;https://img.ly/docs/cesdk/js/text/edit-c5106b/&quot;&gt;&lt;code&gt;&quot;text&quot;&lt;/code&gt; block&lt;/a&gt; to your page, as shown below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;const text = engine.block.create(&quot;text&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// set the vluae of the text variable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.replaceText(text, &quot;Surfing\nis\nCOOL!&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// set the text color to white and semi-transparent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.setTextColor(text, { r: 255.0, g: 255.0, b: 255.0, a: 0.8 });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// set the font size for the text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.setTextFontSize(text, 30);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// positioning the text on an absolute position on the video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.setWidthMode(text, &quot;Auto&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.setHeightMode(text, &quot;Auto&quot;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.setPositionX(text, 130);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.setPositionY(text, 800);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;// append the text block to the page&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;engine.block.appendChild(page, text);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The video canvas will now display a white “Surfing is COOL!” message as follows:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 514px) 514px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;514&quot; height=&quot;812&quot; src=&quot;https://img.ly/_astro/js-video-generation-surfing_ro7yG.webp&quot; srcset=&quot;/_astro/js-video-generation-surfing_ro7yG.webp 514w&quot;&gt;&lt;/p&gt;
&lt;p&gt;In this example, the content of the text block is static. However, it can easily be retrieved from a database or any other source for programmatic video generation with different messages, including personalized ones for tailored outreach (e.g., for custom greetings or offers).&lt;/p&gt;
&lt;h2 id=&quot;advanced-use-case-prompt-based-video-generation-using-ai&quot;&gt;&lt;a href=&quot;https://img.ly/blog/how-to-video-generation-with-javascript//#Advanced-Use-Case-Prompt-Based-Video-Generation-Using-AI&quot;&gt;&lt;/a&gt;Advanced Use Case: Prompt-Based Video Generation Using AI&lt;/h2&gt;
&lt;p&gt;As highlighted in our &lt;a href=&quot;https://www.linkedin.com/posts/eray-basar-57684711_imagine-building-a-full-video-app-in-a-single-activity-7262806525797089280-S_L8?utm_source=share&amp;#x26;utm_medium=member_desktop&quot;&gt;recent LinkedIn post&lt;/a&gt;, &lt;a href=&quot;https://IMG.LY&quot;&gt;IMG.LY&lt;/a&gt; has just experimented with an MVP that turns keywords into fully edited short videos, complete with a script, voiceover, and visuals—all automated.&lt;/p&gt;
&lt;p&gt;That is made possible by integrating CE.SDK with an LLM for coding and scriptwriting, &lt;a href=&quot;https://elevenlabs.io/&quot;&gt;ElevenLabs&lt;/a&gt; for voice synthesis, and Flux (via &lt;a href=&quot;https://fal.ai/&quot;&gt;fal.ai&lt;/a&gt;) for visuals. Specifically, the LLM uses CE.SDK for video composition, animation, and rendering.&lt;/p&gt;
&lt;p&gt;This is just the beginning, showing how AI can be combined with CE.SDK to programmatically generate video scripts—or even templates. A possible scenario would be to use the &lt;a href=&quot;https://img.ly/docs/cesdk/js/user-interface/ui-extensions-d194d1/&quot;&gt;Creative Editor SDK plugin API&lt;/a&gt; to extend CE.SDK with custom plugins that adds AI directly to the design editor engine.&lt;/p&gt;
&lt;p&gt;For example, you could develop a plugin with an LLM integration that allows users to input a prompt like: &lt;em&gt;“Create a 15-second video with the text ‘Welcome to our App!’ and upbeat background music.”&lt;/em&gt; The LLM will process the request, using Creative Editor SDK to automate video creation by leveraging its powerful features.&lt;/p&gt;
&lt;p&gt;Alternatively, the AI could produce a video that you can then automatically edit and enhance using CE.SDK capabilities. One thing is certain: the possibilities are endless with CE.SDK + AI!&lt;/p&gt;
&lt;h2 id=&quot;conclusion-the-future-of-programmatic-video-generation&quot;&gt;&lt;a href=&quot;https://img.ly/blog/how-to-video-generation-with-javascript//#Conclusion-The-Future-of-Programmatic-Video-Generation&quot;&gt;&lt;/a&gt;Conclusion: The Future of Programmatic Video Generation&lt;/h2&gt;
&lt;p&gt;CE.SDK for JavaScript offers exceptional versatility in automating video workflows, allowing you to easily generate, edit, and render videos with just a few lines of code. You can stitch multiple video files, add background audio tracks, and incorporate dynamic text variables to finally produce videos in the browser—all thanks to the CE.SDK headless engine.&lt;/p&gt;
&lt;p&gt;Looking ahead, AI integration with CE.SDK is unlocking even more powerful possibilities, such as prompt-based video content creation and automatic editing directly in the browser.&lt;/p&gt;
&lt;p&gt;With the headless engine provided by CreativeEditor SDK, programmatic video generation in JavaScript can be implemented in just a few minutes. Explore the video capabilities of CE.SDK and &lt;a href=&quot;https://img.ly/docs/cesdk/js/get-started/overview-e18f40/&quot;&gt;dive into the docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Stay tuned for more updates, and please &lt;a href=&quot;https://img.ly/forms/contact-sales?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=plugins1&quot;&gt;reach out&lt;/a&gt; if you have any questions. Thank you for reading.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Over 3,000 creative professionals gain early access to our new features, demos, and updates—don’t miss out, and&lt;/strong&gt; &lt;a href=&quot;https://share.hsforms.com/1IgAOV1wASXGPnFG4ZPLejg1hk3i?ref=img.ly&quot;&gt;&lt;strong&gt;subscribe&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;to our newsletter.&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;</content:encoded><dc:creator>Antonello</dc:creator><dc:creator>Jan</dc:creator><media:content url="https://blog.img.ly/2025/01/javascript-generate-video.jpg" medium="image"/><category>Video Editing</category><category>Creative Automation</category><category>CE.SDK</category></item></channel></rss>