<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Louis Chi</title>
    <description>The latest articles on DEV Community by Louis Chi (@ref_vid).</description>
    <link>https://dev.to/ref_vid</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4201088%2F97943aaf-ee72-4b2d-8179-71b58a101a43.png</url>
      <title>DEV Community: Louis Chi</title>
      <link>https://dev.to/ref_vid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ref_vid"/>
    <language>en</language>
    <item>
      <title>How to take product photos at home that AI video models can actually use</title>
      <dc:creator>Louis Chi</dc:creator>
      <pubDate>Sun, 25 Oct 2026 11:58:43 +0000</pubDate>
      <link>https://dev.to/ref_vid/how-to-take-product-photos-at-home-that-ai-video-models-can-actually-use-gj8</link>
      <guid>https://dev.to/ref_vid/how-to-take-product-photos-at-home-that-ai-video-models-can-actually-use-gj8</guid>
      <description>&lt;p&gt;Most "the AI ruined my product" complaints are photo problems. The logo melts, the bottle turns from teal to green, the shoe grows an extra stripe. Usually the input is one busy lifestyle shot, or five photos taken in five different lights.&lt;/p&gt;

&lt;p&gt;I build &lt;a href="https://refvid.com/how-to-shoot-product-reference-photos?utm_source=devto" rel="noopener noreferrer"&gt;RefVid&lt;/a&gt;, which turns product photos into video with reference-to-video models such as Kling Omni. We shoot many test sets for invented products (a serum called LUMEN, a shoe called AERO 1). This is what works with a phone and a kitchen table.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a reference-to-video model does with your photos
&lt;/h2&gt;

&lt;p&gt;Image-to-video animates one picture; once the camera moves past it, the model invents the rest, and products drift. Reference-to-video treats your photos as a description of the object and builds every frame of a new scene around them. The model can only keep what it can see, so it needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3 to 5 photos&lt;/strong&gt; of the same object, not 1 and not 20.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different angles&lt;/strong&gt; of it: front, three-quarter, side, back, a detail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same light, background and color in every photo.&lt;/strong&gt; The model averages across the set. If the photos disagree, you get the average.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point causes most of the failures, so the setup below is mostly about consistency, not beauty.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: window, sweep, phone
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Light.&lt;/strong&gt; A big window without direct sun is the best free light you have. Put the product about a meter from it and stand a sheet of white card on the other side to lift the shadows. If you shoot at night, use two identical lamps at 45 degrees on either side. Never mix a warm bulb with daylight: the color shifts between angles, and the model learns a color your product doesn't have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backdrop.&lt;/strong&gt; Curve a sheet of white or light gray paper (or bent foam board) from the table up to the wall so there is no horizon line. Set the product well forward on the flat part so its shadow doesn't climb the wall. Use mid-gray for white products so their edges don't vanish. No wood grain, no patterned cloth, nothing with text. A background that shows up in every reference gets treated as part of the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Light box.&lt;/strong&gt; For small things (jewelry, cosmetics, earbuds), a light box gives soft, almost shadowless light from every side. You can make one: cut windows into three sides of a cardboard box, tape baking paper over them, line the inside with a curved sheet of white paper, and put one lamp on each side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phone settings.&lt;/strong&gt; These matter more than the phone model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the main lens at 1x or 2x. The ultra-wide bends straight edges, and the model will faithfully reproduce a bent bottle.&lt;/li&gt;
&lt;li&gt;Turn off portrait mode, beauty filters and any color "look".&lt;/li&gt;
&lt;li&gt;On iPhone, tap and hold the product until AE/AF Lock appears, then drag the exposure slider down a little so white labels keep detail. The stock camera can't lock white balance, which is one more reason to keep a single light type.&lt;/li&gt;
&lt;li&gt;On Android, Pro mode lets you set white balance to a fixed value (around 5000 to 5500 K for daylight) and keep ISO low. Fix it once and leave it for the whole set.&lt;/li&gt;
&lt;li&gt;Rest the phone on books or a tripod, use the 3-second timer, and wipe the lens.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The five angles (plus one)
&lt;/h2&gt;

&lt;p&gt;Shoot these in order, without moving the lights:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Front&lt;/strong&gt;, straight on at the product's eye level.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;45-degree three-quarter view.&lt;/strong&gt; Usually your best hero image, and the one we upload first, because the first image is treated as the main view.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Side.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Back&lt;/strong&gt;, including the label panel if there is one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detail close-up&lt;/strong&gt; of the logo or main marking, filling most of the frame and perfectly sharp.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The plus one: &lt;strong&gt;an in-use shot&lt;/strong&gt; if the product is held or worn. A hand around the bottle, the watch on a wrist. Product-only photos don't show scale, and without one you can get a perfume bottle the size of a wine bottle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fii3y9matet9bv5xlwezx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fii3y9matet9bv5xlwezx.jpg" alt="A reference photo of an invented serum bottle on a plain backdrop" width="240" height="240"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;One of the three plain-backdrop photos of LUMEN, a test serum. Same light, same paper, three angles.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then pick 3 to 5 of these. Front, three-quarter and logo close-up is the minimum set we'd trust; add the back and the in-use shot if you have them. The full shot list, including top-down views and turntable clips, is in our &lt;a href="https://refvid.com/how-to-shoot-product-reference-photos" rel="noopener noreferrer"&gt;guide to taking product photos at home&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jewelry and other reflective things
&lt;/h2&gt;

&lt;p&gt;Shiny surfaces mirror the room, and whatever is mirrored ends up in the video, in every scene. So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Turn off the ceiling light and use one large soft source. Stand white card around the piece so it reflects plain white, and shoot from slightly above so it reflects the card, not you.&lt;/li&gt;
&lt;li&gt;Shoot jewelry on mid-gray paper, or black for silver and diamonds. Wipe off fingerprints and dust first; a macro lens makes them huge.&lt;/li&gt;
&lt;li&gt;Stand rings upright with a small ball of putty hidden behind the band.&lt;/li&gt;
&lt;li&gt;Get as close as the phone still focuses (newer iPhone Pros switch to macro on their own) instead of using digital zoom.&lt;/li&gt;
&lt;li&gt;Clear glass disappears on white. Put a strip of black card just outside the frame on each side to draw dark edges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have a clean set, a slow turn under moving light is where jewelry looks best; our &lt;a href="https://refvid.com/jewelry-sparkle" rel="noopener noreferrer"&gt;jewelry sparkle template&lt;/a&gt; is that shot on black velvet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common failures, and the photo that fixes them
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The logo warps or the text changes.&lt;/strong&gt; Small printed text is the weakest point of every video model we've tested. You can't make it perfect, but you can stop making it worse: include one close-up where the logo fills the frame and is sharp at 100 percent zoom, and plan scenes where the logo is large and faces the camera. Fine print like ingredient lists will be redrawn whatever you do, so keep it out of focus or turned away. More on this in &lt;a href="https://refvid.com/how-to-keep-logos-and-packaging-accurate" rel="noopener noreferrer"&gt;how to keep logos and packaging accurate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The color drifts between clips.&lt;/strong&gt; Almost always mixed light or mixed sessions: two photos from Tuesday's window, two from Friday's lamp. Reshoot all of them in one session, in one light. Also, shoot each colorway as its own set. A blue bottle and a red bottle in one reference set give you a purple-ish one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The shape is wrong on the back or the sides.&lt;/strong&gt; The model never saw them. Add the side and back photos. A product with a distinctive cap needs a top-down shot too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Random props show up in the scene.&lt;/strong&gt; They were in the references. Crop them out, and keep lifestyle photos out of the set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt fights the photos.&lt;/strong&gt; Once the photos are attached, don't describe the product again in words ("a teal glass bottle with a gold cap"). Name it and describe the scene instead. When words and photos disagree, the model blends them.&lt;/p&gt;

&lt;h2&gt;
  
  
  From photos to clips
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftmc8yaq9ws7do5nshbvu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftmc8yaq9ws7do5nshbvu.jpg" alt="A running shoe in a gym, generated from three reference photos" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;AERO 1, an invented running shoe: three photos against one gray backdrop, then a gym scene generated from them with Kling 3.0 Omni.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once the set is right, the video part is short: save the product once, write one line about the scene, and every clip starts from the same object. Details, including what to do with only one shop photo, are in &lt;a href="https://refvid.com/product-photo-to-video" rel="noopener noreferrer"&gt;product photo to video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The clips worth making first are the ones a shop actually uses: a &lt;a href="https://refvid.com/product-360-turntable" rel="noopener noreferrer"&gt;360-degree turntable&lt;/a&gt; for the product page, a vertical clip of someone holding it for TikTok and Reels, an unboxing. Our &lt;a href="https://refvid.com/ecommerce-video" rel="noopener noreferrer"&gt;ecommerce video guide&lt;/a&gt; covers which format goes where.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One light type, no mixing.&lt;/li&gt;
&lt;li&gt;Plain curved backdrop, product well forward.&lt;/li&gt;
&lt;li&gt;Main lens, no filters, exposure locked.&lt;/li&gt;
&lt;li&gt;Front, three-quarter, side, back, logo close-up, plus one in-use shot.&lt;/li&gt;
&lt;li&gt;Colors match across the set; one set per colorway.&lt;/li&gt;
&lt;li&gt;Logo sharp at full size, checked on a big screen, not the phone.&lt;/li&gt;
&lt;li&gt;Hero angle uploaded first, 3 to 5 photos in total.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Side effect: these are the photos every shop wants anyway, so one afternoon at the window covers both the listing and the video. If your output still drifts, tell me in the comments what broke.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>video</category>
      <category>tutorial</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>How we keep one character's face the same across AI video shots (Kling reference-to-video, with code)</title>
      <dc:creator>Louis Chi</dc:creator>
      <pubDate>Sat, 24 Oct 2026 13:59:29 +0000</pubDate>
      <link>https://dev.to/ref_vid/how-we-keep-one-characters-face-the-same-across-ai-video-shots-kling-reference-to-video-with-1koi</link>
      <guid>https://dev.to/ref_vid/how-we-keep-one-characters-face-the-same-across-ai-video-shots-kling-reference-to-video-with-1koi</guid>
      <description>&lt;p&gt;If you've built anything on top of a text-to-video model, you've hit this: the first clip is great, the second has a slightly different nose, and by the fifth it's a different person. A prompt like "a woman with a copper-red bob and an olive jacket" fits millions of people, and the model picks a new one on every request.&lt;/p&gt;

&lt;p&gt;I run &lt;a href="https://refvid.com/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=consistency-post" rel="noopener noreferrer"&gt;RefVid&lt;/a&gt;, a small tool built around one idea: you save a character or a product once, as a few photos, and every clip after that is generated from exactly those photos. This post covers what that takes in code. We use Kling's reference-to-video endpoints through fal's queue API, and everything below comes from our production code and our own test clips.&lt;/p&gt;

&lt;h2&gt;
  
  
  Words alone drift; photos hold
&lt;/h2&gt;

&lt;p&gt;We started by measuring the problem. We took one invented character, Mia, and generated the same three scenes twice on Kling 3.0 Omni, 5 seconds each:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompt only, with Mia described in as much detail as we could fit (hair, freckles, earrings, jacket).&lt;/li&gt;
&lt;li&gt;The same scene prompts with no appearance words at all, plus four reference photos of Mia attached.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F801jj7fw4zz2p9wc2laa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F801jj7fw4zz2p9wc2laa.jpg" alt="Prompt-only kitchen clip: the character is described in words" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Prompt only. A copper-red bob and an olive jacket, but not quite the same woman as in the other scenes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flceomei2p654fgerscur.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flceomei2p654fgerscur.jpg" alt="The same kitchen scene with four reference photos attached" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Four reference photos, no appearance words in the prompt. The same face in every scene.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The prompt-only set cost $1.26 for three clips, and each one gives you a different person who matches the description. The reference set keeps her. The full side-by-side is on our &lt;a href="https://refvid.com/consistent-character-ai-video?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=consistency-post" rel="noopener noreferrer"&gt;consistent character guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What we took from it: once you attach references, stop describing the person in the prompt. When the words and the photos disagree, the model blends them, and the blend is exactly the drift you were trying to fix.&lt;/p&gt;
&lt;h2&gt;
  
  
  The request shape: elements, not just images
&lt;/h2&gt;

&lt;p&gt;Kling's reference-to-video takes subjects as &lt;em&gt;elements&lt;/em&gt;. An element is one frontal image plus extra views of the same subject. The prompt points at it as &lt;code&gt;@Element1&lt;/code&gt;, &lt;code&gt;@Element2&lt;/code&gt;, and so on. Here is the core of how we turn a saved subject into an element (simplified from our provider):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_ELEMENT_IMAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// one frontal + up to three extra views per element&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;frontal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;imageUrls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;frontal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;at least one reference image is required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;views&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MAX_ELEMENT_IMAGES&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// reference_image_urls needs at least one image, so a one-photo subject repeats its frontal view&lt;/span&gt;
    &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;frontal_image_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;frontal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reference_image_urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;views&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;frontal&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// photos beyond four go in as general references instead of being dropped&lt;/span&gt;
    &lt;span class="na"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MAX_ELEMENT_IMAGES&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things we learned the hard way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Order matters.&lt;/strong&gt; The first image is treated as the frontal view, so make it your sharpest, most evenly lit, straight-on shot. A three-quarter view in that slot gives you a slightly turned face in every clip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One photo still works, but it has to be sent twice.&lt;/strong&gt; The endpoint wants at least one extra view, so a single-image subject sends its frontal again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overflow goes to &lt;code&gt;image_urls&lt;/code&gt;.&lt;/strong&gt; If someone uploads five photos, the fifth still helps as a plain reference instead of being silently ignored.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The prompt must name every element
&lt;/h2&gt;

&lt;p&gt;If the prompt never mentions &lt;code&gt;@Element1&lt;/code&gt;, the model is free to ignore the photos. People don't write &lt;code&gt;@Element1&lt;/code&gt;, though. They write "Mia pours coffee". So before a prompt goes out, we swap each subject's name for its token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// "Mia holds NOIR 07 and says \"Hi, I'm Mia\""&lt;/span&gt;
&lt;span class="c1"&gt;//   → "@Element1 holds @Element2 and says \"Hi, I'm Mia\""&lt;/span&gt;
&lt;span class="nf"&gt;bindSubjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mia&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NOIR 07&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The details are where it breaks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Whole words only.&lt;/strong&gt; "Mia" must not match inside "Miami". We use Unicode-aware boundaries, &lt;code&gt;(?&amp;lt;![\p{L}\p{N}])&lt;/code&gt; before and &lt;code&gt;(?![\p{L}\p{N}])&lt;/code&gt; after. They're skipped for Chinese, Japanese and Thai, where words have no spaces around them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not inside quoted dialogue.&lt;/strong&gt; Kling 4 clips have sound, and if the swap reaches the spoken line, the voice literally says "at Element one". Text inside &lt;code&gt;"…"&lt;/code&gt;, &lt;code&gt;“…”&lt;/code&gt;, &lt;code&gt;„…“&lt;/code&gt;, &lt;code&gt;«…»&lt;/code&gt; and &lt;code&gt;「…」&lt;/code&gt; is left alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unnamed subjects get prefixed.&lt;/strong&gt; If a subject is attached but never named, its token is put at the start of the prompt so the model still uses it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respect authors who already use tokens.&lt;/strong&gt; If a prompt already contains &lt;code&gt;@Element1&lt;/code&gt;, we leave it untouched.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Long sequences: split, then chain
&lt;/h2&gt;

&lt;p&gt;Faces hold up best in short clips. Drift creeps in as the camera moves and the face turns to angles the references don't cover. So instead of one long generation, we do two things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-shot requests.&lt;/strong&gt; Kling accepts a &lt;code&gt;multi_prompt&lt;/code&gt; list, one prompt per shot, and splits the duration between them. On Kling 4, each shot's prompt is capped at 512 characters. Names and translations can push a shot over that cap, so in that case we send one prompt that lists the shots in order rather than let the request fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shotsFit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shots&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shotsFit&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;multi_prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shots&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shots&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`Shot &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; s): &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Chained clips.&lt;/strong&gt; For a series, we start each clip from the last frame of the previous one (&lt;code&gt;start_image_url&lt;/code&gt;) and attach the same element every time. We made a four-shot running-shoe sequence this way: four 5-second clips at $0.42 each, plus $0.03 to pull the last frames out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flbrqr54366snch2ah4iu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flbrqr54366snch2ah4iu.jpg" alt="Shot 1 of a chained four-shot sequence" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Shot 1. Each of the next shots starts from the previous shot's last frame, with the same product element attached.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxq33rzg9ob1vz7ybkuj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxq33rzg9ob1vz7ybkuj.jpg" alt="Shot 4 of the same sequence" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Shot 4. Same shoe, same path, same light direction.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The start frame carries the scene and the light. The element carries the identity. You need both: with only the start frame, the identity slips once the camera moves away from what that frame showed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Products are harder than faces
&lt;/h2&gt;

&lt;p&gt;People forgive a slightly different jaw. Nobody forgives a misspelled logo. For products:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shoot three photos on a plain backdrop: the hero angle, a three-quarter view, and a close-up where the label fills the frame.&lt;/li&gt;
&lt;li&gt;Put the label in the prompt as a fact about the scene, not as a description: "the LUMEN label faces the camera and stays readable".&lt;/li&gt;
&lt;li&gt;Keep the label large in frame in at least one shot. Small text in a wide shot has too few pixels to come out right.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F64c6a888q5specp0qyep.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F64c6a888q5specp0qyep.jpg" alt="LUMEN serum bottle on a bathroom shelf, generated from three reference photos" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;LUMEN is a serum we invented for testing. Three reference photos, Kling 3.0 Omni; the label carries over into each scene.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Plumbing notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use the queue and a webhook, not a blocking call.&lt;/strong&gt; A 3-second 720p Kling 4 clip with sound took about 80 seconds to generate for us. We submit to &lt;code&gt;queue.fal.run/&amp;lt;model&amp;gt;?fal_webhook=&amp;lt;url&amp;gt;&lt;/code&gt;, verify the webhook signature, and keep a status poll as a fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat a 5xx on the result fetch as retryable.&lt;/strong&gt; A failed fetch of the result doesn't mean the job failed. We only mark a generation failed when fal reports an error for the job itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the webhook idempotent.&lt;/strong&gt; Record the request id before you act on it. Webhooks get delivered twice, and you don't want to refund or charge twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd tell anyone building this
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Version the reference set. Most "the face changed" bug reports come down to clip 1 and clip 4 having been made from different photos.&lt;/li&gt;
&lt;li&gt;Strip appearance words from prompts once references are attached.&lt;/li&gt;
&lt;li&gt;Keep clips short and cut between them. Chaining 5-second clips beats one 15-second clip for identity.&lt;/li&gt;
&lt;li&gt;Test with an invented character, so you know what "the same person" means.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to try it without building any of this, &lt;a href="https://refvid.com/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=consistency-post" rel="noopener noreferrer"&gt;RefVid&lt;/a&gt; does all of the above behind one upload. Ask in the comments about the request shapes or the regexes. I'm happy to go into more detail.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>video</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
