<?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: Justin Koo</title>
    <description>The latest articles on DEV Community by Justin Koo (@loveoftheai).</description>
    <link>https://dev.to/loveoftheai</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%2F4136295%2Fb13685fd-3cff-4911-9ac0-a11e24a49188.jpg</url>
      <title>DEV Community: Justin Koo</title>
      <link>https://dev.to/loveoftheai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/loveoftheai"/>
    <language>en</language>
    <item>
      <title>The Most Damaging Bug I Found This Month Wasn't in Code — It Was Between Two Documents</title>
      <dc:creator>Justin Koo</dc:creator>
      <pubDate>Mon, 21 Sep 2026 20:09:59 +0000</pubDate>
      <link>https://dev.to/loveoftheai/the-most-damaging-bug-i-found-this-month-wasnt-in-code-it-was-between-two-documents-1e9g</link>
      <guid>https://dev.to/loveoftheai/the-most-damaging-bug-i-found-this-month-wasnt-in-code-it-was-between-two-documents-1e9g</guid>
      <description>&lt;p&gt;The most damaging bug I found this month wasn't in code. It was between two documents.&lt;/p&gt;

&lt;p&gt;Last week I tore down &lt;a href="https://spout.finance" rel="noopener noreferrer"&gt;Spout Finance&lt;/a&gt; — a tokenized-equity borrowing protocol on Solana — for their public beta feedback bounty. I read the docs, stress-tested the economics, published a &lt;a href="https://loveoftheai.github.io/spout-teardown/" rel="noopener noreferrer"&gt;full teardown&lt;/a&gt;. Standard stuff.&lt;/p&gt;

&lt;p&gt;Then I did one extra pass that most people skip: I diffed the &lt;strong&gt;docs&lt;/strong&gt; against the &lt;strong&gt;Terms of Service&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six numbers, six disagreements
&lt;/h2&gt;

&lt;p&gt;The ToS (spout.finance/terms — the legally binding document) and the docs (the document users actually read) disagree on:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Docs say&lt;/th&gt;
&lt;th&gt;ToS says&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;spAsset mint/redeem fee&lt;/td&gt;
&lt;td&gt;0.20%&lt;/td&gt;
&lt;td&gt;0.25%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Withdrawal fee&lt;/td&gt;
&lt;td&gt;0.20%, lender side only&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1% on borrowing &lt;em&gt;and&lt;/em&gt; lending withdrawals&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Liquidation buffers&lt;/td&gt;
&lt;td&gt;4%–12.5%&lt;/td&gt;
&lt;td&gt;7.5%–20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Loss waterfall&lt;/td&gt;
&lt;td&gt;Insurance → Junior → Senior&lt;/td&gt;
&lt;td&gt;Insurance → Junior → &lt;strong&gt;Protocol Treasury&lt;/strong&gt; → Senior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Tranche yield targets&lt;/td&gt;
&lt;td&gt;Senior ~9%, Junior ~32% APY&lt;/td&gt;
&lt;td&gt;Senior ~8.67%, Junior ~24–27%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Options cycle cadence&lt;/td&gt;
&lt;td&gt;weekly (Friday→Friday)&lt;/td&gt;
&lt;td&gt;"weekly or biweekly depending on the asset"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus a fee the docs' fee page doesn't carry at all: an &lt;strong&gt;~7% "Idle Routing Fee"&lt;/strong&gt; on yield from idle pool capital deployed to onchain money markets.&lt;/p&gt;

&lt;p&gt;Individually, each looks like a drafting slip. Collectively they describe &lt;strong&gt;two different products&lt;/strong&gt;. And #2 is the scary one: if "borrowing withdrawals" means drawing loan proceeds, a 1% fee on a 50%-LTV draw is 2% of borrowed notional — a de-facto origination fee sitting directly under the docs' "no origination fee, ever" headline.&lt;/p&gt;

&lt;p&gt;The docs' waterfall actually &lt;em&gt;undersells&lt;/em&gt; the real structure (#4 gives Senior an extra protection layer). So this isn't a company hiding costs in fine print — it's a company whose two documents drifted apart, in both directions, on the numbers its entire risk model stands on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The method (an afternoon, mostly grep)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pull both documents as text.&lt;/strong&gt; Curl the HTML, strip tags. If the legal docs live in GitBook, request &lt;code&gt;/llms.txt&lt;/code&gt; and the &lt;code&gt;.md&lt;/code&gt; version of each page — no scraping needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract every number with its context line.&lt;/strong&gt; Percentages, basis points, time periods, fee names, waterfall orders. &lt;code&gt;grep -nE '[0-9]+(\.[0-9]+)?\s*(%|bps|basis points)' terms.md &amp;gt; terms_hits.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join on concept, not string.&lt;/strong&gt; "Withdrawal fee", "withdrawals from the protocol", "funds leaving the pool" — same concept, three phrasings. This is the step that takes actual reading; budget an hour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff the pairs.&lt;/strong&gt; For every concept with a number on both sides, do the numbers match? Every mismatch goes in a table with section citations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check for orphans.&lt;/strong&gt; Numbers that appear in ONE document only — like that 7% idle routing fee. Those are often the most interesting finds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No fuzzing, no testnet exploits, no chain forensics — just patient reading of the two documents a company publishes about itself.&lt;/p&gt;

&lt;p&gt;I put the method on camera here (70 seconds, using the public Spout material): &lt;a href="https://www.youtube.com/watch?v=4pLWgMKtVRI" rel="noopener noreferrer"&gt;youtube.com/watch?v=4pLWgMKtVRI&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why founders should care
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The document users would sue under and the document users read first are different products.&lt;/strong&gt; In a dispute, the ToS wins. In a purchase decision, the docs win. When they disagree, you've built a lawsuit generator with your marketing budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each divergence is cheap to fix &lt;em&gt;now&lt;/em&gt;.&lt;/strong&gt; It's a one-line correction — or a decision to actually align the product with one of the two claims. After launch, after a liquidation event, after a regulator asks? Expensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's not just fintech.&lt;/strong&gt; I ran the same 5-minute check on three other crypto products' public materials last week: &lt;strong&gt;all three had gaps&lt;/strong&gt; — one had a flagship marketing claim its ToS never mentions, two had fee structures in one document and absent from the other. The pattern is the norm, not the exception.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 5-minute self-check
&lt;/h2&gt;

&lt;p&gt;Open your own pricing/docs page and your own ToS in two windows. Search both for: every &lt;code&gt;%&lt;/code&gt;, every &lt;code&gt;fee&lt;/code&gt;, every &lt;code&gt;fee&lt;/code&gt;-adjacent noun, &lt;code&gt;APY&lt;/code&gt;, &lt;code&gt;guarantee&lt;/code&gt;, &lt;code&gt;freeze&lt;/code&gt;, &lt;code&gt;terminate&lt;/code&gt;, &lt;code&gt;refund&lt;/code&gt;. Ask of each hit: &lt;em&gt;does the other document agree with this number, or is it silent?&lt;/em&gt; Silence counts too — a fee that exists in the ToS but not the docs page is how "hidden fees" headlines get written.&lt;/p&gt;

&lt;p&gt;The fix, if you find drift: render both documents from &lt;strong&gt;one canonical numbers source&lt;/strong&gt;. Fees, buffers, waterfalls, yield targets — define each number once, in one file, and generate both outputs from it. Documentation drift is a build problem, not a writing problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  I do this as a service
&lt;/h2&gt;

&lt;p&gt;Documentation-vs-Terms review for fintech and crypto products: I cross-check your docs, ToS, whitepaper and marketing site for contradictions, gaps, and orphan claims — delivered as a cited table of findings plus fix recommendations within 72 hours. Documentation review, not legal advice (I'm not a lawyer; your counsel makes the calls — I make sure they see every mismatch in one place).&lt;/p&gt;

&lt;p&gt;Method + case studies: &lt;a href="https://loveoftheai.github.io/demo-videos/audit.html" rel="noopener noreferrer"&gt;loveoftheai.github.io/demo-videos/audit.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Spout teardown that started this (with the full six-contradiction table): &lt;a href="https://loveoftheai.github.io/spout-teardown/" rel="noopener noreferrer"&gt;loveoftheai.github.io/spout-teardown/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All findings cited in this post are from Spout's public documents (spout.finance/docs and spout.finance/terms) as of Sep 21, 2026, and were published in the teardown before this article. Not investment advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>documentation</category>
      <category>fintech</category>
      <category>crypto</category>
    </item>
    <item>
      <title>I cut launch trailers for free before anyone pays me — here's why that works</title>
      <dc:creator>Justin Koo</dc:creator>
      <pubDate>Mon, 21 Sep 2026 19:37:41 +0000</pubDate>
      <link>https://dev.to/loveoftheai/i-cut-launch-trailers-for-free-before-anyone-pays-me-heres-why-that-works-3g3m</link>
      <guid>https://dev.to/loveoftheai/i-cut-launch-trailers-for-free-before-anyone-pays-me-heres-why-that-works-3g3m</guid>
      <description>&lt;p&gt;You're two weeks from launch. You need a 30-60 second trailer. You DM a few video editors and get back: &lt;strong&gt;$1,500-5,000, 2-4 weeks, 50% upfront&lt;/strong&gt;, and a portfolio of videos for &lt;em&gt;other people's&lt;/em&gt; products.&lt;/p&gt;

&lt;p&gt;You have no way to know what &lt;em&gt;your&lt;/em&gt; trailer will look like until you've already paid.&lt;/p&gt;

&lt;h2&gt;
  
  
  The spec-first workflow
&lt;/h2&gt;

&lt;p&gt;I flipped the order. Before anyone pays me anything, I cut the actual trailer for the product — from nothing but its &lt;strong&gt;public site, repo, and posts&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read everything public: landing page, docs, changelog, founder posts.&lt;/li&gt;
&lt;li&gt;Write the script: hook, product, receipts, offer. Every claim sourced from public material.&lt;/li&gt;
&lt;li&gt;Cut the real thing overnight: screen captures with motion, scripted voice-over, 1080p.&lt;/li&gt;
&lt;li&gt;Deliver it labeled as a &lt;strong&gt;spec trailer&lt;/strong&gt; — a draft cut by someone who has never talked to you.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then the founder judges the real thing, not promises. If the format fits, the paid version (repo access, real footage, revisions) follows. If it doesn't, they lost nothing and keep the video.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two real examples
&lt;/h2&gt;

&lt;p&gt;Both unsolicited, both clearly labeled as spec trailers, all facts from public sources.&lt;/p&gt;

&lt;p&gt;Poko Motion — a live AI product. 40 seconds, cut in one sitting from their public site: hook card, product sections, honest end card.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/HaxdQ0_Rbgo" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;PRIMO AI — a fitness app still stuck in app review, with zero public footage. So the trailer is pure typography built from the founder's public build-in-public posts, labeled &lt;strong&gt;concept&lt;/strong&gt; because that's what it is:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Jw_L2HIsbYE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;And a launch trailer for my own product (real captures, motion terminal, replay):&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/wq9CKdPMVPA" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "unsolicited" is a feature, not a bug
&lt;/h2&gt;

&lt;p&gt;A spec trailer proves three things a portfolio can't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt; — overnight, not a two-week pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Taste&lt;/strong&gt; — the founder sees their own product framed, not a template.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research&lt;/strong&gt; — the script only works if someone actually read the public material. If the script is wrong, you learned the vendor can't read. Cheap discovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full production recipe (PIL + ffmpeg + one neural voice, ~$0 in software) is public in &lt;a href="https://dev.to/loveoftheai/40-second-launch-trailers-with-pil-ffmpeg-and-one-neural-voice-no-editor-no-cloud-2o91"&gt;my previous post&lt;/a&gt;. The secret isn't the pipeline — it's doing the reading and the sitting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you're pre-launch and have no trailer: your public site is enough for a first cut. 30-60 seconds, first cut in 48 hours, launch-week pricing while my calendar is empty.&lt;/p&gt;

&lt;p&gt;Portfolio and contact: &lt;a href="https://loveoftheai.github.io/demo-videos/" rel="noopener noreferrer"&gt;loveoftheai.github.io/demo-videos&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Spec/concept trailers are drafts made from public material only and labeled as such inside the video. If you're Poko, PRIMO, or another company above and want yours taken down or re-cut with real access — DMs open.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>marketing</category>
      <category>indiehackers</category>
      <category>productlaunch</category>
    </item>
    <item>
      <title>40-second launch trailers with PIL, ffmpeg and one neural voice — no editor, no cloud</title>
      <dc:creator>Justin Koo</dc:creator>
      <pubDate>Mon, 21 Sep 2026 18:50:58 +0000</pubDate>
      <link>https://dev.to/loveoftheai/40-second-launch-trailers-with-pil-ffmpeg-and-one-neural-voice-no-editor-no-cloud-2o91</link>
      <guid>https://dev.to/loveoftheai/40-second-launch-trailers-with-pil-ffmpeg-and-one-neural-voice-no-editor-no-cloud-2o91</guid>
      <description>&lt;p&gt;I build 40–60 second launch trailers for small AI products, and the entire pipeline is &lt;strong&gt;Pillow, ffmpeg and one neural voice&lt;/strong&gt;. No timeline editor, no cloud video service, no stock template. This post is the actual recipe — every command is real and runs on a laptop.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/HaxdQ0_Rbgo" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not an editor (or a video model)
&lt;/h2&gt;

&lt;p&gt;A 40s launch trailer is not cinematography. It's three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Typography cards&lt;/strong&gt; — big statements, one per beat&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motion&lt;/strong&gt; — screenshots or cards that move just enough to feel alive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice&lt;/strong&gt; — 5 sentences of script, read clean&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Video models generate &lt;em&gt;footage&lt;/em&gt;, but founders don't need footage of their product — they need their &lt;strong&gt;actual UI&lt;/strong&gt; shown with rhythm and a voice that lands. And a timeline editor is slow precisely because the format is so rigid: 5 beats, card → screen → card. Rigid formats should be code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1 — cards with Pillow
&lt;/h2&gt;

&lt;p&gt;One card = one &lt;code&gt;ImageDraw&lt;/code&gt; pass. Palette locked to 4 colors, one font family, accent bars instead of decoration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;

&lt;span class="n"&gt;BG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ACC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DIM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;238&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;232&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;235&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;155&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;145&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;BG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;big&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;380&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MOST FITNESS APPS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;big&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;520&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RENT THEIR BRAIN.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;big&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rectangle&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;560&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;716&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ACC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# accent bar
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole hook card is ~15 lines. The rule that matters: &lt;strong&gt;one idea per card, one accent per card&lt;/strong&gt;. If a card needs two accent bars, it's two cards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2 — motion with ffmpeg zoompan
&lt;/h2&gt;

&lt;p&gt;Ken Burns on a static card, 4K canvas downscaled to 1080 (this is the classic zoompan aliasing fix):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-loop&lt;/span&gt; 1 &lt;span class="nt"&gt;-i&lt;/span&gt; card.png &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"scale=3840:2160,zoompan=z='min(1+0.0009*on,1.12)':d=210:&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
   x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=1920x1080:fps=30"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; 7 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p card_move.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;d=210&lt;/code&gt; = 7 seconds × 30fps. The &lt;code&gt;min()&lt;/code&gt; cap is what stops the classic zoompan runaway zoom. For product screenshots the same filter gives you a slow push-in — a static screenshot suddenly reads as "demo".&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3 — one neural voice
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;edge-tts &lt;span class="nt"&gt;--voice&lt;/span&gt; en-US-ChristopherNeural &lt;span class="nt"&gt;--rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+10% &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Most fitness apps rent their intelligence from someone else's cloud."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--write-media&lt;/span&gt; seg1.mp3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deep voice, +10% pace, five segments. Script discipline: &lt;strong&gt;one sentence per segment&lt;/strong&gt;, written &lt;em&gt;after&lt;/em&gt; the cards, so the voice confirms what the eye just read instead of racing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assembly — the mux trick that keeps audio in sync
&lt;/h2&gt;

&lt;p&gt;Per segment, pad the audio to the exact video length (never rely on &lt;code&gt;-shortest&lt;/code&gt;, it clips tails):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; seg1.mp4 &lt;span class="nt"&gt;-i&lt;/span&gt; seg1.mp3 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"[1:a]apad=whole_dur=7.0,atrim=0:7.0[a]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; 0:v &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[a]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v copy seg1_mux.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then concat and web-optimize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"file 'seg1_mux.mp4'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;file 'seg2_mux.mp4'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; list.txt
ffmpeg &lt;span class="nt"&gt;-f&lt;/span&gt; concat &lt;span class="nt"&gt;-safe&lt;/span&gt; 0 &lt;span class="nt"&gt;-i&lt;/span&gt; list.txt &lt;span class="nt"&gt;-c&lt;/span&gt; copy &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart trailer.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total render time for a 40s trailer: &lt;strong&gt;under 3 minutes&lt;/strong&gt; on a small ARM box. The slow part is the script — always is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The spec-trailer move
&lt;/h2&gt;

&lt;p&gt;Here's the workflow that changed my conversion rate: when a founder posts "I need a launch video", I don't send a quote. I &lt;strong&gt;cut a spec trailer from their public site overnight&lt;/strong&gt; — their screenshots, their copy, my script and motion — clearly labeled "spec trailer" in the end card, facts only from public sources. They wake up to their product looking like a real launch.&lt;/p&gt;

&lt;p&gt;The video at the top of this post is one of those, cut in one sitting. A second example, this one from a real browser capture plus a terminal sequence rendered frame-by-frame with PIL:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/wq9CKdPMVPA" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Ethics guardrail that keeps this honest: the spec is labeled a spec, nothing is invented, and the founder owes nothing if the format doesn't fit. It's a sample with their name on it, not a finished deliverable held hostage.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this stack is the wrong tool
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You need live-action humans → camera, always&lt;/li&gt;
&lt;li&gt;You need 3D → Blender, and honestly, hire someone&lt;/li&gt;
&lt;li&gt;The product is visual-noise (games, image models) → capture matters more than typography, and capture is manual&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For everything else — dev tools, AI apps, infra, anything with a UI and a sentence of value prop — this pipeline turns "launch video" from a two-week agency project into a 48-hour turnaround.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I do these commercially — $99 launch-week rate, 48h delivery, script + voice + motion included. Samples and pricing: &lt;a href="https://loveoftheai.github.io/demo-videos" rel="noopener noreferrer"&gt;https://loveoftheai.github.io/demo-videos&lt;/a&gt; — or find me on X &lt;a href="https://x.com/wuwei2022" rel="noopener noreferrer"&gt;@wuwei2022&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>python</category>
      <category>sideprojects</category>
      <category>indiehackers</category>
    </item>
  </channel>
</rss>
