<?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[MixVio AI]]></title><description><![CDATA[MixVio AI]]></description><link>https://dev.mixvio.ai</link><image><url>https://cdn.hashnode.com/uploads/logos/6ac7155f8cadc38d9e483af7/db251d60-b932-474b-93b6-0f7ce3aef7f3.png</url><title>MixVio AI</title><link>https://dev.mixvio.ai</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 12 Oct 2026 12:26:10 GMT</lastBuildDate><atom:link href="https://dev.mixvio.ai/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Limit True Peak to -1 dBTP with ffmpeg Before Uploading]]></title><description><![CDATA[AI-generated example made with MixVio's Burning Bridges AI effect. The lead is an AI-generated portrait, not a real person.
A clip can look fine in your editor and still clip after upload. Social plat]]></description><link>https://dev.mixvio.ai/limit-true-peak-to-1-dbtp-with-ffmpeg-before-uploading</link><guid isPermaLink="true">https://dev.mixvio.ai/limit-true-peak-to-1-dbtp-with-ffmpeg-before-uploading</guid><category><![CDATA[FFmpeg]]></category><category><![CDATA[audio]]></category><category><![CDATA[video]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[MixVio AI]]></dc:creator><pubDate>Mon, 12 Oct 2026 11:42:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ac7155f8cadc38d9e483af7/45615402-eb33-4b6b-94da-f0639c5fb9a7.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<img src="https://cdn.hashnode.com/uploads/covers/6ac7155f8cadc38d9e483af7/45615402-eb33-4b6b-94da-f0639c5fb9a7.jpg" alt="Cover: &quot;Fix inter-sample peaks before you upload&quot; next to an output frame from a 15-second AI party clip" style="display:block;margin:0 auto" />

<p><em>AI-generated example made with MixVio's Burning Bridges AI effect. The lead is an AI-generated portrait, not a real person.</em></p>
<p>A clip can look fine in your editor and still clip after upload. Social platforms re-encode audio, and a file whose waveform already touches 0 dBFS often has inter-sample peaks above 0 dB once it is reconstructed or transcoded. You hear that as harsh distortion on the loudest beats.</p>
<p>This tutorial shows an ffmpeg true peak limit workflow I used on a 15-second AI party clip: measure with <code>ebur128</code>, resample to 48 kHz, take off a little gain, and run an oversampled limiter so the result sits at or below -1 dBTP. Everything here is about levels only. It says nothing about music rights, which are a separate question.</p>
<h2>The example file</h2>
<p>The example clip was made with <a href="https://mixvio.ai/ai-effects/burning-bridges-ai?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=burning-bridges&amp;utm_content=burning-bridges-ai-true-peak#effect-workspace">Burning Bridges AI on MixVio</a>: a 15-second 1280x720 video whose effect attaches the original source track after the visual edit. As delivered, its audio was 44.1 kHz stereo, about -12.6 LUFS integrated, with peaks at +1.1 dBFS, so the source track was already clipping before any platform touched it.</p>
<p>That makes it a good test case: we want to fix the peaks without changing the music, the timing or the video.</p>
<h2>Step 1: measure integrated loudness and true peak</h2>
<p><code>ebur128</code> with <code>peak=true</code> reports EBU R128 loudness and a 4x oversampled true-peak value:</p>
<pre><code class="language-bash">ffmpeg -hide_banner -nostats -i in.mp4 \
  -af ebur128=peak=true -f null - 2&gt;&amp;1 | grep -E "I:|Peak:"
</code></pre>
<p>On the example clip:</p>
<pre><code class="language-text">I:         -12.6 LUFS
Peak:        1.1 dBFS
</code></pre>
<p>The <code>Peak</code> line in the true-peak section is the number to watch. Anything above about -1 dBTP is likely to clip after a lossy re-encode. Most platforms also normalise playback to around -14 LUFS, so a -12.6 LUFS file will simply be turned down; there is little to gain from leaving it that loud.</p>
<h2>Step 2: understand why the sample peak lies</h2>
<p>A sample peak only looks at the stored samples. Between two samples the reconstructed analog waveform can rise higher, especially on bright, dense mixes. AAC and Opus encoders work on that reconstructed signal, so a file with sample peaks at -0.1 dBFS can still produce encoded peaks above 0 dB.</p>
<p>True-peak meters estimate this by oversampling (4x here) before taking the maximum. That is why you should limit at a higher sample rate too: a limiter that only sees 48 kHz samples cannot catch overs it does not see.</p>
<h2>Step 3: resample, trim gain, oversample and limit</h2>
<p>This keeps the video stream untouched (<code>-c:v copy</code>) and only rebuilds the audio:</p>
<pre><code class="language-bash">ffmpeg -i in.mp4 -c:v copy \
  -af "aresample=48000:resampler=soxr,volume=-1.6dB,\
aresample=192000,alimiter=limit=0.84:level=disabled,\
aresample=48000" \
  -c:a aac -b:a 192k out.mp4
</code></pre>
<p>What each part does:</p>
<ul>
<li><p><code>aresample=48000:resampler=soxr</code> converts 44.1 kHz to 48 kHz, the rate most video platforms expect, with the high-quality SoX resampler.</p>
</li>
<li><p><code>volume=-1.6dB</code> brings the integrated loudness close to -14 LUFS. This is plain gain; it does not change the mix.</p>
</li>
<li><p><code>aresample=192000</code> upsamples 4x so the limiter can see inter-sample overs.</p>
</li>
<li><p><code>alimiter=limit=0.84:level=disabled</code> caps the signal at about -1.5 dBFS (0.84 linear). <code>level=disabled</code> stops alimiter from re-normalising the output back up.</p>
</li>
<li><p><code>aresample=48000</code> returns to the delivery rate.</p>
</li>
<li><p><code>-c:a aac -b:a 192k</code> encodes AAC at a bitrate that leaves headroom for the next encode.</p>
</li>
</ul>
<h2>Step 4: measure again</h2>
<p>Run the same <code>ebur128</code> command on <code>out.mp4</code>:</p>
<pre><code class="language-text">I:         -14.2 LUFS
Peak:       -1.9 dBFS
</code></pre>
<p>Integrated loudness moved by the gain change only, and the true peak is now below -1 dBTP. A quick check that the format is what the platform expects:</p>
<pre><code class="language-bash">ffprobe -v error -select_streams a \
  -show_entries stream=sample_rate,channels -of csv=p=0 out.mp4
# 48000,2
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/6ac7155f8cadc38d9e483af7/95d3ad4c-42b2-4cd3-9199-d71011f5be36.jpg" alt="Chart: true peak per 50 ms over 15 seconds for the source, the 48 kHz master and the social export, with 0 dBFS and -1 dBTP reference lines" style="display:block;margin:0 auto" />

<p><em>True peak over time for the source (44.1 kHz), the 48 kHz master and the social export. Chart from ffmpeg ebur128 measurements; the final social export measured -14.3 LUFS and -2.4 dBTP.</em></p>
<p>The chart comes from a slightly different export of the same chain, which is why its numbers differ from my run by a few tenths of a dB. Both sit well under the -1 dBTP line.</p>
<h2>Alternative: two-pass loudnorm</h2>
<p>If you would rather target loudness directly, <code>loudnorm</code> can do both jobs. Run it once to measure:</p>
<pre><code class="language-bash">ffmpeg -hide_banner -i in.mp4 \
  -af loudnorm=I=-14:TP=-1.5:LRA=11:print_format=summary \
  -f null -
</code></pre>
<p>Then feed the measured values back in a second pass with <code>measured_I</code>, <code>measured_TP</code>, <code>measured_LRA</code>, <code>measured_thresh</code> and <code>linear=true</code>. Linear mode applies a single gain where it can, which keeps the dynamics of a music track intact. If the target would push peaks over the ceiling, loudnorm falls back to dynamic mode, so always re-measure.</p>
<p>For short clips that are already loud, the explicit chain in Step 3 is easier to reason about: one gain value, one limiter, nothing adaptive.</p>
<h2>Batch it</h2>
<p>A tiny shell loop applies the same chain to a folder of exports and prints the result for each file:</p>
<pre><code class="language-bash">#!/usr/bin/env bash
set -euo pipefail
AF="aresample=48000:resampler=soxr,volume=-1.6dB"
AF="$AF,aresample=192000,alimiter=limit=0.84:level=disabled"
AF="$AF,aresample=48000"
mkdir -p fixed
for f in exports/*.mp4; do
  out="fixed/$(basename "$f")"
  ffmpeg -nostdin -loglevel error -y -i "$f" -c:v copy \
    -af "$AF" -c:a aac -b:a 192k "$out"
  printf '%s ' "$out"
  ffmpeg -hide_banner -nostats -i "$out" -af ebur128=peak=true \
    -f null - 2&gt;&amp;1 | grep -E "Peak:" | tail -n 1
done
</code></pre>
<p>Adjust <code>volume</code> per file if your sources differ a lot in loudness; the limiter is there to catch peaks, not to do the loudness work.</p>
<h2>Things to keep in mind</h2>
<ul>
<li><p><strong>Don't swap the music to fix levels.</strong> Gain and limiting change levels, not content. If a platform raises a rights question, that is not something a filter can answer.</p>
</li>
<li><p><strong>Keep video untouched.</strong> <code>-c:v copy</code> avoids a generation loss on the picture.</p>
</li>
<li><p><strong>Leave headroom.</strong> -1 dBTP is a common ceiling; -1.5 to -2 dBTP gives extra safety for platforms that re-encode at low bitrates.</p>
</li>
<li><p><strong>Check by ear.</strong> A limiter working too hard pumps. If you hear it, lower the gain before the limiter instead of pushing the limit lower.</p>
</li>
</ul>
<p>A last note on the example: the effect page states "This is an independent AI effect and is not affiliated with Drake."</p>
<p><em>Disclosure: written by the MixVio team with AI assistance; the example video was made with MixVio's Burning Bridges AI effect from an AI-generated portrait.</em></p>
]]></content:encoded></item><item><title><![CDATA[Draw a 9:16 Safe-Zone Overlay for Shorts with Python]]></title><description><![CDATA[AI-generated example made with MixVio's Burning Bridges AI effect. The lead is an AI-generated portrait, not a real person.
Shorts, Reels and TikTok all draw their own interface on top of your video: ]]></description><link>https://dev.mixvio.ai/draw-a-9-16-safe-zone-overlay-for-shorts-with-python</link><guid isPermaLink="true">https://dev.mixvio.ai/draw-a-9-16-safe-zone-overlay-for-shorts-with-python</guid><category><![CDATA[Python]]></category><category><![CDATA[pillow]]></category><category><![CDATA[FFmpeg]]></category><category><![CDATA[video]]></category><dc:creator><![CDATA[MixVio AI]]></dc:creator><pubDate>Mon, 12 Oct 2026 01:04:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ac7155f8cadc38d9e483af7/7c411a8f-78dd-491c-a405-b0dca7be3b0e.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/uploads/covers/6ac7155f8cadc38d9e483af7/7c411a8f-78dd-491c-a405-b0dca7be3b0e.jpg" alt="Cover: " />
<em>AI-generated example made with MixVio's Burning Bridges AI effect. The lead is an AI-generated portrait, not a real person.</em></p>
<p>Shorts, Reels and TikTok all draw their own interface on top of your video: a caption and sound label along the bottom, a column of buttons on the right, and status or search elements at the top. Text or faces in those areas get covered.</p>
<p>Checking that by eye on every export gets old quickly. This post builds a small Python script that grabs a few frames from a 9:16 export with ffmpeg, tints the risky areas with Pillow, and saves one contact sheet you can glance at before uploading.</p>
<h2>The zones</h2>
<p>Every platform draws its UI slightly differently, and the layouts change. Rather than chase exact pixel values, I use one conservative set of zones for a 1080x1920 frame:</p>
<ul>
<li><strong>Top 10%</strong> (y &lt; 192): status bar, search, account labels.</li>
<li><strong>Bottom 20%</strong> (y &gt;= 1536): caption, sound name, progress bar.</li>
<li><strong>Right 15%</strong> (x &gt;= 918): like, comment, share and profile buttons.</li>
</ul>
<p>Keep titles and faces out of those areas and the video reads cleanly on all three apps. Defining the zones as fractions keeps the script working for 720x1280 exports too.</p>
<h2>What you need</h2>
<ul>
<li>Python 3 with Pillow (<code>pip install pillow</code>).</li>
<li>ffmpeg on your PATH.</li>
<li>A 9:16 export to check.</li>
</ul>
<p>The example export here is a 17.5-second 1080x1920 short built from a 16:9 clip made with <a href="https://mixvio.ai/ai-effects/burning-bridges-ai?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=burning-bridges&amp;utm_content=burning-bridges-ai-safezone#effect-workspace">Burning Bridges AI on MixVio</a>. The effect outputs 16:9 only, so the 9:16 version is a letterboxed post-production edit: the 16:9 result sits in the middle of the frame, with a title card at the start and an end card at the end. That makes it a good test case, because the titles live in the letterbox bars, right next to the zones.</p>
<h2>The script</h2>
<pre><code class="language-python">import subprocess
import sys
from PIL import Image, ImageDraw

W, H = 1080, 1920
ZONES = {  # name: (box as fractions x0, y0, x1, y1), RGBA tint
    "top 10%": ((0.0, 0.0, 1.0, 0.10), (255, 220, 0, 90)),
    "bottom 20%": ((0.0, 0.80, 1.0, 1.0), (255, 40, 40, 90)),
    "right 15%": ((0.85, 0.0, 1.0, 1.0), (255, 140, 0, 90)),
}


def grab(video, t, out):
    cmd = ["ffmpeg", "-v", "error", "-y", "-ss", str(t), "-i", video,
           "-frames:v", "1", out]
    subprocess.run(cmd, check=True)
    return Image.open(out).convert("RGBA")


def overlay(frame):
    w, h = frame.size
    layer = Image.new("RGBA", frame.size, (0, 0, 0, 0))
    d = ImageDraw.Draw(layer)
    for name, ((x0, y0, x1, y1), rgba) in ZONES.items():
        box = (int(x0 * w), int(y0 * h), int(x1 * w) - 1, int(y1 * h) - 1)
        d.rectangle(box, fill=rgba, outline=rgba[:3] + (255,), width=4)
    return Image.alpha_composite(frame, layer)


def sheet(video, times, out="safezone.png", scale=0.5):
    tiles = []
    for i, t in enumerate(times):
        img = overlay(grab(video, t, f"_f{i}.png"))
        img = img.resize((int(W * scale), int(H * scale)))
        ImageDraw.Draw(img).text((12, 12), f"{t:.2f} s", fill="white")
        tiles.append(img)
    tw, th = tiles[0].size
    out_img = Image.new("RGBA", (tw * len(tiles), th), "black")
    for i, tile in enumerate(tiles):
        out_img.paste(tile, (i * tw, 0))
    out_img.convert("RGB").save(out)
    return out


if __name__ == "__main__":
    print(sheet(sys.argv[1], [0.05, 11.9, 17.0]))
</code></pre>
<p>The script has four parts.</p>
<h3>ZONES</h3>
<p>Each zone is a box in fractions of the frame (x0, y0, x1, y1) plus an RGBA tint. An alpha of 90 out of 255 is enough to see the zone without hiding what is under it, and a solid outline marks the edge. Overlapping zones (the bottom-right corner) simply get darker, which is a useful hint: that corner is covered by both the caption and the buttons.</p>
<h3>grab()</h3>
<p><code>grab()</code> asks ffmpeg for one frame at time <code>t</code>. Putting <code>-ss</code> before <code>-i</code> makes ffmpeg seek quickly, which is accurate enough for QA. It writes a PNG and loads it as RGBA so it can be composited.</p>
<h3>overlay()</h3>
<p><code>overlay()</code> draws all zones onto a transparent layer the size of the frame and alpha-composites it over the frame. Drawing on a separate layer keeps the tint even; drawing straight onto the frame with a semi-transparent fill would not blend.</p>
<h3>sheet()</h3>
<p><code>sheet()</code> grabs each time stamp, overlays the zones, scales each frame to half size, stamps the time in the corner, and pastes the frames side by side. The output is a single RGB PNG.</p>
<h2>Choosing the sample times</h2>
<p>Pick frames where something important is on screen. For this export:</p>
<ul>
<li><strong>0.05 s</strong>: the title card. The first frame doubles as the cover on some platforms.</li>
<li><strong>11.9 s</strong>: a frame from the main clip, with the persistent label above it and the small print below.</li>
<li><strong>17.0 s</strong>: the end card.</li>
</ul>
<p>Run it with the export path:</p>
<pre><code class="language-bash">python3 safezone.py short.mp4
# safezone.png  (1620x960 for three 1080x1920 frames at 0.5 scale)
</code></pre>
<h2>Reading the result</h2>
<p><img src="https://mixvioai.wordpress.com/wp-content/uploads/2026/10/02_img_as2-sz.jpg" alt="QA sheet: three frames of the 9:16 export with the top 10%, bottom 20% and right 15% safe zones tinted: the cover at 0.05 s, a clip frame at 11.9 s and the end card at 17.0 s" />
<em>AI-generated example made with MixVio's Burning Bridges AI effect. The lead is an AI-generated portrait, not a real person.</em></p>
<p>What to look for:</p>
<ul>
<li><strong>Titles outside the tint.</strong> The title on the cover frame sits between the top zone and the clip, and clear of the right column.</li>
<li><strong>Faces outside the tint.</strong> The 16:9 clip sits in the middle of the frame, so the lead's face stays clear of every zone. The right edge of the clip falls under the button column; that is acceptable here because only the background is there, but it is exactly what this check is for.</li>
<li><strong>End card text.</strong> Calls to action at the very bottom are the most common mistake; the caption covers them.</li>
<li><strong>The bottom-right corner.</strong> If anything you care about is in the double-tinted corner, move it.</li>
</ul>
<p>If something sits in a zone, fix the edit and run the script again. It takes a couple of seconds.</p>
<h2>Variations</h2>
<ul>
<li><strong>More frames.</strong> Pass a longer list, or sample evenly with <code>[i * dur / 8 for i in range(8)]</code> after reading the duration with ffprobe.</li>
<li><strong>Different zones.</strong> If you only publish to one platform, tighten the fractions for that app's current layout.</li>
<li><strong>A grid instead of a row.</strong> For long exports, paste the tiles into rows of four.</li>
<li><strong>CI check.</strong> Save the sheet next to each export in your render pipeline so every file has a QA image.</li>
</ul>
<h2>Limits</h2>
<p>This is a visual aid, not a guarantee. Platforms move UI elements, captions can grow to several lines, and some devices add their own system bars. Treat the zones as "keep important things out", not "everything outside is always visible".</p>
<p>A note on the example clip, from the effect page: "This is an independent AI effect and is not affiliated with Drake."</p>
<p><em>Disclosure: written by the MixVio team with AI assistance; the example video was made with MixVio's Burning Bridges AI effect from an AI-generated portrait.</em></p>
]]></content:encoded></item><item><title><![CDATA[Validate the Shot Before Generation: A Photo-to-Video Manifest for Vidu Q4]]></title><description><![CDATA[A photo-to-video request contains two different kinds of information: what the system can accept, and what the shot is supposed to communicate. Mixing them into one paragraph makes avoidable input mis]]></description><link>https://dev.mixvio.ai/validate-the-shot-before-generation-a-photo-to-video-manifest-for-vidu-q4</link><guid isPermaLink="true">https://dev.mixvio.ai/validate-the-shot-before-generation-a-photo-to-video-manifest-for-vidu-q4</guid><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[MixVio AI]]></dc:creator><pubDate>Sat, 10 Oct 2026 07:33:54 GMT</pubDate><content:encoded><![CDATA[<p>A photo-to-video request contains two different kinds of information: what the system can accept, and what the shot is supposed to communicate. Mixing them into one paragraph makes avoidable input mistakes harder to spot.</p>
<p>I work with the MixVio team. This guide uses our current Vidu Q4 interface as a concrete example and proposes a local planning manifest. It is not an API integration, an internal schema, or an independent test of model quality.</p>
<h2>Separate inputs, intent and review</h2>
<p>Keep three small records. The input record holds the mode, ordered references, duration, resolution, ratio and audio setting. The intent record describes a subject action, camera motion and sound. The review record says what you will inspect after generation.</p>
<p>An accepted request does not mean a usable video. Validation catches a thirteen-photo pack; review catches a product label that changed in the output. Those are different failure stages.</p>
<h2>Start with a manifest</h2>
<p>Here is an illustrative five-second reference-mode shot. The asset labels are placeholders. The manifest is a planning document; do not submit it as a MixVio API payload.</p>
<pre><code class="language-json">{
  "mode": "reference_to_video",
  "durationSeconds": 5,
  "resolution": "540p",
  "ratio": "9:16",
  "generateAudio": true,
  "images": [
    {"label": "product-front", "role": "identity"},
    {"label": "desk-scene", "role": "setting"}
  ],
  "audio": [],
  "prompt": "Keep the product from @Image1 on the desk from @Image2. Slowly push the camera closer while soft daylight shifts across the scene. Use quiet room ambience without speech.",
  "review": ["identity", "label legibility", "motion", "sound"]
}
</code></pre>
<p>The reference array has a meaningful order. If the first upload is the product and the second is the desk, the prompt must keep that same @Image1 / @Image2 mapping. Reordering the files without revising the prompt changes the request. An audio reference uses the corresponding @Audio1-style tag.</p>
<h2>Validate against the selected route</h2>
<p>On MixVio, image-to-video takes exactly one start photo, follows its ratio and always includes native audio. Reference-to-video takes 1–12 photos and up to three MP3 files; it offers five ratios and an audio toggle. Both routes list 3–16 seconds and 540p, 720p, 1080p, 2K or 4K. These are the current MixVio route limits, not a claim that every Vidu interface exposes identical inputs.</p>
<p>A small local check can reject inconsistent plans before you upload assets:</p>
<pre><code class="language-javascript">// Educational local planning check, not a MixVio API payload or production schema.
export function validateShot(s) {
  const errors = [];
  const modes = ['image_to_video', 'reference_to_video'];
  const resolutions = ['540p', '720p', '1080p', '2K', '4K'];
  const ratios = ['16:9', '9:16', '4:3', '3:4', '1:1'];
  if (!s || typeof s !== 'object' || Array.isArray(s)) return ['Shot must be an object'];
  if (!modes.includes(s.mode)) errors.push('Choose a supported mode');
  if (!Number.isInteger(s.durationSeconds) || s.durationSeconds &lt; 3 || s.durationSeconds &gt; 16) errors.push('Duration must be an integer from 3 to 16');
  if (!resolutions.includes(s.resolution)) errors.push('Choose a listed resolution');
  if (!Array.isArray(s.images)) errors.push('Images must be an array');
  if (!Array.isArray(s.audio)) errors.push('Audio must be an array');
  if (typeof s.generateAudio !== 'boolean') errors.push('Choose an explicit audio setting');
  if (s.mode === 'image_to_video') {
    if (s.images?.length !== 1) errors.push('Image mode needs exactly one photo');
    if (s.audio?.length !== 0) errors.push('Use reference mode for MP3 references');
    if (s.ratio !== 'source') errors.push('Image mode follows the photo ratio');
    if (s.generateAudio !== true) errors.push('Image mode always includes native audio');
  }
  if (s.mode === 'reference_to_video') {
    if (!(s.images?.length &gt;= 1 &amp;&amp; s.images.length &lt;= 12)) errors.push('Reference mode needs 1–12 photos');
    if (!(s.audio?.length &gt;= 0 &amp;&amp; s.audio.length &lt;= 3)) errors.push('Reference mode accepts at most 3 MP3 files');
    if (!ratios.includes(s.ratio)) errors.push('Choose a listed reference-mode ratio');
  }
  if (Array.isArray(s.audio) &amp;&amp; s.audio.some(a =&gt; !a || a.format !== 'mp3')) errors.push('Audio references must be MP3');
  if (typeof s.prompt !== 'string' || !s.prompt.trim()) errors.push('Add a shot description');
  return errors;
}
</code></pre>
<p>For the example above, <code>validateShot(shot)</code> returns an empty array. If the reference pack grows to thirteen photos, it returns a reference-count error. If you change the mode to image-to-video while keeping two photos, a selectable ratio and disabled audio, it reports all three inconsistencies.</p>
<p>This helper checks planning fields. It does not inspect file contents, dimensions, ownership, upload success or server acceptance. A real integration still needs its documented upload and server-side validation process. Keep private asset URLs and credentials out of a public manifest.</p>
<h2>Record review separately</h2>
<p>After generation, assign each check <code>pass</code>, <code>fail</code> or <code>not_reviewed</code>, and add a short observation. Before generation, every result remains <code>not_reviewed</code>. Do not fill those fields with expected outcomes.</p>
<p>For this shot, inspect whether the product identity and label remain readable, whether camera motion stays deliberate, and whether the audio contains unwanted speech. The illustrative prompt asks for quiet ambience; it does not guarantee it. If the clip fails, change one part of the request and record the reason.</p>
<p>A fixed logo, legal line or precise end card can be added in an editor after generation. This MixVio route does not expose a last-frame input or video references, so a planning template should not pretend those controls exist.</p>
<h2>Keep the contract dated</h2>
<p>Before reusing a saved manifest, compare its mode and limits with the current <a href="https://mixvio.ai/models/vidu-q4?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=vidu-q4-launch">Vidu Q4</a> interface. Record when you checked them. Save the accepted settings separately from your review observations so a future limit change does not silently rewrite the history of an older run.</p>
<p>The useful artifact is a small, understandable request plus an honest review record. It makes preparation repeatable without treating prompt compliance as guaranteed.</p>
<p>Disclosure: written with AI assistance and reviewed against the public MixVio interface on October 10, 2026. The manifest and scene are illustrative; no video was generated for this guide. The local validation examples were checked independently of the video model.</p>
]]></content:encoded></item><item><title><![CDATA[Treat Image-to-Video Like a Test, Not a Vibe: Logging 14 Runs Across 5 Models in a CSV]]></title><description><![CDATA[Most advice about image-to-video models is anecdotal. Someone tries a prompt, likes the clip, and writes it up. I wanted something I could rerun and argue with, so for one session on 2026-10-08 I trea]]></description><link>https://dev.mixvio.ai/image-to-video-evaluation-14-runs-csv-log</link><guid isPermaLink="true">https://dev.mixvio.ai/image-to-video-evaluation-14-runs-csv-log</guid><category><![CDATA[data analysis]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[AI]]></category><category><![CDATA[video]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[MixVio AI]]></dc:creator><pubDate>Fri, 09 Oct 2026 03:01:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ac7155f8cadc38d9e483af7/da4ba598-5f41-4f56-8260-87455804d487.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most advice about image-to-video models is anecdotal. Someone tries a prompt, likes the clip, and writes it up. I wanted something I could rerun and argue with, so for one session on 2026-10-08 I treated it like a small test: six fixed still images, five models, 14 runs, and one CSV row per run with price, wait time, output size and a verdict.</p>
<p><strong>The raw data is public:</strong> <a href="https://github.com/orvyno/mixvio-notes/tree/main/image-to-video">runs.csv and the exact prompts on GitHub</a> (CC BY 4.0). If you disagree with a verdict, every prompt and setting is there to rerun.</p>
<blockquote>
<p><strong>Disclosure.</strong> I'm Alex Wang and I work on MixVio. All 14 runs were made on MixVio, so this is not a comparison of platforms. Every image and clip referenced here is AI-generated. I edited this text with help from an AI writing assistant; the numbers come from the run log.</p>
</blockquote>
<h2>The setup</h2>
<p><strong>Fixed inputs.</strong> Six AI-generated stills of fictional people and products: a ketchup jar with fries in mid-air, a studio portrait, a back view in a flower field, a cafe creator in front of a menu board, a loose pencil sketch, and a perfume bottle with start and end frames.</p>
<p><strong>One variable at a time.</strong> Within a shot, either the model changed and the prompt stayed byte-for-byte identical, or the prompt changed and the model stayed the same. Runs 01 and 04 are the prompt pair: same jar, same model (Seedance 2.0), same price, different prompt. Runs 01 and 03 are a model pair with the same prompt. Duration stayed at each model's default (5 s, or 6 s for Veo 3.1 Fast). Resolution did not stay fixed, which comes up again under limitations.</p>
<p><strong>Serial jobs.</strong> One job at a time on one account. Run 05's wait still includes queue time, and the log says so.</p>
<h2>The run log schema</h2>
<p>Thirteen columns:</p>
<pre><code class="language-text">run, shot, source_ratio, mode, model, settings, prompt,
credits_paid, credits_list, wait_minutes_approx,
output_resolution, kept, notes
</code></pre>
<p>A few of these earned their place:</p>
<ul>
<li><p><code>credits_paid</code> and <code>credits_list</code> are separate. Four of the five models were discounted on test day, and a single price column would make the totals meaningless a week later.</p>
</li>
<li><p><code>source_ratio</code> sitting next to <code>output_resolution</code> exposes silent reframing. The 16:10 ketchup photo came back from Seedance 2.0 as 864x496 with the ratio on Auto, which is roughly 16:9.</p>
</li>
<li><p><code>prompt</code> is stored verbatim rather than as a template ID, so "same prompt" is checkable, not a claim.</p>
</li>
<li><p><code>kept</code> is the verdict, defined as "would I publish this clip as-is?"</p>
</li>
</ul>
<p>Three rows, with prompts and notes shortened:</p>
<pre><code class="language-csv">04,"Ketchup jar, scene-style prompt",16:10,start frame,Seedance 2.0,480p,"A bright studio product shot of a ketchup jar…",33,65,5,864x496,no,"Model added action nobody asked for…"
09,Cafe creator holding a donut box,16:9,start frame,MiniMax H3,5 s; 768p,"She lifts the box slightly toward the camera and says…",15,15,2,1344x768,picture only,"Stable picture and a clear menu board, but a garbled phrase was added before the line…"
12,"Perfume bottle, glass to crystal",about 9:16,start + end frame,MiniMax H3,5 s; 768p,"The clear glass slowly transforms into cut crystal…",15,15,1.5,768x1376,yes (best),Smooth change from glass to cut crystal between about 1 s and 2.5 s. …
</code></pre>
<p>What I'd change next time: <code>kept</code> ended up with five distinct values (<code>yes</code>, <code>no</code>, <code>yes (second choice)</code>, <code>yes (best)</code>, <code>picture only</code>), and <code>wait_minutes_approx</code> has one non-numeric cell (<code>7.5 (incl. queue)</code>). Fine for a human, annoying in pandas. A boolean <code>usable</code>, a <code>rank</code> and a <code>queued</code> flag would fix it.</p>
<h2>Reviewing clips without scrubbing</h2>
<p>Cuts, camera drift and text changes hide in motion, so I reviewed every clip as frame strips and contact sheets. A single-row strip at two frames per second is one command with ffmpeg's <a href="https://ffmpeg.org/ffmpeg-filters.html#tile">tile filter</a>:</p>
<pre><code class="language-bash">ffmpeg -i run11.mp4 -vf "fps=2,scale=256:-2,tile=10x1" -frames:v 1 run11-strip.png
</code></pre>
<p>Ten tiles at 2 fps covers a 5 s clip; widen the grid for longer ones.</p>
<p>For the two clips with scripted speech, I transcribed locally with <a href="https://github.com/openai/whisper">Whisper</a> and compared the text against the script. SRT output keeps timestamps, so you can see where extra words land:</p>
<pre><code class="language-bash">whisper run09.mp4 --model base --language en --output_format srt
</code></pre>
<h2>The metric: cost per usable clip</h2>
<p>Average cost per run is the number a pricing page gives you, and it's the wrong one. The session cost 386 credits for 14 runs, about 28 per run. Only 7 clips were usable, so the real figure was about <strong>55 credits per usable clip</strong>, roughly double. At list prices the same 14 runs would have cost 750 credits; MiniMax H3 was the only model that wasn't discounted.</p>
<p>The gap between 28 and 55 is the retry tax, and it's the part that prompting and model choice can move. The pencil sketch alone used 86 credits over three runs and produced nothing usable.</p>
<h2>What the data showed</h2>
<p><strong>1. Motion prompts beat scene prompts.</strong> Run 01 described what moves and ended with "Keep the jar label sharp and unchanged, background still." Run 04 described the picture like an image prompt, at the same 33 credits, and the model invented action: whole potatoes launched out of the sack, a sparkle effect appeared, the camera rose, and a surface that isn't in the photo showed up under the jar. Explicit "keep X unchanged" clauses held up well: the jar label survived all three ketchup runs, and all nine menu-board prices survived both cafe runs.</p>
<img src="https://raw.githubusercontent.com/orvyno/mixvio-notes/main/image-to-video/media/motion-vs-scene-prompt.gif" alt="Run 01 (motion prompt) next to run 04 (scene prompt), same photo and model. AI-generated." style="display:block;margin:0 auto" />

<p><em>Run 01 (motion prompt) next to run 04 (scene prompt), same photo and model. AI-generated.</em></p>
<p><strong>2. Camera instructions were the least reliable field.</strong> Seedance 2.0 pushed "slow push-in" all the way to a label close-up. Kling 3.0 ignored it. "Locked-off camera" and "camera stays still" both drifted on Seedance 2.0. Direction words failed too: "to the left over her shoulder" went to her left on Seedance 2.0 Mini and the opposite way on Veo 3.1 Fast. She faces away from the camera, so her left is also screen left; Veo simply got it wrong. Write "screen left" anyway, so the side never depends on which way the subject faces.</p>
<p><strong>3. Start and end frames went 2 for 2.</strong> Both perfume runs were usable. MiniMax H3 (15 credits, about 1.5 minutes) kept the QUIETUDE label crisp throughout the change; Seedance 2.0 at 480p got there but softer, with slight drift.</p>
<img src="https://raw.githubusercontent.com/orvyno/mixvio-notes/main/image-to-video/media/perfume-label-through-change-minimax-h3.png" alt="The label at four points in run 12 (MiniMax H3), from start frame to end frame. AI-generated." style="display:block;margin:0 auto" />

<p><em>The label at four points in run 12 (MiniMax H3), from start frame to end frame. AI-generated.</em></p>
<p><strong>4. Some inputs shouldn't be animated.</strong> The pencil sketch failed three times. Seedance 2.0 Mini redrew it as clean cel animation. Kling 3.0 hard-cut to a new angle after about 2 s and swapped the goggles for headphones. The retry with "Continuous single shot, no cuts" removed the cut, but the pencil texture was still gone.</p>
<img src="https://raw.githubusercontent.com/orvyno/mixvio-notes/main/image-to-video/media/sketch-kling-3-unwanted-cut.png" alt="Frames from run 11 (Kling 3.0) showing the unwanted cut. AI-generated." style="display:block;margin:0 auto" />

<p><em>Frames from run 11 (Kling 3.0) showing the unwanted cut. AI-generated.</em></p>
<p><strong>5. Speech needs a transcript check.</strong> Seedance 2.0 said the line word for word. MiniMax H3 added a garbled phrase before it and slurred "gummy". The picture was good enough to ship; only the transcript caught the problem.</p>
<p>On wait times, Kling 3.0, MiniMax H3 and Veo 3.1 Fast finished in about 1.5 to 2 minutes, while Seedance 2.0 took 3.5 to 7.5.</p>
<h2>Limitations</h2>
<ul>
<li><p><strong>One run per verdict.</strong> These models are stochastic and I didn't repeat any configuration, so there is no variance estimate. A "no" could be bad luck; a "yes" could be good luck.</p>
</li>
<li><p><strong>Resolution wasn't controlled.</strong> Seedance runs were at 480p, Kling 3.0 and Veo 3.1 Fast at 720p, MiniMax H3 at 768p. Judgments about soft text partly reflect that.</p>
</li>
<li><p><strong>Single platform, single day.</strong> Prices and model behavior change, and other platforms may configure the same models differently.</p>
</li>
<li><p><strong>One reviewer.</strong> <code>kept</code> is my judgment, not a blinded rating.</p>
</li>
<li><p><strong>No model-side failures</strong> happened, so the log says nothing about how failed jobs are billed.</p>
</li>
<li><p><strong>Small n.</strong> Six images and 14 runs are enough to surface failure modes, not to rank models.</p>
</li>
</ul>
<p>If you rerun a row, especially the same row several times to get at variance, I'd like to see the result.</p>
<h2>Where the runs were made</h2>
<p>Every run went through <a href="https://mixvio.ai/?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=i2v_guide_2026q4&amp;utm_content=brand_link">MixVio</a>'s <a href="https://mixvio.ai/tools/image-to-video?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=i2v_guide_2026q4&amp;utm_content=i2v_link">image to video tool</a>, which puts these five models behind one upload and shows the credit price before each run; that's where the two price columns came from. I work there, which made it the convenient harness; nothing here shows it is better or worse than alternatives. Runway's <a href="https://help.runwayml.com/hc/en-us/articles/48324313115155-Image-to-Video-Prompting-Guide">image-to-video prompting guide</a> is a good platform-neutral read on prompting with the motion a photo already implies. A longer, less technical write-up with more clips is <a href="https://medium.com/@MixVioAI/image-to-video-ai-what-14-test-clips-taught-me-about-turning-photos-into-video-d18e8297110a">on Medium</a>.</p>
<p><em>Alex Wang works on MixVio AI.</em></p>
]]></content:encoded></item><item><title><![CDATA[How to Create Readable AI Posters and Consistent Product Images with Nano Banana 2.1]]></title><description><![CDATA[AI image generation often fails in predictable ways: a poster looks polished but the headline is misspelled, a product changes shape between references, or a one-line edit redraws the entire scene.
Na]]></description><link>https://dev.mixvio.ai/how-to-create-readable-ai-posters-and-consistent-product-images-with-nano-banana-2-1</link><guid isPermaLink="true">https://dev.mixvio.ai/how-to-create-readable-ai-posters-and-consistent-product-images-with-nano-banana-2-1</guid><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[image generation]]></category><category><![CDATA[graphic design]]></category><category><![CDATA[Prompt Engineering]]></category><dc:creator><![CDATA[MixVio AI]]></dc:creator><pubDate>Thu, 08 Oct 2026 04:05:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ac7155f8cadc38d9e483af7/ac9886b7-cae3-457f-b165-aa873d3b1cd0.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AI image generation often fails in predictable ways: a poster looks polished but the headline is misspelled, a product changes shape between references, or a one-line edit redraws the entire scene.</p>
<p>Nano Banana 2.1 is useful when the job depends on readable text, multiple reference images, or a controlled edit. This guide shows a practical workflow for those cases using <a href="https://mixvio.ai/models/nano-banana-2-1?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=nano-banana-2-1-guide">Nano Banana 2.1 on MixVio</a>.</p>
<blockquote>
<p><strong>Disclosure:</strong> This guide is published by MixVio AI and describes the model as it is configured on MixVio.</p>
</blockquote>
<h2>1. Choose the right starting mode</h2>
<p>Start with <strong>text-to-image</strong> when you are creating a new scene. Start with <strong>image edit</strong> when composition, identity, packaging, lighting, or another part of an existing image must survive the change.</p>
<p>That decision changes how you should write the prompt:</p>
<ul>
<li><p>For a new image, describe the subject, composition, exact words, typography, lighting, and output format.</p>
</li>
<li><p>For an edit, state the requested change first, then list what must remain unchanged.</p>
</li>
</ul>
<p>A vague edit prompt such as “make it better” gives the model too much freedom. A useful edit prompt creates a boundary around the change.</p>
<h2>2. Build poster prompts around exact strings</h2>
<p>For posters and product graphics, put every required line of copy in quotation marks. Then describe its role in the hierarchy.</p>
<p><strong>Example prompt</strong></p>
<blockquote>
<p>Vintage concert poster, 2:3, warm sunset palette. Large headline: “SUMMER NIGHTS LIVE.” Smaller subline: “Riverside Park · Free Entry.” Bold retro typography, strong separation between text and background, silhouettes of a crowd at the bottom.</p>
</blockquote>
<p>This prompt does four useful things:</p>
<ol>
<li><p>It supplies the exact text.</p>
</li>
<li><p>It defines which line is primary and which is secondary.</p>
</li>
<li><p>It separates typography from the visual background.</p>
</li>
<li><p>It fixes the output shape before the scene is composed.</p>
</li>
</ol>
<p>Use <strong>2K or 4K</strong> when the design contains dates, labels, disclaimers, or other small text. At 1K, fine type has fewer pixels to work with and is more likely to blur.</p>
<p>After generation, inspect every character. Readable text is improved, but a final proofread is still part of the workflow.</p>
<h2>3. Give every reference image a job</h2>
<p>In image edit mode, Nano Banana 2.1 accepts <strong>1–14 JPEG, PNG, or WebP images</strong> on MixVio. The cleanest way to use several references is to assign each upload a role by position.</p>
<p><strong>Example prompt</strong></p>
<blockquote>
<p>Place the bottle from image 1 and the box from image 2 on the marble counter shown in image 3. Use soft morning window light. Keep both product labels unchanged. Preserve the bottle proportions and the box colors.</p>
</blockquote>
<p>Avoid asking the model to infer why each image was uploaded. Explicit roles reduce ambiguity:</p>
<ul>
<li><p>“image 1 is the product”</p>
</li>
<li><p>“image 2 is the packaging reference”</p>
</li>
<li><p>“image 3 supplies the environment”</p>
</li>
<li><p>“image 4 supplies the lighting direction”</p>
</li>
</ul>
<p>Google’s prompt guidance mentions up to four characters and ten objects. Treat that as composition guidance, rather than a different upload limit. The practical limit on MixVio remains 14 images.</p>
<p>For products, preserve observable details in the prompt: label spelling, cap shape, materials, proportions, and brand colors. For people, name the features that matter and check faces and hands closely after generation.</p>
<h2>4. Write precise edits as change-and-lock instructions</h2>
<p>A reliable edit prompt has two parts:</p>
<ol>
<li><p><strong>Change:</strong> the one thing the model should modify.</p>
</li>
<li><p><strong>Lock:</strong> the details that must stay the same.</p>
</li>
</ol>
<p><strong>Example</strong></p>
<blockquote>
<p>Change the background to pale sage green. Keep the model, outfit, pose, crop, facial features, and lighting unchanged.</p>
</blockquote>
<p>For a packaging edit:</p>
<blockquote>
<p>Replace only the headline on the front label with “NIGHT RENEWAL.” Keep the bottle shape, label layout, logo, colors, shadows, and camera angle unchanged.</p>
</blockquote>
<p>If a result drifts, add concrete locks instead of adding general adjectives. “Keep the camera at eye level” is more actionable than “make it consistent.”</p>
<h2>5. Match output settings to the destination</h2>
<p>Nano Banana 2.1 supports <strong>1K, 2K, and 4K</strong> output on MixVio. It also supports common landscape, square, portrait, and extreme aspect ratios.</p>
<p>A useful starting map is:</p>
<table>
<thead>
<tr>
<th>Destination</th>
<th>Suggested ratio</th>
<th>Resolution</th>
</tr>
</thead>
<tbody><tr>
<td>Website hero</td>
<td>16:9 or 21:9</td>
<td>2K or 4K</td>
</tr>
<tr>
<td>Social post</td>
<td>1:1 or 4:5</td>
<td>2K</td>
</tr>
<tr>
<td>Story or short-form cover</td>
<td>9:16</td>
<td>2K</td>
</tr>
<tr>
<td>Poster</td>
<td>2:3 or 3:4</td>
<td>2K or 4K</td>
</tr>
<tr>
<td>Panoramic strip</td>
<td>4:1 or 8:1</td>
<td>2K or 4K</td>
</tr>
</tbody></table>
<p>The <strong>Thinking</strong> setting controls how much reasoning the model applies to the request. It is not a simple quality selector. Minimal can be enough for a straightforward variation; Medium is a sensible default; High is better reserved for prompts with several constraints.</p>
<p>Optional web search is most useful when an image depends on current real-world information. Leave it off for ordinary fictional scenes, product mockups, and controlled edits.</p>
<h2>6. Validate the result before using it</h2>
<p>Before exporting, check the image at 100% zoom:</p>
<ul>
<li><p>Is every required word spelled correctly?</p>
</li>
<li><p>Did any label, logo, or product feature change?</p>
</li>
<li><p>Are faces, hands, and repeated objects consistent?</p>
</li>
<li><p>Did the edit preserve the locked details?</p>
</li>
<li><p>Is the crop appropriate for the destination?</p>
</li>
<li><p>Does small text remain readable at the final display size?</p>
</li>
</ul>
<p>When something fails, revise only the relevant part of the prompt. For example, add an exact label string or another lock constraint instead of rewriting the whole scene description.</p>
<h2>7. Estimate the run before generating</h2>
<p>MixVio shows the exact credit estimate before each run. Current Nano Banana 2.1 pricing is:</p>
<ul>
<li><p><strong>1K:</strong> 6 credits, or 7 with web search</p>
</li>
<li><p><strong>2K:</strong> 9 credits, or 10 with web search</p>
</li>
<li><p><strong>4K:</strong> 12 credits, or 13 with web search</p>
</li>
</ul>
<p>Creating and editing use the same schedule. Reserved credits are released when a run fails.</p>
<p>A sensible test sequence is to validate composition at 1K or 2K, then move to 4K for the final version when the text, references, and crop are working. Always check the estimate shown in the generator before submitting because product pricing can change.</p>
<h2>A reusable prompt template</h2>
<p>For a new image:</p>
<blockquote>
<p>[Format and aspect ratio]. [Main subject and composition]. Exact headline: “[TEXT].” Subline: “[TEXT].” [Typography]. [Lighting]. [Background]. Keep the text clearly separated from detailed visual elements.</p>
</blockquote>
<p>For an edit:</p>
<blockquote>
<p>Change [one specific element]. Keep [identity, product shape, labels, pose, crop, lighting, colors, and any other fixed details] unchanged.</p>
</blockquote>
<p>You can try these workflows on the <a href="https://mixvio.ai/models/nano-banana-2-1?utm_source=hashnode&amp;utm_medium=article&amp;utm_campaign=nano-banana-2-1-guide">Nano Banana 2.1 model page</a>. New MixVio accounts currently receive 40 welcome credits valid for seven days, with no credit card required.</p>
]]></content:encoded></item></channel></rss>