<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Tts on hippotion</title><link>https://blog.hippotion.com/tags/tts/</link><description>Recent content in Tts on hippotion</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 13 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.hippotion.com/tags/tts/index.xml" rel="self" type="application/rss+xml"/><item><title>🎙️ Cloning My Own Voice for My Kid's Audiobooks</title><link>https://blog.hippotion.com/posts/clone-your-voice-hungarian-audiobooks/</link><pubDate>Fri, 13 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.hippotion.com/posts/clone-your-voice-hungarian-audiobooks/</guid><description>Zero-shot voice cloning with XTTS-v2 on a CPU-only k3s node: 26 seconds of phone audio in, a cloned-voice audiobook out — and an honest verdict from the bedtime jury. Every manual step, including the ones that went wrong.</description><content:encoded><![CDATA[<h2 id="the-problem-nobody-sells-a-fix-for">The problem nobody sells a fix for</h2>
<p>My kid loves audiobooks. The commercial platforms barely carry Hungarian
children&rsquo;s books, and none of them carry the one narrator my kid actually
prefers: me. I can&rsquo;t read aloud every evening — but my homelab doesn&rsquo;t have
that excuse.</p>
<p>The platform half (ebook → M4B → Audiobookshelf on k3s) is a story for
another post. This one is about the voice: how to go from a phone recording
to an audiobook narrated in your own voice, step by step, on hardware with
no GPU.</p>
<p>The short version: <strong>XTTS-v2 does zero-shot voice cloning from a ~20-second
sample.</strong> No training, no fine-tuning, no dataset. One clean recording and a
flag.</p>
<hr>
<h2 id="why-xtts-v2-in-2026">Why XTTS-v2, in 2026?</h2>
<p>It&rsquo;s not the best open TTS model anymore. Chatterbox beats ElevenLabs in
blind tests; F5-TTS sounds cleaner. But model selection for a small language
is constraint-first, not leaderboard-first: Chatterbox has <strong>no Hungarian</strong>,
NVIDIA&rsquo;s TTS NIMs have <strong>no Hungarian</strong>, Kokoro — no Hungarian. XTTS-v2
speaks Hungarian <em>and</em> clones voices <em>and</em> runs on CPU. That intersection
has exactly one resident.</p>
<p>I run it via <a href="https://github.com/DrewThomasson/ebook2audiobook">ebook2audiobook</a>,
which wraps XTTS with Calibre ingestion and M4B chaptering.</p>
<hr>
<h2 id="step-1--record-25-seconds-of-yourself">Step 1 — Record ~25 seconds of yourself</h2>
<p>Phone voice-memo app, quiet room, ~20 cm from your mouth. Mine came out as
28 seconds of stereo 48 kHz AAC. Two rules that matter more than gear:</p>
<ul>
<li><strong>Read the way you want the books narrated.</strong> The clone copies prosody —
energy, pacing, warmth — not just timbre. A flat recital clones into a
flat narrator. I read a children&rsquo;s tale the way I&rsquo;d read it at bedtime.</li>
<li><strong>Don&rsquo;t peak the mic.</strong> My sample hit −0.1 dB max volume — right at the
clipping ceiling. It worked, but quieter is safer. Check yours:</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ffmpeg -i janos.m4a -af volumedetect -f null - 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">|</span> grep volume
</span></span><span class="line"><span class="cl"><span class="c1"># mean_volume: -21.4 dB   ← fine</span>
</span></span><span class="line"><span class="cl"><span class="c1"># max_volume:  -0.1 dB    ← living dangerously</span>
</span></span></code></pre></div><hr>
<h2 id="step-2--normalize-to-what-xtts-wants">Step 2 — Normalize to what XTTS wants</h2>
<p>XTTS expects a mono WAV; 24 kHz matches its internal rate. Trim the silence
off both ends while you&rsquo;re at it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ffmpeg -i janos.m4a <span class="se">\
</span></span></span><span class="line"><span class="cl">  -af <span class="s2">&#34;silenceremove=start_periods=1:start_threshold=-45dB:start_silence=0.2,\
</span></span></span><span class="line"><span class="cl"><span class="s2">areverse,silenceremove=start_periods=1:start_threshold=-45dB:start_silence=0.2,\
</span></span></span><span class="line"><span class="cl"><span class="s2">areverse&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ar <span class="m">24000</span> -ac <span class="m">1</span> janos.wav
</span></span></code></pre></div><p>(The double-<code>areverse</code> is the classic trick: <code>silenceremove</code> only trims the
front, so you flip the audio, trim the front again, flip it back.)</p>
<p>Drop the result where your TTS stack looks for voices. In ebook2audiobook
that&rsquo;s the <code>voices/</code> tree, organised by language:</p>
<pre tabindex="0"><code>voices/hun/adult/male/janos.wav
</code></pre><hr>
<h2 id="step-3--synthesize">Step 3 — Synthesize</h2>
<p>One flag does the cloning. Headless run on the k3s pod:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl <span class="nb">exec</span> -n web-audiobooks deploy/ebook2audiobook -- sh -c <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s1">&#39;cd /app &amp;&amp; python app.py --headless \
</span></span></span><span class="line"><span class="cl"><span class="s1">     --ebook &#34;/app/ebooks/tale.txt&#34; \
</span></span></span><span class="line"><span class="cl"><span class="s1">     --language hun \
</span></span></span><span class="line"><span class="cl"><span class="s1">     --tts_engine xtts \
</span></span></span><span class="line"><span class="cl"><span class="s1">     --device cpu \
</span></span></span><span class="line"><span class="cl"><span class="s1">     --voice /app/voices/hun/adult/male/janos.wav \
</span></span></span><span class="line"><span class="cl"><span class="s1">     --output_format m4b \
</span></span></span><span class="line"><span class="cl"><span class="s1">     --output_dir /app/audiobooks&#39;</span>
</span></span></code></pre></div><p>On my 12-core CPU node this runs at roughly 3× real-time — a 2-minute tale
takes ~8 minutes, a full children&rsquo;s book is an overnight job. The first run
computes speaker latents from your WAV; after that it&rsquo;s ordinary synthesis
with your voice as the reference.</p>
<hr>
<h2 id="step-4--ab-before-you-batch">Step 4 — A/B before you batch</h2>
<p>Render one <em>short</em> book twice — stock narrator and cloned voice — and put
both in front of the household jury. Cloning quality is personal in the most
literal sense: MOS scores won&rsquo;t tell you whether it sounds like <em>you</em>. My
benchmark has strong opinions and goes to bed at eight.</p>
<p>Only after the clone passes do you re-render the library with <code>--voice</code>.</p>
<p><img alt="Audiobookshelf library with the same tale twice: stock narrator and the &ldquo;apa hangján&rdquo; clone, side by side for the jury" loading="lazy" src="/posts/clone-your-voice-hungarian-audiobooks/abs-ab.png"></p>
<hr>
<h2 id="the-manual-steps-that-earn-the-word-manual">The manual steps that earn the word &ldquo;manual&rdquo;</h2>
<p>Things the tutorials skip, learned the slow way:</p>
<ul>
<li><strong>Long conversions die with the browser tab.</strong> Gradio-style web UIs tie
the job to the open page; close the laptop and you get &ldquo;Conversion
cancelled&rdquo; half a book in. Anything longer than ~15 minutes of audio runs
headless under <code>nohup</code>.</li>
<li><strong>CPU synthesis leaks memory over hours.</strong> My pod has a hard 6 Gi limit on
a 16 Gi node, and a 6-hour run will hit it. Keep the cap (it protects the
other 30 namespaces), and rely on the tool&rsquo;s <code>--session &lt;id&gt;</code> resume — it
picks up at the exact sentence. One catch: headless resume still asks an
interactive <code>Resume? [y]es</code> — pipe <code>echo y |</code> into it.</li>
<li><strong>The per-chapter FLACs survive a crash.</strong> If the final M4B muxing step
OOMs, don&rsquo;t re-synthesize: the chapters are sitting in the session&rsquo;s tmp
directory, and <code>ffmpeg</code> will assemble them into a chaptered M4B with a
hand-written FFMETADATA file in about two minutes, at near-zero memory.</li>
</ul>
<p>None of this is hard. It&rsquo;s just undocumented — which is the gap between
&ldquo;there&rsquo;s a model for that&rdquo; and your kid pressing play.</p>
<hr>
<h2 id="postscript-the-jury-came-back">Postscript: the jury came back</h2>
<p>The clone failed. Recognizably my timbre, nowhere near natural — I wouldn&rsquo;t
play it to my kid, which is the only metric that exists for this project.</p>
<p>Worth being precise about <em>what</em> failed: the stock XTTS-v2 narrator passed
the ear test and the library keeps growing with it. Zero-shot <strong>cloning</strong> is
the part that fell short — a 2023 model conditioning on 26 seconds of a
voice it has never seen, in a language that was never its strong suit. The
pipeline above is still the right pipeline; the model isn&rsquo;t there yet on
CPU-class options.</p>
<p>The next experiment is already picked: <a href="https://huggingface.co/Maxdorger29/f5-tts-hungarian">F5-TTS Hungarian</a>,
a 2026 fine-tune on 280 hours of actual Hungarian speech, built precisely
for short-sample cloning. It needs CUDA, which my node doesn&rsquo;t have — but a
rented spot GPU tests it for the price of an espresso. If it passes the
bedtime jury, that&rsquo;ll be its own post.</p>
<p>Negative results are results. The jury reconvenes when the GPU shows up.</p>
]]></content:encoded></item></channel></rss>