[{"data":1,"prerenderedAt":1009},["ShallowReactive",2],{"\u002Fdocs\u002Fbatch-animate-videos":3},{"id":4,"title":5,"body":6,"category":999,"date":1000,"description":1001,"extension":1002,"heroImage":1003,"meta":1004,"navigation":129,"path":1005,"readingMinutes":120,"seo":1006,"stem":1007,"tool":999,"__hash__":1008},"docs\u002Fdocs\u002Fbatch-animate-videos.md","Batch animate videos",{"type":7,"value":8,"toc":992},"minimark",[9,14,18,26,30,33,691,695,698,925,932,936,968,972,988],[10,11,13],"h2",{"id":12},"one-template-many-videos","One template, many videos",[15,16,17],"p",{},"Personalized video is normally a per-clip cost: an editor or an agency makes each one. With Bluepic it is a loop. You animate a template once, then send one render request per row of data. Each row produces its own MP4: one per recipient, product, city, or language.",[15,19,20,21,25],{},"The template does not change between rows. Only the ",[22,23,24],"code",{},"data"," does. The clip is a pure function of the values you send.",[10,27,29],{"id":28},"the-pattern","The pattern",[15,31,32],{},"Read your rows (from a CSV, a database, a CRM export), and render one video per row. Here it is over the demo template, which needs no API key:",[34,35,40],"pre",{"className":36,"code":37,"language":38,"meta":39,"style":39},"language-js shiki shiki-themes github-dark","const rows = [\n  { text1_text: \"ALEX RIVERA\", image4_image: { src: \"https:\u002F\u002F…\u002Falex.jpg\" } },\n  { text1_text: \"SAM CHEN\", image4_image: { src: \"https:\u002F\u002F…\u002Fsam.jpg\" } },\n  { text1_text: \"MIA LOPEZ\", image4_image: { src: \"https:\u002F\u002F…\u002Fmia.jpg\" } },\n  \u002F\u002F …hundreds more\n];\n\nasync function renderOne(data) {\n  const res = await fetch(\"https:\u002F\u002Fapi.bluepic.io\u002Fapi\u002Frender\", {\n    method: \"POST\",\n    headers: { Authorization: \"\u003Cyour API key>\", \"Content-Type\": \"application\u002Fjson\" },\n    body: JSON.stringify({\n      templateId: \"7ccf097c-10f2-4efc-bd03-8abe967c4711\",\n      data,\n      format: \"mp4\",\n    }),\n  });\n  \u002F\u002F The video endpoint streams; read to the `end` event for the hosted URL.\n  const reader = res.body.getReader();\n  const decoder = new TextDecoder();\n  let buffer = \"\";\n  for (;;) {\n    const { value, done } = await reader.read();\n    if (value) buffer += decoder.decode(value, { stream: true });\n    let i;\n    while ((i = buffer.indexOf(\"\\n\\n\")) !== -1) {\n      const line = buffer.slice(0, i).split(\"\\n\").find((l) => l.startsWith(\"data:\"));\n      buffer = buffer.slice(i + 2);\n      if (line) {\n        const evt = JSON.parse(line.slice(5).trim());\n        if (evt.type === \"end\") return evt.result;\n        if (evt.type === \"error\") throw new Error(evt.message);\n      }\n    }\n    if (done) break;\n  }\n  throw new Error(\"no result\");\n}\n","js","",[22,41,42,62,81,96,111,118,124,131,153,178,190,214,232,243,249,260,266,272,278,297,315,333,342,374,401,410,451,519,543,552,589,612,637,643,649,662,668,685],{"__ignoreMap":39},[43,44,47,51,55,58],"span",{"class":45,"line":46},"line",1,[43,48,50],{"class":49},"snl16","const",[43,52,54],{"class":53},"sDLfK"," rows",[43,56,57],{"class":49}," =",[43,59,61],{"class":60},"s95oV"," [\n",[43,63,65,68,72,75,78],{"class":45,"line":64},2,[43,66,67],{"class":60},"  { text1_text: ",[43,69,71],{"class":70},"sU2Wk","\"ALEX RIVERA\"",[43,73,74],{"class":60},", image4_image: { src: ",[43,76,77],{"class":70},"\"https:\u002F\u002F…\u002Falex.jpg\"",[43,79,80],{"class":60}," } },\n",[43,82,84,86,89,91,94],{"class":45,"line":83},3,[43,85,67],{"class":60},[43,87,88],{"class":70},"\"SAM CHEN\"",[43,90,74],{"class":60},[43,92,93],{"class":70},"\"https:\u002F\u002F…\u002Fsam.jpg\"",[43,95,80],{"class":60},[43,97,99,101,104,106,109],{"class":45,"line":98},4,[43,100,67],{"class":60},[43,102,103],{"class":70},"\"MIA LOPEZ\"",[43,105,74],{"class":60},[43,107,108],{"class":70},"\"https:\u002F\u002F…\u002Fmia.jpg\"",[43,110,80],{"class":60},[43,112,114],{"class":45,"line":113},5,[43,115,117],{"class":116},"sAwPA","  \u002F\u002F …hundreds more\n",[43,119,121],{"class":45,"line":120},6,[43,122,123],{"class":60},"];\n",[43,125,127],{"class":45,"line":126},7,[43,128,130],{"emptyLinePlaceholder":129},true,"\n",[43,132,134,137,140,144,147,150],{"class":45,"line":133},8,[43,135,136],{"class":49},"async",[43,138,139],{"class":49}," function",[43,141,143],{"class":142},"svObZ"," renderOne",[43,145,146],{"class":60},"(",[43,148,24],{"class":149},"s9osk",[43,151,152],{"class":60},") {\n",[43,154,156,159,162,164,167,170,172,175],{"class":45,"line":155},9,[43,157,158],{"class":49},"  const",[43,160,161],{"class":53}," res",[43,163,57],{"class":49},[43,165,166],{"class":49}," await",[43,168,169],{"class":142}," fetch",[43,171,146],{"class":60},[43,173,174],{"class":70},"\"https:\u002F\u002Fapi.bluepic.io\u002Fapi\u002Frender\"",[43,176,177],{"class":60},", {\n",[43,179,181,184,187],{"class":45,"line":180},10,[43,182,183],{"class":60},"    method: ",[43,185,186],{"class":70},"\"POST\"",[43,188,189],{"class":60},",\n",[43,191,193,196,199,202,205,208,211],{"class":45,"line":192},11,[43,194,195],{"class":60},"    headers: { Authorization: ",[43,197,198],{"class":70},"\"\u003Cyour API key>\"",[43,200,201],{"class":60},", ",[43,203,204],{"class":70},"\"Content-Type\"",[43,206,207],{"class":60},": ",[43,209,210],{"class":70},"\"application\u002Fjson\"",[43,212,213],{"class":60}," },\n",[43,215,217,220,223,226,229],{"class":45,"line":216},12,[43,218,219],{"class":60},"    body: ",[43,221,222],{"class":53},"JSON",[43,224,225],{"class":60},".",[43,227,228],{"class":142},"stringify",[43,230,231],{"class":60},"({\n",[43,233,235,238,241],{"class":45,"line":234},13,[43,236,237],{"class":60},"      templateId: ",[43,239,240],{"class":70},"\"7ccf097c-10f2-4efc-bd03-8abe967c4711\"",[43,242,189],{"class":60},[43,244,246],{"class":45,"line":245},14,[43,247,248],{"class":60},"      data,\n",[43,250,252,255,258],{"class":45,"line":251},15,[43,253,254],{"class":60},"      format: ",[43,256,257],{"class":70},"\"mp4\"",[43,259,189],{"class":60},[43,261,263],{"class":45,"line":262},16,[43,264,265],{"class":60},"    }),\n",[43,267,269],{"class":45,"line":268},17,[43,270,271],{"class":60},"  });\n",[43,273,275],{"class":45,"line":274},18,[43,276,277],{"class":116},"  \u002F\u002F The video endpoint streams; read to the `end` event for the hosted URL.\n",[43,279,281,283,286,288,291,294],{"class":45,"line":280},19,[43,282,158],{"class":49},[43,284,285],{"class":53}," reader",[43,287,57],{"class":49},[43,289,290],{"class":60}," res.body.",[43,292,293],{"class":142},"getReader",[43,295,296],{"class":60},"();\n",[43,298,300,302,305,307,310,313],{"class":45,"line":299},20,[43,301,158],{"class":49},[43,303,304],{"class":53}," decoder",[43,306,57],{"class":49},[43,308,309],{"class":49}," new",[43,311,312],{"class":142}," TextDecoder",[43,314,296],{"class":60},[43,316,318,321,324,327,330],{"class":45,"line":317},21,[43,319,320],{"class":49},"  let",[43,322,323],{"class":60}," buffer ",[43,325,326],{"class":49},"=",[43,328,329],{"class":70}," \"\"",[43,331,332],{"class":60},";\n",[43,334,336,339],{"class":45,"line":335},22,[43,337,338],{"class":49},"  for",[43,340,341],{"class":60}," (;;) {\n",[43,343,345,348,351,354,356,359,362,364,366,369,372],{"class":45,"line":344},23,[43,346,347],{"class":49},"    const",[43,349,350],{"class":60}," { ",[43,352,353],{"class":53},"value",[43,355,201],{"class":60},[43,357,358],{"class":53},"done",[43,360,361],{"class":60}," } ",[43,363,326],{"class":49},[43,365,166],{"class":49},[43,367,368],{"class":60}," reader.",[43,370,371],{"class":142},"read",[43,373,296],{"class":60},[43,375,377,380,383,386,389,392,395,398],{"class":45,"line":376},24,[43,378,379],{"class":49},"    if",[43,381,382],{"class":60}," (value) buffer ",[43,384,385],{"class":49},"+=",[43,387,388],{"class":60}," decoder.",[43,390,391],{"class":142},"decode",[43,393,394],{"class":60},"(value, { stream: ",[43,396,397],{"class":53},"true",[43,399,400],{"class":60}," });\n",[43,402,404,407],{"class":45,"line":403},25,[43,405,406],{"class":49},"    let",[43,408,409],{"class":60}," i;\n",[43,411,413,416,419,421,424,427,429,432,435,437,440,443,446,449],{"class":45,"line":412},26,[43,414,415],{"class":49},"    while",[43,417,418],{"class":60}," ((i ",[43,420,326],{"class":49},[43,422,423],{"class":60}," buffer.",[43,425,426],{"class":142},"indexOf",[43,428,146],{"class":60},[43,430,431],{"class":70},"\"",[43,433,434],{"class":53},"\\n\\n",[43,436,431],{"class":70},[43,438,439],{"class":60},")) ",[43,441,442],{"class":49},"!==",[43,444,445],{"class":49}," -",[43,447,448],{"class":53},"1",[43,450,152],{"class":60},[43,452,454,457,460,462,464,467,469,472,475,478,480,482,485,487,490,493,496,499,502,505,508,511,513,516],{"class":45,"line":453},27,[43,455,456],{"class":49},"      const",[43,458,459],{"class":53}," line",[43,461,57],{"class":49},[43,463,423],{"class":60},[43,465,466],{"class":142},"slice",[43,468,146],{"class":60},[43,470,471],{"class":53},"0",[43,473,474],{"class":60},", i).",[43,476,477],{"class":142},"split",[43,479,146],{"class":60},[43,481,431],{"class":70},[43,483,484],{"class":53},"\\n",[43,486,431],{"class":70},[43,488,489],{"class":60},").",[43,491,492],{"class":142},"find",[43,494,495],{"class":60},"((",[43,497,498],{"class":149},"l",[43,500,501],{"class":60},") ",[43,503,504],{"class":49},"=>",[43,506,507],{"class":60}," l.",[43,509,510],{"class":142},"startsWith",[43,512,146],{"class":60},[43,514,515],{"class":70},"\"data:\"",[43,517,518],{"class":60},"));\n",[43,520,522,525,527,529,531,534,537,540],{"class":45,"line":521},28,[43,523,524],{"class":60},"      buffer ",[43,526,326],{"class":49},[43,528,423],{"class":60},[43,530,466],{"class":142},[43,532,533],{"class":60},"(i ",[43,535,536],{"class":49},"+",[43,538,539],{"class":53}," 2",[43,541,542],{"class":60},");\n",[43,544,546,549],{"class":45,"line":545},29,[43,547,548],{"class":49},"      if",[43,550,551],{"class":60}," (line) {\n",[43,553,555,558,561,563,566,568,571,574,576,578,581,583,586],{"class":45,"line":554},30,[43,556,557],{"class":49},"        const",[43,559,560],{"class":53}," evt",[43,562,57],{"class":49},[43,564,565],{"class":53}," JSON",[43,567,225],{"class":60},[43,569,570],{"class":142},"parse",[43,572,573],{"class":60},"(line.",[43,575,466],{"class":142},[43,577,146],{"class":60},[43,579,580],{"class":53},"5",[43,582,489],{"class":60},[43,584,585],{"class":142},"trim",[43,587,588],{"class":60},"());\n",[43,590,592,595,598,601,604,606,609],{"class":45,"line":591},31,[43,593,594],{"class":49},"        if",[43,596,597],{"class":60}," (evt.type ",[43,599,600],{"class":49},"===",[43,602,603],{"class":70}," \"end\"",[43,605,501],{"class":60},[43,607,608],{"class":49},"return",[43,610,611],{"class":60}," evt.result;\n",[43,613,615,617,619,621,624,626,629,631,634],{"class":45,"line":614},32,[43,616,594],{"class":49},[43,618,597],{"class":60},[43,620,600],{"class":49},[43,622,623],{"class":70}," \"error\"",[43,625,501],{"class":60},[43,627,628],{"class":49},"throw",[43,630,309],{"class":49},[43,632,633],{"class":142}," Error",[43,635,636],{"class":60},"(evt.message);\n",[43,638,640],{"class":45,"line":639},33,[43,641,642],{"class":60},"      }\n",[43,644,646],{"class":45,"line":645},34,[43,647,648],{"class":60},"    }\n",[43,650,652,654,657,660],{"class":45,"line":651},35,[43,653,379],{"class":49},[43,655,656],{"class":60}," (done) ",[43,658,659],{"class":49},"break",[43,661,332],{"class":60},[43,663,665],{"class":45,"line":664},36,[43,666,667],{"class":60},"  }\n",[43,669,671,674,676,678,680,683],{"class":45,"line":670},37,[43,672,673],{"class":49},"  throw",[43,675,309],{"class":49},[43,677,633],{"class":142},[43,679,146],{"class":60},[43,681,682],{"class":70},"\"no result\"",[43,684,542],{"class":60},[43,686,688],{"class":45,"line":687},38,[43,689,690],{"class":60},"}\n",[10,692,694],{"id":693},"run-the-batch-with-a-concurrency-limit","Run the batch with a concurrency limit",[15,696,697],{},"Do not fire all rows at once. Video renders are heavier than stills, so cap how many run in parallel and let the rest queue. A small worker pool is enough:",[34,699,701],{"className":36,"code":700,"language":38,"meta":39,"style":39},"async function renderBatch(rows, concurrency = 4) {\n  const results = [];\n  let next = 0;\n  async function worker() {\n    while (next \u003C rows.length) {\n      const i = next++;\n      try {\n        results[i] = await renderOne(rows[i]);\n      } catch (err) {\n        results[i] = { error: String(err) };\n      }\n    }\n  }\n  await Promise.all(Array.from({ length: concurrency }, worker));\n  return results; \u002F\u002F one hosted MP4 URL (or an error) per input row\n}\n\nconst urls = await renderBatch(rows, 4);\n",[22,702,703,729,741,755,768,786,803,811,825,836,851,855,859,863,885,896,900,904],{"__ignoreMap":39},[43,704,705,707,709,712,714,717,719,722,724,727],{"class":45,"line":46},[43,706,136],{"class":49},[43,708,139],{"class":49},[43,710,711],{"class":142}," renderBatch",[43,713,146],{"class":60},[43,715,716],{"class":149},"rows",[43,718,201],{"class":60},[43,720,721],{"class":149},"concurrency",[43,723,57],{"class":49},[43,725,726],{"class":53}," 4",[43,728,152],{"class":60},[43,730,731,733,736,738],{"class":45,"line":64},[43,732,158],{"class":49},[43,734,735],{"class":53}," results",[43,737,57],{"class":49},[43,739,740],{"class":60}," [];\n",[43,742,743,745,748,750,753],{"class":45,"line":83},[43,744,320],{"class":49},[43,746,747],{"class":60}," next ",[43,749,326],{"class":49},[43,751,752],{"class":53}," 0",[43,754,332],{"class":60},[43,756,757,760,762,765],{"class":45,"line":98},[43,758,759],{"class":49},"  async",[43,761,139],{"class":49},[43,763,764],{"class":142}," worker",[43,766,767],{"class":60},"() {\n",[43,769,770,772,775,778,781,784],{"class":45,"line":113},[43,771,415],{"class":49},[43,773,774],{"class":60}," (next ",[43,776,777],{"class":49},"\u003C",[43,779,780],{"class":60}," rows.",[43,782,783],{"class":53},"length",[43,785,152],{"class":60},[43,787,788,790,793,795,798,801],{"class":45,"line":120},[43,789,456],{"class":49},[43,791,792],{"class":53}," i",[43,794,57],{"class":49},[43,796,797],{"class":60}," next",[43,799,800],{"class":49},"++",[43,802,332],{"class":60},[43,804,805,808],{"class":45,"line":126},[43,806,807],{"class":49},"      try",[43,809,810],{"class":60}," {\n",[43,812,813,816,818,820,822],{"class":45,"line":133},[43,814,815],{"class":60},"        results[i] ",[43,817,326],{"class":49},[43,819,166],{"class":49},[43,821,143],{"class":142},[43,823,824],{"class":60},"(rows[i]);\n",[43,826,827,830,833],{"class":45,"line":155},[43,828,829],{"class":60},"      } ",[43,831,832],{"class":49},"catch",[43,834,835],{"class":60}," (err) {\n",[43,837,838,840,842,845,848],{"class":45,"line":180},[43,839,815],{"class":60},[43,841,326],{"class":49},[43,843,844],{"class":60}," { error: ",[43,846,847],{"class":142},"String",[43,849,850],{"class":60},"(err) };\n",[43,852,853],{"class":45,"line":192},[43,854,642],{"class":60},[43,856,857],{"class":45,"line":216},[43,858,648],{"class":60},[43,860,861],{"class":45,"line":234},[43,862,667],{"class":60},[43,864,865,868,871,873,876,879,882],{"class":45,"line":245},[43,866,867],{"class":49},"  await",[43,869,870],{"class":53}," Promise",[43,872,225],{"class":60},[43,874,875],{"class":142},"all",[43,877,878],{"class":60},"(Array.",[43,880,881],{"class":142},"from",[43,883,884],{"class":60},"({ length: concurrency }, worker));\n",[43,886,887,890,893],{"class":45,"line":251},[43,888,889],{"class":49},"  return",[43,891,892],{"class":60}," results; ",[43,894,895],{"class":116},"\u002F\u002F one hosted MP4 URL (or an error) per input row\n",[43,897,898],{"class":45,"line":262},[43,899,690],{"class":60},[43,901,902],{"class":45,"line":268},[43,903,130],{"emptyLinePlaceholder":129},[43,905,906,908,911,913,915,917,920,923],{"class":45,"line":274},[43,907,50],{"class":49},[43,909,910],{"class":53}," urls",[43,912,57],{"class":49},[43,914,166],{"class":49},[43,916,711],{"class":142},[43,918,919],{"class":60},"(rows, ",[43,921,922],{"class":53},"4",[43,924,542],{"class":60},[15,926,927,928,931],{},"Each entry in ",[22,929,930],{},"urls"," lines up with a row in your data. Store them, attach them to your emails, post them, or hand them to whatever pipeline triggered the batch.",[10,933,935],{"id":934},"what-to-keep-in-mind","What to keep in mind",[937,938,939,953,959],"ul",{},[940,941,942,946,947,952],"li",{},[943,944,945],"strong",{},"Credits."," Each video is metered by length and resolution (1 credit per second of 2K at 24fps), so a batch's cost is the sum of its clips. See ",[948,949,951],"a",{"href":950},"\u002Fdocs\u002Fvideo-credits","how video credits work"," to estimate before a large job.",[940,954,955,958],{},[943,956,957],{},"Aspect ratios."," Need square for a feed and vertical for stories? Build one template per ratio and run the same batch against each; the data stays identical.",[940,960,961,964,965,967],{},[943,962,963],{},"Idempotency."," The same ",[22,966,24],{}," always produces the same video (rendering is deterministic), so you can safely cache results by input and skip re-rendering rows that have not changed.",[10,969,971],{"id":970},"next","Next",[937,973,974,981],{},[940,975,976,977,225],{},"The single-request version: ",[948,978,980],{"href":979},"\u002Fdocs\u002Fgenerate-video-via-api","generate video via API",[940,982,983,984,225],{},"Build the template you are batching: ",[948,985,987],{"href":986},"\u002Fdocs\u002Fhow-to-video-templates","how to build a video template in Studio",[989,990,991],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":39,"searchDepth":64,"depth":64,"links":993},[994,995,996,997,998],{"id":12,"depth":64,"text":13},{"id":28,"depth":64,"text":29},{"id":693,"depth":64,"text":694},{"id":934,"depth":64,"text":935},{"id":970,"depth":64,"text":971},"platform","2026-07-26","Generate hundreds of personalized videos from one template. Loop over your data, one render request per row, and collect a hosted MP4 for each. Personalized video at scale, from a spreadsheet or a database.","md","\u002Fdocs-images\u002Fbatch-animate-videos.svg",{},"\u002Fdocs\u002Fbatch-animate-videos",{"title":5,"description":1001},"docs\u002Fbatch-animate-videos","YNpHev1Meom1K_hl_HqlvPILWjPHIQGbDZU9BLvxQ5g",1785414150289]