<?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: kartikjindal</title>
    <description>The latest articles on DEV Community by kartikjindal (@kartikjindal).</description>
    <link>https://dev.to/kartikjindal</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%2F4208886%2F3d4c3560-9f0e-4d06-b7d9-66c150eb7d30.png</url>
      <title>DEV Community: kartikjindal</title>
      <link>https://dev.to/kartikjindal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kartikjindal"/>
    <language>en</language>
    <item>
      <title>Real-looking people in a product video, for $0, on a MacBook: I tested 5 methods</title>
      <dc:creator>kartikjindal</dc:creator>
      <pubDate>Mon, 26 Oct 2026 13:56:27 +0000</pubDate>
      <link>https://dev.to/kartikjindal/real-looking-people-in-a-product-video-for-0-on-a-macbook-i-tested-5-methods-121o</link>
      <guid>https://dev.to/kartikjindal/real-looking-people-in-a-product-video-for-0-on-a-macbook-i-tested-5-methods-121o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I made the &lt;strong&gt;same 30-second story five ways&lt;/strong&gt; to find the best &lt;em&gt;free, local&lt;/em&gt; way to put real-looking people in a product video: free stock, AI images (SDXL), AI video (LTX-Video), Blender 3D and a lip-synced AI narrator.&lt;/li&gt;
&lt;li&gt;Everything ran on &lt;strong&gt;one MacBook Pro (M1 Max, 64 GB), cost $0&lt;/strong&gt;, and used about 28 GB of models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The winner is AI images with SDXL:&lt;/strong&gt; realistic, consistent characters at &lt;strong&gt;~2 min per shot&lt;/strong&gt; on the Mac GPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI video (LTX-Video)&lt;/strong&gt; is the only method where people truly move, but it took &lt;strong&gt;11–26 min per 5-second clip&lt;/strong&gt;, and crowds and big motion melt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Surprise:&lt;/strong&gt; the video model &lt;em&gt;generates&lt;/em&gt; a clip in ~30 s. The other 95% of the time is decoding plus memory swap.&lt;/li&gt;
&lt;li&gt;Everything is open: &lt;a href="https://kartikjindal.github.io/local-character-video-5-methods-benchmark/" rel="noopener noreferrer"&gt;live side-by-side dashboard&lt;/a&gt; · &lt;a href="https://github.com/kartikjindal/local-character-video-5-methods-benchmark" rel="noopener noreferrer"&gt;repo with inputs, outputs, scripts and logs&lt;/a&gt; · &lt;a href="https://github.com/kartikjindal/local-character-video-5-methods-benchmark/blob/main/REPORT.md" rel="noopener noreferrer"&gt;full report&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuq9tb4nd6hkno0tcn6bl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuq9tb4nd6hkno0tcn6bl.jpg" alt="Same story, five ways. Rows: free stock, AI images, AI video, Blender 3D, talking narrator" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Same 30-second story, five ways. Rows top to bottom: free stock · AI images (SDXL) · AI video (LTX-Video) · Blender 3D · talking narrator (SadTalker). Columns are the five shots.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;▶ &lt;strong&gt;Watch all five videos in sync:&lt;/strong&gt; &lt;a href="https://kartikjindal.github.io/local-character-video-5-methods-benchmark/" rel="noopener noreferrer"&gt;side-by-side dashboard&lt;/a&gt; · each video embedded on the &lt;a href="https://kartikjindal.github.io/local-character-video-5-methods-benchmark/blog/" rel="noopener noreferrer"&gt;original post&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;You make product demos, explainers or launch videos with code, an editor or a tool like Remotion or HyperFrames. Your app screens look great, but there are &lt;strong&gt;no humans&lt;/strong&gt;: no customer, no user, no story. You want people in the video, you don't want to pay for footage or an AI video subscription, and you have a decent Mac.&lt;/p&gt;

&lt;p&gt;By the end you'll know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which of the five free methods to use for which shot;&lt;/li&gt;
&lt;li&gt;what each one really costs in time on Apple Silicon, and why;&lt;/li&gt;
&lt;li&gt;the exact settings, prompts and patches that made them work, including the errors you'll hit and their fixes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;My explainer videos had a recurring hole. The product screens were sharp, but every "real world" moment (a candidate arriving, a phone buzzing, an interview) was a silhouette or an icon. Viewers connect with &lt;strong&gt;people&lt;/strong&gt;. The usual answers are paid stock footage, a paid AI video tool, or filming. I wanted to know how far &lt;strong&gt;free and local&lt;/strong&gt; gets you in 2026.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I tested (methodology)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One story, five methods; only the people change.&lt;/strong&gt; The story: Aarav gets a QR call letter for a walk-in interview → scans in at reception → waits in a lounge until his phone buzzes → interviews → his record is handed off to the company's HR system. That's 5 shots, 30.4 seconds in total.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fairness controls.&lt;/strong&gt; Every version shares:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the same storyboard and shot timings (6.5 / 5.3 / 6.5 / 5.7 / 6.4 s);&lt;/li&gt;
&lt;li&gt;the same narration (Kokoro TTS, voice &lt;code&gt;am_michael&lt;/code&gt;, generated locally);&lt;/li&gt;
&lt;li&gt;the same app overlay cards and the same music bed;&lt;/li&gt;
&lt;li&gt;the same assembly template, rendered with HyperFrames (HTML + GSAP → MP4) at 1080p.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hardware.&lt;/strong&gt; MacBook Pro, &lt;strong&gt;Apple M1 Max&lt;/strong&gt;: 10-core CPU (8 performance + 2 efficiency), &lt;strong&gt;32-core GPU&lt;/strong&gt;, &lt;strong&gt;64 GB&lt;/strong&gt; of unified memory, macOS 26.5. There's no NVIDIA GPU, so "GPU" means the Apple GPU through PyTorch's &lt;code&gt;mps&lt;/code&gt; backend or Blender's Metal renderer. I left my normal apps open (an IDE, a Linux VM, an editor), which turned out to matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I measured.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wall-clock time per step, from logs.&lt;/li&gt;
&lt;li&gt;Which device each step ran on.&lt;/li&gt;
&lt;li&gt;Memory and swap.&lt;/li&gt;
&lt;li&gt;Seven quality criteria scored 1–5: realism, character consistency, motion, story fit, speed, reliability and licence clarity.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You need&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Apple Silicon Mac, ideally 32 GB+ of RAM&lt;/td&gt;
&lt;td&gt;The AI video model needs ~25 GB of working memory in fp32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~30 GB of free disk&lt;/td&gt;
&lt;td&gt;Model weights&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python 3.10 &lt;strong&gt;and&lt;/strong&gt; 3.11, &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;uv&lt;/a&gt;, ffmpeg&lt;/td&gt;
&lt;td&gt;Two separate environments; SadTalker needs old pinned libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blender 4.5&lt;/td&gt;
&lt;td&gt;Method 4. Use the blender.org DMG; the Homebrew cask asks for sudo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node 18+&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npx hyperframes&lt;/code&gt; for the final assembly (any editor works)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The models and their download sizes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Used for&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Licence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://huggingface.co/SG161222/RealVisXL_V4.0" rel="noopener noreferrer"&gt;RealVisXL V4&lt;/a&gt; (SDXL, fp16) + &lt;a href="https://huggingface.co/madebyollin/sdxl-vae-fp16-fix" rel="noopener noreferrer"&gt;fp16-fix VAE&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;AI images&lt;/td&gt;
&lt;td&gt;7.3 GB&lt;/td&gt;
&lt;td&gt;Open RAIL++-M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://huggingface.co/Lightricks/LTX-Video-0.9.5" rel="noopener noreferrer"&gt;LTX-Video 0.9.5&lt;/a&gt; transformer + VAE&lt;/td&gt;
&lt;td&gt;AI video&lt;/td&gt;
&lt;td&gt;6.3 GB&lt;/td&gt;
&lt;td&gt;Open RAIL-M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://huggingface.co/city96/t5-v1_1-xxl-encoder-bf16" rel="noopener noreferrer"&gt;T5-XXL encoder, bf16 copy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;LTX text encoder&lt;/td&gt;
&lt;td&gt;9.5 GB (instead of 19 GB)&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://github.com/OpenTalker/SadTalker" rel="noopener noreferrer"&gt;SadTalker&lt;/a&gt; + GFPGAN weights&lt;/td&gt;
&lt;td&gt;Narrator&lt;/td&gt;
&lt;td&gt;1.7 GB&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://quaternius.com" rel="noopener noreferrer"&gt;Quaternius&lt;/a&gt; animated characters&lt;/td&gt;
&lt;td&gt;Blender&lt;/td&gt;
&lt;td&gt;small&lt;/td&gt;
&lt;td&gt;CC0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Method 1: free stock photos
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it works.&lt;/strong&gt; Search Wikimedia Commons (its API needs no key) and Openverse for each shot. Filter licences to CC0, CC BY and CC BY-SA (never NC or ND), crop to 16:9, and add a slow camera push.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://commons.wikimedia.org/w/api.php?action=query&amp;amp;list=search&amp;amp;srnamespace=6&amp;amp;srsearch=filetype:video%20job%20interview&amp;amp;format=json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result.&lt;/strong&gt; No usable free &lt;em&gt;video&lt;/em&gt; exists for offices, receptions or interviews: the hits are under 720p or are news footage. So all five shots became &lt;strong&gt;photos&lt;/strong&gt;. Each shot shows a different person, most aren't Indian, the "lounge" is a bus terminal, and in shot 1 we only see the back of a head.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time:&lt;/strong&gt; ~22 min of searching and reviewing. &lt;strong&gt;GPU:&lt;/strong&gt; none.&lt;br&gt;
&lt;strong&gt;Verdict:&lt;/strong&gt; real, and legally clean with attribution, but it can't tell &lt;em&gt;your&lt;/em&gt; story with &lt;em&gt;your&lt;/em&gt; character. Pexels has much better free video, but needs an API key.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 2: AI images with SDXL (the winner)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it works.&lt;/strong&gt; Write one fixed character description and reuse it in every shot prompt. Fix the seeds, generate 2 options per shot, and pick the best.&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="n"&gt;AARAV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aarav, a 26 year old Indian man, short neat black hair, light stubble, navy blazer over a white shirt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;STYLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;photorealistic, candid documentary photo, 35mm lens, natural light, shallow depth of field, ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;vae&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoencoderKL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;madebyollin/sdxl-vae-fp16-fix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch_dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StableDiffusionXLPipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SG161222/RealVisXL_V4.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vae&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vae&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                 &lt;span class="n"&gt;torch_dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fp16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DPMSolverMultistepScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use_karras_sigmas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;AARAV&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, sitting on a sofa ... looking down at his smartphone, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;STYLE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;negative_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;NEG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1344&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_inference_steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;guidance_scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;5.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Generator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;manual_seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;images&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;fp16-fix VAE&lt;/strong&gt; matters: the stock SDXL VAE in fp16 on Apple GPUs tends to decode black or NaN images.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuj5t74b2tyrbglo0jvul.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuj5t74b2tyrbglo0jvul.jpg" alt="All 10 SDXL candidates, two per shot" width="800" height="1144"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;All 10 candidates, two per shot. Choosing took seconds.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result.&lt;/strong&gt; These look like candid office photos, and Aarav is recognisably the same person in most shots. &lt;strong&gt;Weaknesses:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the busy interview scene his identity drifted (glasses and a beard appeared).&lt;/li&gt;
&lt;li&gt;Prompts are followed loosely: no receptionist actually scans his phone.&lt;/li&gt;
&lt;li&gt;The images are stills, so motion comes only from the camera.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Time:&lt;/strong&gt; &lt;strong&gt;108–131 s per 1344×768 image&lt;/strong&gt; (average 119 s), so 16 images took ~28 min. &lt;strong&gt;GPU:&lt;/strong&gt; yes (MPS, fp16).&lt;br&gt;
&lt;strong&gt;Verdict:&lt;/strong&gt; ⭐ the best realism per minute.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 3: AI video with LTX-Video (alive, but fragile)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it works.&lt;/strong&gt; Animate the method 2 stills with LTX-Video 0.9.5 image-to-video, prompting only the &lt;em&gt;motion&lt;/em&gt;. Two memory tricks are essential on a Mac:&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="n"&gt;pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LTXImageToVideoPipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lightricks/LTX-Video-0.9.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                               &lt;span class="n"&gt;text_encoder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch_dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1) encode every prompt once on the CPU, then drop the 19 GB text encoder before generating
&lt;/span&gt;&lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text_encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;T5EncoderModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city96/t5-v1_1-xxl-encoder-bf16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch_dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;emb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MOTION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;NEG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;do_classifier_free_guidance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;shots&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text_encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vae&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable_tiling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;# 2) tiled decoding
&lt;/span&gt;
&lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ne&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;emb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;frames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;still&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt_embeds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt_attention_mask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;negative_prompt_embeds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ne&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;negative_prompt_attention_mask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;nm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;448&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_frames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_inference_steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;guidance_scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Generator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;manual_seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;frames&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result.&lt;/strong&gt; For &lt;strong&gt;one person with small motion it's genuinely convincing:&lt;/strong&gt; Aarav smiles at his phone, and in the interview he talks and gestures while the panel listens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffb794485sgerawewrf2i.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffb794485sgerawewrf2i.jpg" alt="Interview clip: natural talking and gesturing" width="800" height="156"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The interview clip at 0 s, 2.5 s and 5 s: natural talking and gesturing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Crowds and big motion break it.&lt;/strong&gt; The busy reception melts into a blur after ~3 s, and walking out of the building warps his body.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8r3i9we64s0fid9r5dn9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8r3i9we64s0fid9r5dn9.jpg" alt="Reception clip: fine at 0s, morphing at 2.5s, melted at 5s" width="800" height="156"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The reception clip at 0 s, 2.5 s and 5 s: the crowd melts.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I rescued those two clips by keeping the clean first ~3 s and playing them forward then backward (&lt;code&gt;ffmpeg ... trim,split,reverse,concat&lt;/code&gt;). The raw clips are in the repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quick test (49 frames, 20 steps): 278 s.&lt;/li&gt;
&lt;li&gt;Full clips (121 frames ≈ 5 s, 30 steps): &lt;strong&gt;811, 681, 1,568, 1,381 and 804 s&lt;/strong&gt;, ~87 min for all five.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GPU:&lt;/strong&gt; yes (MPS, &lt;strong&gt;fp32&lt;/strong&gt;; see the pitfalls).&lt;br&gt;
&lt;strong&gt;Verdict:&lt;/strong&gt; use it for 2–3 hero moments, not a whole film.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 4: Blender 3D with free rigged characters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it works.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download CC0 rigged and animated characters (Quaternius, via Poly Pizza; no login).&lt;/li&gt;
&lt;li&gt;Recolour them to match the cast and build sets from primitives.&lt;/li&gt;
&lt;li&gt;Script every shot in Blender Python and render headless with EEVEE on the GPU:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/Applications/Blender.app/Contents/MacOS/Blender &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt; shots.py &lt;span class="nt"&gt;--&lt;/span&gt; s1 full 1920 1080 24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result.&lt;/strong&gt; &lt;strong&gt;Every shot does exactly what the storyboard asks.&lt;/strong&gt; He holds the phone to the scanner, the phone buzzes, he walks out, and nobody is stuck in a T-pose. But it looks like a video game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;faces have no expressions and there's no lip-sync;&lt;/li&gt;
&lt;li&gt;the same three base models are reused for everyone;&lt;/li&gt;
&lt;li&gt;the sets are untextured.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Realistic free 3D humans are hard to get: Mixamo needs an Adobe login, and MetaHuman and Character Creator aren't free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time:&lt;/strong&gt; ~35 min in total, of which &lt;strong&gt;14 min&lt;/strong&gt; was rendering 878 frames at 1080p. &lt;strong&gt;GPU:&lt;/strong&gt; yes (Metal, EEVEE).&lt;br&gt;
&lt;strong&gt;Verdict:&lt;/strong&gt; total control and consistency, but no realism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 5: a talking AI narrator with SadTalker
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How it works.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a fictional presenter face with SDXL (method 2).&lt;/li&gt;
&lt;li&gt;Lip-sync it to the full narration with SadTalker.&lt;/li&gt;
&lt;li&gt;Clean up the face with GFPGAN.&lt;/li&gt;
&lt;li&gt;Show it as a corner bubble.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python inference.py &lt;span class="nt"&gt;--driven_audio&lt;/span&gt; narration.wav &lt;span class="nt"&gt;--source_image&lt;/span&gt; presenter.png &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--still&lt;/span&gt; &lt;span class="nt"&gt;--preprocess&lt;/span&gt; crop &lt;span class="nt"&gt;--enhancer&lt;/span&gt; gfpgan &lt;span class="nt"&gt;--size&lt;/span&gt; 256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result.&lt;/strong&gt; The lip-sync is frame-accurate across the full 30 s, but only the head moves, and at 512×512 it only works small. &lt;strong&gt;Match the face to the voice:&lt;/strong&gt; my first presenter was a woman against a male voice, and it looked wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the CPU: 24 s per step, projected at &lt;strong&gt;~2.5 h&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the GPU after a patch: &lt;strong&gt;11.7 min&lt;/strong&gt;, about 13× faster.&lt;/li&gt;
&lt;li&gt;The GFPGAN clean-up then took &lt;strong&gt;39 min on the CPU&lt;/strong&gt;, because GFPGAN has no Apple GPU path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GPU:&lt;/strong&gt; partly.&lt;br&gt;
&lt;strong&gt;Verdict:&lt;/strong&gt; a nice guide or presenter; it can't carry the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&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;1 · Stock&lt;/th&gt;
&lt;th&gt;2 · AI images&lt;/th&gt;
&lt;th&gt;3 · AI video&lt;/th&gt;
&lt;th&gt;4 · Blender&lt;/th&gt;
&lt;th&gt;5 · Narrator&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Realism&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same character throughout&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;People actually move&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matches the storyboard&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliability&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Licence clarity&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Average&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time for 30 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~22 min&lt;/td&gt;
&lt;td&gt;~28 min&lt;/td&gt;
&lt;td&gt;~87 min&lt;/td&gt;
&lt;td&gt;~35 min&lt;/td&gt;
&lt;td&gt;~52 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ran on&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;GPU + CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Blender ties SDXL on the average only because consistency and licence clarity weigh as much as realism. &lt;strong&gt;If "real-looking people" is the goal, SDXL wins clearly.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the time actually goes on a Mac
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Device&lt;/th&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SDXL image, 1344×768, 30 steps&lt;/td&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;108–131 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LTX-Video: &lt;strong&gt;generating&lt;/strong&gt; a 5 s clip&lt;/td&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;27–29 s&lt;/strong&gt; (~1 step/s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LTX-Video: &lt;strong&gt;decoding + swap&lt;/strong&gt; for that clip&lt;/td&gt;
&lt;td&gt;GPU + swap&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;650–1,540 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blender, 878 frames at 1080p&lt;/td&gt;
&lt;td&gt;GPU (Metal)&lt;/td&gt;
&lt;td&gt;14 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SadTalker face renderer, 380 steps&lt;/td&gt;
&lt;td&gt;CPU → GPU&lt;/td&gt;
&lt;td&gt;24.1 → &lt;strong&gt;1.85&lt;/strong&gt; s per step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GFPGAN clean-up, 760 frames&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;~3.1 s per frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assembling one 30 s video&lt;/td&gt;
&lt;td&gt;CPU + headless Chrome&lt;/td&gt;
&lt;td&gt;63–93 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The biggest surprise: &lt;strong&gt;the AI video model isn't slow at generating.&lt;/strong&gt; 95% of each clip's time went to decoding the frames while macOS swapped &lt;strong&gt;up to 32 GB&lt;/strong&gt; to disk. The T5 encoder alone is 19 GB in fp32, and my IDE and VM were open. Unloading T5 after encoding cut the swap to 17.5 GB. &lt;strong&gt;Closing other apps is the single biggest speed-up.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls and fixes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Input type (float) and bias type (c10::BFloat16) should be the same&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;M1 has no native bf16&lt;/td&gt;
&lt;td&gt;Load LTX-Video in &lt;strong&gt;fp32&lt;/strong&gt;; it costs ~2× the memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Expected a 'mps' device type for generator but found 'cpu'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A CPU random generator fed to an MPS pipeline&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Generator("mps")&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SadTalker: &lt;code&gt;Conv3D is not supported on MPS&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Its pinned PyTorch 2.1&lt;/td&gt;
&lt;td&gt;Upgrade to &lt;strong&gt;PyTorch 2.5.1&lt;/strong&gt; and apply &lt;a href="https://github.com/kartikjindal/local-character-video-5-methods-benchmark/blob/main/patches/sadtalker-apple-gpu.patch" rel="noopener noreferrer"&gt;this 3-file patch&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SadTalker: &lt;code&gt;invalid type: 'torch.mps.FloatTensor'&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;CUDA-style &lt;code&gt;.type()&lt;/code&gt; calls&lt;/td&gt;
&lt;td&gt;Covered by the same patch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;No module named 'pkg_resources'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;New setuptools&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uv pip install "setuptools&amp;lt;70"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;basicsr&lt;/code&gt; import error on &lt;code&gt;functional_tensor&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;New torchvision&lt;/td&gt;
&lt;td&gt;Change the import to &lt;code&gt;torchvision.transforms.functional&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SDXL produces black or NaN images in fp16&lt;/td&gt;
&lt;td&gt;The stock VAE overflows&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;madebyollin/sdxl-vae-fp16-fix&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A 30 GB download takes hours&lt;/td&gt;
&lt;td&gt;One slow stream&lt;/td&gt;
&lt;td&gt;Download models in parallel with &lt;code&gt;hf download&lt;/code&gt;, and use the bf16 T5 copy (9.5 GB, not 19 GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;brew install --cask blender&lt;/code&gt; asks for a password&lt;/td&gt;
&lt;td&gt;The cask needs sudo&lt;/td&gt;
&lt;td&gt;Copy Blender.app from the official DMG into &lt;code&gt;~/Applications&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Which method should you use?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You need a recurring, realistic main character →&lt;/strong&gt; SDXL stills with a fixed character description and seeds, plus camera motion. Generate 2–3 options per shot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One hero moment needs real motion (a smile, a nod, someone talking) →&lt;/strong&gt; LTX-Video on that SDXL still. One person, small motion, keep the clean seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The shot has a crowd or a person walking →&lt;/strong&gt; keep it a still. Today's local video models melt here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need exact staging (someone holds a phone to a scanner at a precise moment) →&lt;/strong&gt; Blender, if a game look is acceptable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want a presenter face →&lt;/strong&gt; a SadTalker bubble with a voice-matched face.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need real people and don't care who →&lt;/strong&gt; stock, ideally Pexels with a free API key.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One machine and one run per clip.&lt;/strong&gt; No repeated runs, so no variance numbers. An M3 or M4, or a Mac with less load, will be faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The scores are my judgement&lt;/strong&gt;, not a user study. The repo has every output so you can judge for yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I picked the best of 2 seeds&lt;/strong&gt; for the stills and trimmed two AI video clips. That's realistic practice, but it is curation.&lt;/li&gt;
&lt;li&gt;On an NVIDIA GPU (CUDA, bf16) the AI video steps would be many times faster; this is a Mac-specific result.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/kartikjindal/local-character-video-5-methods-benchmark &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;local-character-video-5-methods-benchmark
uv venv .venv-ai &lt;span class="nt"&gt;--python&lt;/span&gt; 3.11 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--python&lt;/span&gt; .venv-ai/bin/python &lt;span class="nt"&gt;-r&lt;/span&gt; requirements-ai.txt
./download-models.sh                                     &lt;span class="c"&gt;# ~23 GB, in parallel&lt;/span&gt;
.venv-ai/bin/python gen_stills.py &lt;span class="nt"&gt;--seeds&lt;/span&gt; 2              &lt;span class="c"&gt;# method 2&lt;/span&gt;
.venv-ai/bin/python gen_video.py &lt;span class="nt"&gt;--frames&lt;/span&gt; 121 &lt;span class="nt"&gt;--steps&lt;/span&gt; 30 &lt;span class="c"&gt;# method 3&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 8777                              &lt;span class="c"&gt;# dashboard: compare all five&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SadTalker and Blender steps are in the &lt;a href="https://github.com/kartikjindal/local-character-video-5-methods-benchmark#reproduce" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Free and local gets you surprisingly far. &lt;strong&gt;SDXL stills give realistic, consistent characters for ~2 minutes of GPU time per shot&lt;/strong&gt;, and that should be the backbone. Local AI video is real but narrow: magic for one person making small movements, unusable for crowds, and slow on a Mac mainly because of memory, not compute. Everything here cost $0 and about 28 GB of disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would you try next?&lt;/strong&gt; I'm curious whether a newer local video model, or a Mac with more memory headroom, changes the AI video verdict. If you've run LTX-Video, Wan or HunyuanVideo on Apple Silicon, share your timings in the comments.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;▶ &lt;a href="https://kartikjindal.github.io/local-character-video-5-methods-benchmark/" rel="noopener noreferrer"&gt;Live side-by-side dashboard&lt;/a&gt; (all five videos in sync)&lt;/li&gt;
&lt;li&gt;📦 &lt;a href="https://github.com/kartikjindal/local-character-video-5-methods-benchmark" rel="noopener noreferrer"&gt;Repo: inputs, outputs, scripts, logs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://github.com/kartikjindal/local-character-video-5-methods-benchmark/blob/main/REPORT.md" rel="noopener noreferrer"&gt;Full report with every measured number&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Tools: &lt;a href="https://huggingface.co/SG161222/RealVisXL_V4.0" rel="noopener noreferrer"&gt;RealVisXL V4&lt;/a&gt; · &lt;a href="https://github.com/Lightricks/LTX-Video" rel="noopener noreferrer"&gt;LTX-Video&lt;/a&gt; · &lt;a href="https://github.com/OpenTalker/SadTalker" rel="noopener noreferrer"&gt;SadTalker&lt;/a&gt; · &lt;a href="https://github.com/TencentARC/GFPGAN" rel="noopener noreferrer"&gt;GFPGAN&lt;/a&gt; · &lt;a href="https://www.blender.org" rel="noopener noreferrer"&gt;Blender&lt;/a&gt; · &lt;a href="https://quaternius.com" rel="noopener noreferrer"&gt;Quaternius&lt;/a&gt; · &lt;a href="https://huggingface.co/hexgrad/Kokoro-82M" rel="noopener noreferrer"&gt;Kokoro TTS&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/hyperframes" rel="noopener noreferrer"&gt;HyperFrames&lt;/a&gt; · &lt;a href="https://ffmpeg.org" rel="noopener noreferrer"&gt;ffmpeg&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;All AI faces in this post are fictional.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>opensource</category>
      <category>video</category>
    </item>
  </channel>
</rss>
