<?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: Jason Wei</title>
    <description>The latest articles on DEV Community by Jason Wei (@jason_wei_5209decf79e9a41).</description>
    <link>https://dev.to/jason_wei_5209decf79e9a41</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%2F4088465%2F393e76cb-d15a-4e20-81ac-326f8843c66e.jpg</url>
      <title>DEV Community: Jason Wei</title>
      <link>https://dev.to/jason_wei_5209decf79e9a41</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jason_wei_5209decf79e9a41"/>
    <language>en</language>
    <item>
      <title>Making 15-Second AI Rap Videos Reliable: Structured Prompts, a Credit Ledger, and Safe Refunds</title>
      <dc:creator>Jason Wei</dc:creator>
      <pubDate>Thu, 05 Nov 2026 10:01:34 +0000</pubDate>
      <link>https://dev.to/jason_wei_5209decf79e9a41/making-15-second-ai-rap-videos-reliable-structured-prompts-a-credit-ledger-and-safe-refunds-2gg8</link>
      <guid>https://dev.to/jason_wei_5209decf79e9a41/making-15-second-ai-rap-videos-reliable-structured-prompts-a-credit-ledger-and-safe-refunds-2gg8</guid>
      <description>&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%2Fstcmaqegvyp3ywol89s3.png" 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%2Fstcmaqegvyp3ywol89s3.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently launched &lt;a href="https://airapgenerators.com/" rel="noopener noreferrer"&gt;AI Rap Generators&lt;/a&gt;, a small tool that turns two adult portraits into a 15-second vertical rap video with an original verse and beat. You pick a "stage" (roast battle, birthday, pet duet, rooftop), optionally add a name or inside joke, and a few minutes later you get a 9:16 MP4.&lt;/p&gt;

&lt;p&gt;This post is a write-up of the three things that mattered most while building it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why I made the product narrow on purpose&lt;/li&gt;
&lt;li&gt;How I structure prompts so a video model behaves&lt;/li&gt;
&lt;li&gt;How I think about credits and failed renders when every generation costs real money&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code in this post is a simplified sketch of the patterns, not a copy of production code.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Narrow beats general (for a solo builder)
&lt;/h2&gt;

&lt;p&gt;General-purpose text-to-video tools are impressive, but they hand the user a long list of decisions: the prompt, the camera, the music, how to keep a face consistent across shots. Open-ended input also means an unbounded number of ways to fail.&lt;/p&gt;

&lt;p&gt;So I locked the format to &lt;strong&gt;two people, one stage, one rap&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user only does two things: upload portraits and pick a stage.&lt;/li&gt;
&lt;li&gt;I can turn almost every other decision into a per-scene template and tune for reliability.&lt;/li&gt;
&lt;li&gt;The output (15 seconds, 9:16) already matches how people share on TikTok, Reels, and Shorts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade-off is obvious, and I put it on the page instead of hiding it: videos are 15 seconds, there is no custom song upload yet, and it is built for two adult performers.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The model: Seedance 2.0
&lt;/h2&gt;

&lt;p&gt;Videos are generated with &lt;strong&gt;Seedance 2.0&lt;/strong&gt;, ByteDance's multimodal audio-video model. Two properties made it a good fit for this product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It generates video and audio together.&lt;/strong&gt; For a rap clip, the beat, the vocal, and the performance come out of one pass, so the mouth movement is produced alongside the sound instead of being glued on afterwards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It accepts reference images.&lt;/strong&gt; The user's portraits act as identity references (face, outfit, hair), and the stage supplies everything else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;According to the provider docs I used, clip length goes up to 15 seconds, which is exactly the length I wanted, and a single request can combine a text prompt with several reference images. Check the docs of whichever provider you call it through, since limits and parameters can differ.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Prompts as structured templates
&lt;/h2&gt;

&lt;p&gt;My first prompts were a paragraph of prose per scene. They produced inconsistent results, and when something went wrong I could not tell which sentence caused it.&lt;/p&gt;

&lt;p&gt;The fix was to split every scene prompt into five labelled blocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Scene]        identity + environment + camera setup
[Music]        genre, tempo, instruments, vocal requirements
[Timeline]     a second-by-second action script
[Lyrics]       the exact lines to perform
[Constraints]  hard rules and a list of things that must NOT happen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified example for a rooftop stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Scene]
Use reference image 1 for the single rapper's identity, outfit, hair,
and proportions. On the rooftop terrace, keep the performer centered in
a locked-off knee-up vertical 9:16 shot. Preserve the city skyline,
string lights, palms, lounge furniture, and reflective stone floor;
no other people enter.

[Music]
Confident melodic rap, 110 BPM: hard kick, snare, rolling hats, deep sub
bass, piano-and-bell hook, airy pads. One clear vocal rides a constant
radio-loud beat.

[Timeline]
0-3s: bounce on beat and point at camera. 3-6s: broad arm sweeps.
6-9s: lean in with a smile and head bob. 9-12s: step side to side,
hands wide. 12-15s: keep the energy high and freeze on the final hit.

[Lyrics, in English, perform exactly these]
1. ...
(six lines)

[Constraints]
Accurate lip-sync, one rapper only, no subtitles or text, no cuts, no
camera move, no extra people. Keep the performer in the lower three
quarters, knee-up, with hands below the top quarter.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this structure works for me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scene: lock identity first, then framing.&lt;/strong&gt; Saying which reference image controls identity, and fixing the shot as locked-off and knee-up, removes a lot of variation. Waist-up framing is also kinder to faces, hands, and lip sync than full-body shots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Music: translate "vibe" into parameters.&lt;/strong&gt; "A cool rap beat" does very little. A fixed tempo, a named instrument list, and "one clear vocal" give the model something concrete, and they let me plan the lyrics against a known rhythm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timeline: one action per segment.&lt;/strong&gt; Splitting 15 seconds into five 3-second beats, each with one main movement, gives the clip visual variety and a clear ending ("freeze on the final hit") so it feels finished instead of cut off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lyrics: short on purpose.&lt;/strong&gt; Six lines of roughly 7 to 10 syllables fit in 15 seconds. Longer lines are where lip sync drifts. Telling the model to perform the lines &lt;em&gt;exactly&lt;/em&gt; also reduces rewrites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Constraints: one rule per failure mode.&lt;/strong&gt; This is the block that grew the most. Typical image-to-video problems and the rule I added for each:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure mode&lt;/th&gt;
&lt;th&gt;Constraint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lip sync drifting&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Accurate lip-sync&lt;/code&gt;, shorter lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra people appearing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;one rapper only&lt;/code&gt;, &lt;code&gt;no extra people&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Garbled text on signs or captions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;no subtitles or text&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Face changing after a cut or camera move&lt;/td&gt;
&lt;td&gt;&lt;code&gt;no cuts, no camera move&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hands deformed or cropped at the top of frame&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hands below the top quarter&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Framing hidden by app UI overlays&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lower three quarters, knee-up&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The takeaway: &lt;strong&gt;a longer prompt is not a better prompt.&lt;/strong&gt; Saying clearly what should happen &lt;em&gt;and&lt;/em&gt; what must not happen is what moved quality, and having separate blocks means I can change one thing at a time and see what it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Treat generations like payments
&lt;/h2&gt;

&lt;p&gt;Every generation costs real money, and some will fail. If a user pays and gets a broken video, trust is gone. So I treat credits like a small payments system. Three rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hold credits before doing the work.&lt;/strong&gt; Never call the model first and charge afterwards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record every change in an append-only ledger&lt;/strong&gt; with an idempotency key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refund on terminal failure, at most once.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A simplified schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;credit_ledger&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;         &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt;    &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;delta&lt;/span&gt;      &lt;span class="nb"&gt;INT&lt;/span&gt;    &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;-- negative = spend, positive = grant/refund&lt;/span&gt;
  &lt;span class="n"&gt;kind&lt;/span&gt;       &lt;span class="nb"&gt;TEXT&lt;/span&gt;   &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'purchase'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'hold'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'refund'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;job_id&lt;/span&gt;     &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;idem_key&lt;/span&gt;   &lt;span class="nb"&gt;TEXT&lt;/span&gt;   &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;-- one business action, one row&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;credit_balance&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;-- last line of defense&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating a job and holding credits happen in one transaction. The conditional update makes the balance check and the deduction a single atomic statement:&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;dec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`UPDATE credit_balance SET balance = balance - $2
   WHERE user_id = $1 AND balance &amp;gt;= $2
   RETURNING balance`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;]&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="nx"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowCount&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&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="s2"&gt;INSUFFICIENT_CREDITS&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;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`INSERT INTO jobs (user_id, scene_key, status, cost_credits)
   VALUES ($1, $2, 'created', $3) RETURNING id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sceneKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`INSERT INTO credit_ledger (user_id, delta, kind, job_id, idem_key)
   VALUES ($1, $2, 'hold', $3, $4)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`hold:job:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&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;id&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="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The refund path has two guards. First, the job status moves to &lt;code&gt;failed&lt;/code&gt; only from a non-terminal state (a compare-and-set). Second, the ledger insert uses &lt;code&gt;ON CONFLICT DO NOTHING&lt;/code&gt;, and the balance is only increased if a row was actually inserted:&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;upd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`UPDATE jobs SET status = 'failed', error_code = $2, finished_at = now()
   WHERE id = $1 AND status NOT IN ('succeeded','failed')
   RETURNING user_id, cost_credits`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errorCode&lt;/span&gt;&lt;span class="p"&gt;]&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="nx"&gt;upd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowCount&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&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;// already in a terminal state&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`INSERT INTO credit_ledger (user_id, delta, kind, job_id, idem_key)
   VALUES ($1, $2, 'refund', $3, $4)
   ON CONFLICT (idem_key) DO NOTHING RETURNING id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;upd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&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;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&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;cost_credits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`refund:job:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobId&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="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="nx"&gt;ins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowCount&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`UPDATE credit_balance SET balance = balance + $2 WHERE user_id = $1`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;upd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&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;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&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;cost_credits&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;Duplicate callbacks, worker restarts, and manual retries can all hit this code path, and the user still gets exactly one refund. A nightly query comparing each balance against the sum of its ledger rows catches anything that slips through.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retries are not new jobs
&lt;/h3&gt;

&lt;p&gt;Failures fall into two groups. Timeouts, upstream 5xx errors, and rate limits are worth retrying with backoff. Rejected inputs and moderation failures are not. Retries reuse the original hold, so a user is never charged twice for one video.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Consent and privacy are product features
&lt;/h2&gt;

&lt;p&gt;The tool works with real faces, so the rules are part of the product, not a footnote:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everyone shown must be you or an adult who agreed to take part.&lt;/li&gt;
&lt;li&gt;Images of minors and public figures are not accepted.&lt;/li&gt;
&lt;li&gt;Sexual, hateful, deceptive, or harassing content is not allowed.&lt;/li&gt;
&lt;li&gt;Outputs are labeled as AI-generated.&lt;/li&gt;
&lt;li&gt;Uploaded photos are deleted after seven days.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would rather lose some "make a video of a celebrity" traffic than build something that is awkward to defend. Retention promises like "deleted after seven days" are also easy to forget operationally, so I treat them as a system mechanism (storage lifecycle rules plus a database flag), not as a manual chore.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it can't do yet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Videos are 15 seconds.&lt;/li&gt;
&lt;li&gt;No custom song upload.&lt;/li&gt;
&lt;li&gt;Two adult performers only.&lt;/li&gt;
&lt;li&gt;It is AI, so likeness to the photos varies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Credits are 10 per video, packs start at $6.99, there is no subscription, and failed renders are refunded automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd like feedback on
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Which stage or scene would you want next?&lt;/li&gt;
&lt;li&gt;If you have built on a video model: what other failure modes should I be writing constraints for?&lt;/li&gt;
&lt;li&gt;Does the credit pricing feel fair?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can browse every stage for free at &lt;a href="https://airapgenerators.com/" rel="noopener noreferrer"&gt;airapgenerators.com&lt;/a&gt;. If you try it, I would love to hear where it breaks.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aigc</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Write Prompts for Midjourney, DALL-E, Stable Diffusion, and Flux (They're Not the Same)</title>
      <dc:creator>Jason Wei</dc:creator>
      <pubDate>Fri, 11 Sep 2026 07:51:00 +0000</pubDate>
      <link>https://dev.to/jason_wei_5209decf79e9a41/how-to-write-prompts-for-midjourney-dall-e-stable-diffusion-and-flux-theyre-not-the-same-3e6</link>
      <guid>https://dev.to/jason_wei_5209decf79e9a41/how-to-write-prompts-for-midjourney-dall-e-stable-diffusion-and-flux-theyre-not-the-same-3e6</guid>
      <description>&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%2Fy4ej295cd6x7b5rs0cr3.png" 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%2Fy4ej295cd6x7b5rs0cr3.png" alt=" " width="768" height="1376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the same prompt into four different image models and you'll get four very different results. That's not random — each of these models was trained differently, optimizes for different things, and "reads" prompts differently. Here's a breakdown of the major players in 2026 and how to actually write for each one.&lt;/p&gt;

&lt;p&gt;Midjourney — aesthetics-first, less is often more&lt;/p&gt;

&lt;p&gt;Midjourney (currently on V7/V8) is built around one goal: the best-looking result from the least input. It has strong built-in aesthetic defaults, so short, simple prompts often outperform long, over-specified ones.&lt;/p&gt;

&lt;p&gt;Prompting tips:&lt;/p&gt;

&lt;p&gt;Subject + style + mood is usually enough — you don't need to over-describe&lt;br&gt;
Parameters (aspect ratio, stylization strength) do more work than extra adjectives&lt;br&gt;
Weak spot: text rendering and fine-grained control. If you need accurate in-image text, look elsewhere&lt;br&gt;
DALL-E / GPT Image — strongest instruction-following&lt;/p&gt;

&lt;p&gt;The model behind ChatGPT's image generation (now branded GPT Image) prioritizes following your instructions precisely over free creative interpretation. The more specific and structured your description, the closer the output matches your intent.&lt;/p&gt;

&lt;p&gt;Prompting tips:&lt;/p&gt;

&lt;p&gt;Longer, more explicit prompts work well here — object counts, spatial relationships, exact text content&lt;br&gt;
Strong content-safety filtering makes it a safer default for brand/enterprise use&lt;br&gt;
Won't necessarily produce the most "striking" image, but it's the most controllable&lt;br&gt;
Stable Diffusion — maximum freedom, maximum prompt dependency&lt;/p&gt;

&lt;p&gt;Stable Diffusion (now at 3.5/4) is open-weight, locally deployable, and pairs with tools like ControlNet for precise control. The tradeoff: it has weaker built-in aesthetic defaults than Midjourney, so output quality depends much more heavily on how well you write the prompt.&lt;/p&gt;

&lt;p&gt;Prompting tips:&lt;/p&gt;

&lt;p&gt;Be explicit about style keywords ("cinematic lighting," "oil painting," "35mm film") rather than relying on the model to infer style&lt;br&gt;
Negative prompts matter a lot here — excluding unwanted elements is often more effective than adding more positive description&lt;br&gt;
Best fit for teams that need batch generation, LoRA fine-tuning, or zero marginal cost via self-hosting&lt;br&gt;
Flux — the open-weight model built for prompt accuracy&lt;/p&gt;

&lt;p&gt;Flux (from Black Forest Labs) sits between Midjourney's "just works" simplicity and Stable Diffusion's full control — it's known for high prompt adherence, multi-reference input support, and strong realism in lighting and materials.&lt;/p&gt;

&lt;p&gt;Prompting tips:&lt;/p&gt;

&lt;p&gt;High adherence means detail actually matters — it's worth being specific&lt;br&gt;
Good fit for maintaining consistent characters or brand visuals across a series of generations via LoRA&lt;br&gt;
Ideogram — the specialist for in-image text&lt;/p&gt;

&lt;p&gt;If your use case involves generating readable, accurate text inside the image itself (posters, logos, marketing assets), Ideogram is the clear specialist — most general-purpose models still struggle here.&lt;/p&gt;

&lt;p&gt;What's actually universal across all of them&lt;/p&gt;

&lt;p&gt;Despite the differences, a few rules hold everywhere:&lt;/p&gt;

&lt;p&gt;Structured beats prose — subject, style, lighting, composition, mood as short comma-separated attributes usually outperforms a paragraph of description&lt;br&gt;
Specific beats vague — "studio lighting, 35mm film" beats "nice lighting"&lt;br&gt;
Negative prompts matter, especially on more controllable models like Stable Diffusion&lt;br&gt;
A prompt tuned for one model rarely transfers cleanly to another — you generally need to adapt it, not copy-paste it&lt;br&gt;
A practical starting point&lt;/p&gt;

&lt;p&gt;If you're starting from a reference image rather than a blank prompt box, it helps to extract a baseline structured prompt first, then adapt it per target model — adding style tags and negative prompts for Stable Diffusion, or expanding detail for DALL-E.&lt;/p&gt;

&lt;p&gt;I've been using &lt;a href="https://findimageprompt.com/" rel="noopener noreferrer"&gt;Findimageprompt.com&lt;/a&gt; for the extraction step (Image to Prompt) and then hand-tuning from there — it's faster than writing from scratch every time. It also has Text to Prompt (expand a rough idea into a detailed prompt), an AI Image Describer (check whether a result matches intent), and a built-in AI Image Generator, so the extract → adjust → generate → verify loop can happen in one place.&lt;/p&gt;

&lt;p&gt;Bottom line&lt;/p&gt;

&lt;p&gt;There's no universally "best" model — Midjourney for aesthetics with minimal effort, DALL-E/GPT Image for precise control and enterprise safety, Stable Diffusion for full freedom and local deployment, Flux for high adherence and consistency, Ideogram for text-heavy designs. The prompting principles carry over, but the specifics don't — write for the model you're actually using.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Built a Tool to Reverse-Engineer AI Prompts From Images — Here's What I Learned</title>
      <dc:creator>Jason Wei</dc:creator>
      <pubDate>Fri, 21 Aug 2026 14:40:21 +0000</pubDate>
      <link>https://dev.to/jason_wei_5209decf79e9a41/i-built-a-tool-to-reverse-engineer-ai-prompts-from-images-heres-what-i-learned-1hnc</link>
      <guid>https://dev.to/jason_wei_5209decf79e9a41/i-built-a-tool-to-reverse-engineer-ai-prompts-from-images-heres-what-i-learned-1hnc</guid>
      <description>&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%2Fem59rrraulevcb7yex8m.png" 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%2Fem59rrraulevcb7yex8m.png" alt=" " width="768" height="1376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few months back I kept running into the same annoying loop: I'd see an AI-generated image with a style or lighting I loved, and have zero idea what prompt would get me something similar. Object-tagging tools weren't the answer — they'd give me "person, outdoor, tree" and completely miss the stuff that actually makes an image distinctive: lighting, color grading, art medium, mood.&lt;/p&gt;

&lt;p&gt;So I built Findimageprompt.com to solve that specific problem. Here's a quick rundown of what it does and some of the things I learned building it.&lt;/p&gt;

&lt;p&gt;The core insight: captioning ≠ prompting&lt;/p&gt;

&lt;p&gt;Early on I assumed a good image captioning model would be enough. It's not. Captioning answers "what is this image?" Prompting needs to answer "what would I type to generate something like this?" Those are different tasks, and the second one requires pulling out attributes a normal caption throws away — lighting direction, composition, camera angle, artistic style.&lt;/p&gt;

&lt;p&gt;Output format matters more than I expected&lt;/p&gt;

&lt;p&gt;This was the biggest surprise: a technically accurate, well-written description is often a worse prompt than a rougher, structured one. Generators like Midjourney and Stable Diffusion respond much better to short, comma-separated attributes than to flowing prose. So a meaningful chunk of the work isn't just generating a description — it's reformatting that description into the attribute-list shape these models are actually trained on.&lt;/p&gt;

&lt;p&gt;Four tools, one shared engine&lt;/p&gt;

&lt;p&gt;Rather than shipping a single "image to prompt" feature, I built it as a loop:&lt;/p&gt;

&lt;p&gt;Image to Prompt – image in, structured prompt out&lt;br&gt;
Text to Prompt – rough idea in, detailed prompt out&lt;br&gt;
AI Image Describer – plain-language description, for checking generation results against intent&lt;br&gt;
AI Image Generator – generate directly from any prompt without leaving the site&lt;/p&gt;

&lt;p&gt;The reason for building it this way: reverse-engineering a prompt is rarely a one-shot task. You pull a prompt, generate, compare, and adjust. Having all four tools share the same underlying engine made that iteration loop actually usable instead of copy-pasting between four separate sites.&lt;/p&gt;

&lt;p&gt;Keeping it frictionless&lt;/p&gt;

&lt;p&gt;No signup, no API keys, runs in the browser. For a tool people are going to try on a whim with one image, every bit of setup friction is a reason to bounce before they see if it's useful.&lt;/p&gt;

&lt;p&gt;Where it's at now&lt;/p&gt;

&lt;p&gt;It's live and free to use. I'm currently working through pricing/payment setup and a broader promo push (Product Hunt launch coming up), so feedback on prompt quality — especially cases where the output clearly missed the mark — is genuinely useful to me right now.&lt;/p&gt;

&lt;p&gt;If you work with AI image generation and want to try reverse-engineering a prompt from a reference image, it's here: &lt;a href="https://findimageprompt.com/" rel="noopener noreferrer"&gt;https://findimageprompt.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about the multimodal/prompt-formatting side of it in the comments.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
