DEV Community

Cover image for 5 attempts, 2 SIGKILLs, 1 non-existent flag: how I got a 4-second avatar to speak Spanish
Migbolivar
Migbolivar

Posted on

5 attempts, 2 SIGKILLs, 1 non-existent flag: how I got a 4-second avatar to speak Spanish

Summer Bug Smash: Smash Stories πŸ›πŸ›Ή

5 attempts, 2 SIGKILLs, 1 non-existent flag: how I got a 4-second avatar to speak Spanish

This is the story of a four-second video that took an entire evening, five attempts, two out-of-memory kills, one flag that never existed, and a parameter combo that felt like a magic spell. The mission: make my AI avatar say a sentence in Spanish β€” with the lips actually synced.

The mission

I had an avatar video of myself. I generated the audio with edge-tts (Spanish voice, "La IA no espera. Tu negocio tampoco." β€” "AI doesn't wait. Neither does your business."). All I needed was to map the audio onto the face with Wav2Lip. Simple, right?

Attempt 1 β€” the library moved on without me

librosa.filters.mel(...) β†’ TypeError: unexpected keyword argument
Enter fullscreen mode Exit fullscreen mode

Wav2Lip was written for librosa 0.8. My environment had 0.11, where mel() requires keyword arguments. One-line patch in audio.py, re-run.

Attempt 2 β€” SIGKILL (the quiet killer)

No Python traceback. No error message. Just Killed β€” the signature of the OOM killer on a server with ~8 GB already occupied by other services. Wav2Lip was loading the whole face video into memory.

Attempt 3 β€” the flag that never existed

I shrank the video to 960Γ—540 and confidently added --batch_size 8. The CLI replied: unknown argument. The real flag is --wav2lip_batch_size β€” I'd read the docs from memory and hallucinated the name. (Yes β€” I, the person writing a story about a bug, introduced a hallucinated flag.)

Attempt 4 β€” the second SIGKILL

Correct flag, both batch sizes set: --wav2lip_batch_size 8 --face_batch_size 8. The process made it further… then died the same silent death. At this point I understood: it wasn't the video resolution, it wasn't the flags β€” it was peak memory during inference.

Attempt 5 β€” the magic combo

The winning incantation:

OMP_NUM_THREADS=4 python inference.py \
  --checkpoint_path wav2lip_gan.pth \
  --face miguel-face-small.mp4 \
  --audio avatar-es.mp3 \
  --wav2lip_batch_size 4 \
  --resize_factor 2 \
  --outfile miguel-avatar-es.mp4
Enter fullscreen mode Exit fullscreen mode
  • --resize_factor 2: halves the processing resolution (and memory) while keeping the output watchable
  • --wav2lip_batch_size 4: small enough to fit, big enough to be worth it
  • OMP_NUM_THREADS=4: keeps the rest of the box alive

104 frames. 4.2 seconds. Exit code 0. The lips moved with the Spanish audio, frame by frame.

The plot twist

I was so proud I tried a "high-definition" re-run with --resize_factor 1. The OOM killer came back and murdered it in seconds. The lesson landed twice: I wasn't fixing resolution, I was fighting RAM β€” and the "lower quality" version was the production version all along.

What made the software more resilient (me)

  1. The OOM kill is a feature of your environment, not a bug of your code β€” free -h before you start, batch sizes after.
  2. Read the CLI help, don't trust your memory of it β€” --help takes ten seconds and saves an attempt.
  3. The ugly-but-working config is a legitimate final state β€” resize_factor 2 + batch 4 was "worse" on paper and the only thing that survived.

The takeaway

A four-second video that took five attempts taught me more about memory management than a month of tutorials. And the avatar? He's out there, speaking Spanish, perfectly in sync.

🎬 The actual output: a 4.2s clip, 480Γ—270, 365,943 bytes β€” the smallest, scrappiest, most hard-won video I've made.

bugsmash

This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry.

Top comments (0)