# How I Made an AI Explainer Video in My Own Cloned Voice

> Source: <https://alexop.dev/posts/ai-explainer-video-with-my-cloned-voice/>
> Published: 2026-10-31 00:00:00+00:00

I wanted to understand [aimock](https://github.com/CopilotKit/aimock), the mock server for AI apps from CopilotKit. Reading the source is fine, but I learn faster when someone explains it to me. So I asked Claude Code to make an explainer video, narrated in my own voice.

This is the result. Claude Code generated every frame and every word in one session. I did not record anything.

I started the way I always start. I told Claude Code to clone the repo into a temp folder and explain how it works.

In short, aimock is a fake API server. You point your SDK’s base URL at it, and your real OpenAI, Claude or Gemini client sends real HTTP to it. aimock matches the request against fixtures and answers in the format of whichever provider you call. The clever part is that aimock first converts every request into one shape, so one matcher and one fixture format cover every provider.

I asked follow-up questions until I could explain it back. “So aimock is handling the turn?” Yes, but it keeps no state. The SDK sends the whole history on every turn, and a fixture can check if a tool result is in there.

For the video I used [HyperFrames](https://hyperframes.heygen.com). It renders video from HTML. Every scene is an HTML file with a paused GSAP timeline, and the CLI seeks that timeline frame by frame and encodes it. The same input always produces the same video.

The nice thing about HTML is that the clanker is really good at writing it. Claude Code wrote nine scenes, ran `hyperframes check`, looked at snapshot contact sheets, fixed what was off and rendered. I only gave feedback.

The first version was calm and slow. I told it to make it more like Fireship. It rewrote the script to about 80 seconds, with something new on screen every second or two.

A silent explainer is boring, and the default AI voices all sound the same. Then I read [Armin Ronacher’s post on custom voices with Qwen3-TTS](https://lucumr.pocoo.org/2026/10/29/qwen3-custom-voices/) and realized I have hours of conference talks on YouTube. That should be enough.

Twenty seconds turned out to be enough. Qwen3-TTS has a `Base` model that clones a voice from a few seconds of reference audio plus its transcript. The pipeline Claude Code built:

1. Download 90 seconds of my Vue.js Nation talk with `yt-dlp` .
2. Transcribe it with Whisper and pick 20 seconds that start and end on a full sentence.
3. Generate every line of the script three times with different seeds.
4. Transcribe each take again and keep the one where every word is there.

The generation part is short:

``` python
import torch
from qwen_tts import Qwen3TTSModel

model = Qwen3TTSModel.from_pretrained(
    "Qwen/Qwen3-TTS-12Hz-1.7B-Base",
    device_map="mps", dtype=torch.bfloat16, attn_implementation="sdpa",
)
prompt = model.create_voice_clone_prompt(
    ref_audio="ref.wav",
    ref_text=open("ref.txt").read(),
    x_vector_only_mode=False,  # use the transcript too, not only the speaker embedding
)

torch.manual_seed(23)
wavs, sr = model.generate_voice_clone(
    text="Meet A.I. mock, from CopilotKit.",
    language="English",
    voice_clone_prompt=prompt,
    max_new_tokens=200,  # stops a runaway take from looping forever
)
```

It runs locally on my MacBook. One line takes about five seconds. It needs no API key and costs nothing.

Then the voice set the timing. Claude Code took the word timestamps from each take and moved every animation to land on the word that names it. When the voice says “fixture A matches”, the checkmark appears.

The voiced version worked, but it looked flat. The boxes were small on a big dark background, and every scene cut hard to the next one.

So I spawned an Opus subagent and asked it to make the video more visually interesting and give it better sound, without touching the voice or its timing. It added camera push-ins on the payoff words, five kinds of transitions, a dot grid and film grain, and 61 sound effects timed to the frames. It also found a free techno track on Freesound and lowered it under my voice whenever I speak.

I didn’t expect it to find a bug. Some glitch sounds in the first version played up to three seconds off the picture, and I never noticed.

Not everything worked on the first try.

- **“aimock” came out as “AMOC”.** The model does not know the word. Writing it as “A.I. mock” in the script fixed it.
- **My voice started looping “to go, to go, to go”.** My first reference clip ended mid-sentence with “I would not recommend to go”. In-context cloning treats the reference as the start of the same recording, so a line beginning with “Turn two” turned into a loop. A clip that ends on a full sentence fixed it.
- **Gaps.** The first narrated version had pauses between scenes, and it did not feel like one person talking. Writing one continuous line per scene and timing the scenes to the voice fixed that.
- **The agent cannot hear.** It measured loudness, silence gaps and transcripts, because those are numbers it can check. Whether the voice sounds like me is something only I can judge.

Twenty seconds is enough

Twenty seconds of a conference talk recorded with a lapel mic was enough to make a convincing version of me. If you have talks online, anyone can do the same with your voice. Use this for your own voice, not for somebody else’s.

A year ago I would have needed a screen recorder, a microphone and a free weekend for this. Now it was one session where I mostly gave feedback. The video is far from perfect, but it explains aimock better than my notes did, and it sounds like me.

I think this changes how I will learn new libraries. Instead of reading the README, I will ask the clanker to explain it to me as a video.
