<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Building Stivio]]></title><description><![CDATA[Notes from building an image-to-video tool.]]></description><link>https://building-stivio.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 02 Nov 2026 23:51:48 GMT</lastBuildDate><atom:link href="https://building-stivio.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Users Kept Asking for "Cinematic." So I Turned a Vibe Into a Motion System]]></title><description><![CDATA[The most common request I got from users was not a feature. It was a vibe. "Cinematic." "Dreamy." "Epic." People typed these words into the prompt box of…]]></description><link>https://building-stivio.hashnode.dev/users-kept-asking-for-cinematic-so-i-turned-a-vibe-into-a-motion-system</link><guid isPermaLink="true">https://building-stivio.hashnode.dev/users-kept-asking-for-cinematic-so-i-turned-a-vibe-into-a-motion-system</guid><category><![CDATA[AI]]></category><category><![CDATA[webdev]]></category><category><![CDATA[showdev]]></category><category><![CDATA[MachineLearning]]></category><dc:creator><![CDATA[Gabriella Andrews]]></dc:creator><pubDate>Mon, 26 Oct 2026 08:06:26 GMT</pubDate><content:encoded><![CDATA[<p>The most common request I got from users was not a feature. It was a vibe.</p>
<p>"Cinematic." "Dreamy." "Epic." People typed these words into the prompt box of my image-to-video tool and expected the system to know what they meant. It did not. A model given the word "cinematic" will do something, because models always do something, but that something was different every single time. One render would slowly push into a portrait. The next would whip the camera around a landscape like a trailer for a disaster film. The user asked for the same word twice and got two different videos, and then they asked me why.</p>
<p>That question sat with me, because it exposed a real design problem. A vague word is not a specification. If I wanted consistent results, I had to stop passing vibes straight through to the model and start translating them into numbers first. This post is about the motion system I built to do that: a small set of camera moves, each with a strength and a duration, plus rules about which moves are safe for which kinds of photos. It is not glamorous work. It cut my re-render requests from about one in four to about one in ten, and it is the part of the product users now describe as "it just does what I meant."</p>
<h2>The problem with passing words through</h2>
<p>My first version had a free-text prompt box and nothing else. The user typed a sentence, I appended it to the generation request, and the model interpreted it however it liked. This worked well enough for simple asks like "slow zoom in." It fell apart on taste words, because taste words do not have a single visual meaning. "Cinematic" to one person means a slow push-in on a face. To another it means a sweeping drone shot. To the model it means whatever pattern dominated its training data for that token, which changes with the photo, the aspect ratio, and seemingly the weather.</p>
<p>The deeper issue was that there was nothing to debug. When a user said "this is not what I asked for," I had no record of what was actually requested in machine terms. The prompt text was the only artifact, and the prompt text was the vague part. I could not reproduce the failure, I could not compare two renders, and I could not tell whether a bad result came from the model, the prompt, or the photo itself.</p>
<p>So I made a rule for myself: no motion reaches the model unless it first exists as structured data in my own system. Every motion is now a small record with four fields: the move type, the strength from 0 to 1, the duration in seconds, and the subject it applies to. The user never sees this record directly. They pick words and presets. But underneath, the system always knows exactly what it asked for, and I can log it, replay it, and compare it.</p>
<h2>Twelve presets, three strengths, and a lot of arguing</h2>
<p>The preset list started at thirty and got argued down to twelve. Thirty presets felt generous and tested terribly: users scrolled, hesitated, and picked the first one. Twelve is small enough to scan and large enough to cover the moves people actually want: push in, pull out, orbit left, orbit right, pan left, pan right, tilt up, tilt down, rise, descend, dolly forward, and a gentle handheld drift. Each preset is a move type plus a default strength and a default path shape. The user picks a preset and a strength: soft, standard, or strong. That is the whole interface, and the simplicity is the point. A user choosing between twelve named moves and three strengths makes a decision in seconds. A user facing a blank prompt box makes a wish.</p>
<p>Strength turned out to be where most of the quality lives. At strength 0.3, a push-in is a quiet lean toward the subject. At 0.9, the same preset is a dramatic charge at it. Early on I let users set any strength on any photo, and the failure reports taught me why that was a mistake. Faces distort under strong push-ins: noses widen, ears slide, and a portrait that looked fine as a photo comes back looking like a caricature. Text inside photos warps under strong orbits, so a shop sign bends as the camera circles it. Small subjects get thrown out of frame by strong pans, because the camera moves further than the subject's size can survive.</p>
<p>The fix was caps per photo type, and this is the least exciting and most valuable part of the system. Portraits cap push-in strength at 0.6. Photos with legible text cap orbit strength at 0.5. Small subjects cap pan strength based on how much of the frame they occupy. These caps are not shown as errors. When a user picks a combination over the cap, the control quietly stops at the cap, and a short note explains why: "Strong orbits warp text in this photo." Users almost never complain about a cap they understand. They complain a lot about a render that bent their shop sign.</p>
<h2>A test set of sixty photos</h2>
<p>I did not trust my own taste to set those caps, so I built a test set of sixty photos: twenty portraits, twenty landscapes, twenty product shots. Every preset at every strength ran against the relevant photos, and I watched all of it. That is 12 presets times 3 strengths times 60 photos, and yes, it took a weekend, and yes, my eyes hurt. But it gave me something no amount of reasoning could: a table of what actually breaks, at which strength, on which kind of photo.</p>
<p>Three patterns came out of that weekend. First, faces break first and worst, and they break in ways users notice instantly. A landscape can survive an aggressive move and still look dramatic. A face cannot. Second, text breaks silently: the render looks fine at a glance and wrong the moment you read the sign. Third, the most popular combination was not the most dramatic one. "Cinematic" was picked first by about 40% of users in early testing, and what most of them actually wanted, once I mapped it, was a slow push-in at standard strength on whatever they had uploaded. The word sounded epic. The preference was gentle.</p>
<p>That last finding changed how I think about presets. The preset names are marketing in the good sense: they give users a vocabulary for what they want. But the mapping from name to numbers has to come from watching what users accept, not from what the name promises. "Cinematic" maps to a slow push at 0.5 in my system today, not because a dictionary says so, but because that is the render users kept and stopped re-rolling.</p>
<h2>What the user sees, and what the model gets</h2>
<p>The flow now works like this. The user uploads a photo and picks a preset and a strength, or types a word like "cinematic" and lets the system map it to a preset. Before anything is sent to the model, the system checks the photo type, applies the caps, and writes down the final motion record: move, strength, duration, and the photo it applies to. That record goes to the model as the motion instruction, and it also goes into my logs. If the user re-renders, the record is shown back to them in plain language: "Slow push-in, standard strength, 5 seconds." They can change one field and try again, instead of retyping a wish and hoping.</p>
<p>This changed support completely. "This is not cinematic" used to be an unanswerable ticket. Now I can see that the user got a slow push at 0.5, and I can ask whether they wanted a stronger move or a different one. Most of the time they wanted a stronger move, and the fix is one click. The re-render rate falling from roughly one in four to roughly one in ten did not come from the model getting better. It came from the user and the system finally sharing a vocabulary, and from the system refusing to attempt moves that the test set had already shown would break that kind of photo.</p>
<p>There is one more piece that matters and is easy to skip: duration. A five-second clip forgives a lot. A strong move stretched over five seconds is gentle; the same move over two seconds is violent. My presets carry a default duration, and strength scales the distance, not the speed, so a strong move still takes the full clip to arrive. Users describe strong moves as "dramatic" now instead of "seasick," and that single distinction is worth more than any new preset I could add.</p>
<h2>What I would do differently</h2>
<p>If I started again, I would build the test set before the preset list, not after. I designed twelve presets from intuition, then spent a weekend learning which of them were unsafe at which strengths. The test set should have been the design tool, not the audit. I would also log the motion record from day one. The months of renders I did before structured motion are unrecoverable as data: I know users were unhappy, but I cannot say which moves failed, because nothing was written down.</p>
<p>The other lesson is about where to put the complexity. Users want three choices, not thirty, and they want the system to be honest about limits. A cap with an explanation feels like craft. A failed render feels like a bug. Every time I was tempted to expose more controls, I asked whether the control would help a user say what they meant, or just give them a new way to produce a broken video. Most of the time it was the second one, and the control stayed out.</p>
<p>If you are building anything where users describe motion, camera work, or style in plain words, my advice is to treat the words as input to a translation layer, not as instructions. Build the translation from watching real renders, cap it by what actually breaks, and write down every decision the layer makes. The model is the last step, not the first, and the vocabulary you give your users is a product decision, not a prompt trick.</p>
<p>I build <a href="https://stivio.ai">photos to video ai</a> tools that turn still photos into short clips, and the motion system is the part I am proudest of, mostly because it started as a complaint about a word and ended as a table of numbers I can defend.</p>
]]></content:encoded></item></channel></rss>