DEV Community

Oscar Madera
Oscar Madera

Posted on

SpotForge: 3 Facts In, a Broadcast-Ready Radio Ad Out (Gemma + ElevenLabs)

Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle

What I Built

SpotForge turns three facts about a local business (name, offer, call to action) into a 30-second commercial audio ad, with copy, voice, and a music bed, in about 15 seconds.

The people who would pay are freelancers who manage social media for neighborhood shops. A pizzeria wants a Tuesday 2x1 promo for Instagram Stories, but an agency charges hundreds of dollars for a single spot, and a phone voice note sounds amateur. SpotForge fills that gap.

The pipeline:

  1. Gemma 4 writes an AIDA script (Attention, Interest, Desire, Action) with a word budget based on speech rate (~2.5 words/s in Spanish, ~2.8 in English).
  2. A deterministic normalizer turns 2x1, $15, 30%, and phone numbers into spoken words, because TTS engines mangle them.
  3. ElevenLabs renders the voiceover.
  4. mutagen measures the real MP3 duration. If it overshoots the target by more than 3 seconds, one adaptive retry runs with a smaller word budget.
  5. FFmpeg mixes in a CC0 music bed with ducking and a fadeout.

The business math (ElevenLabs Starter plan, $6/month at the time of writing; check current pricing): roughly $0.08–$0.12 of voice cost per spot, sold at $5–$15. A freelancer with 5 clients and 2 spots per week makes about $150/month for $6 of tooling.

Demo

Live app: spotforge.onrender.com (free Render tier: the first load can take about a minute to wake up)

Three spots generated end-to-end:

Business Voice Duration Audio
Pizzería Don Mario George 28.4s demo_pizzeria.mp3
Barbería El Bigote Liam 31.9s demo_barber.mp3
Apex Auto Repair Roger 27.3s demo_autorepair.mp3

A script Gemma wrote for the pizzeria (Spanish):

"¿Buscas el auténtico sabor de Italia sin salir de casa? En Pizzería Don Mario, usamos horno de leña y masa madre... Los martes y jueves tenemos dos por uno en todas nuestras pizzas familiares... Llama al cinco cinco cinco, cero uno, nueve dos."

Code

GitHub logo oscarbol09 / spotforge

Commercial audio ad generator for local businesses using Google Gemma open weights, ElevenLabs studio voice synthesis, and FFmpeg audio mixing.

SpotForge

Turn three basic facts about a local business into a broadcast-ready 30-second commercial audio ad using Google Gemma open weights, deterministic text normalization, ElevenLabs studio voice synthesis, and FFmpeg audio mixing.

License: MIT Python 3.12 FastAPI Model: Gemma 4


What is SpotForge?

Neighborhood businesses—pizzerias, barbershops, auto mechanics, dental practices—frequently need high-impact promotional audio for social video (Instagram Stories, TikTok Ads), local radio, or telephone hold messaging.

Most cannot afford traditional production:

  • Creative Agencies and Studios: Cost $300 to $800 per 30-second spot for scriptwriting, studio recording, and voice talent.
  • Manual Recording: Cellphone microphone recordings lack commercial energy, proper breathing pauses, and background music, leading listeners to scroll away.

SpotForge converts 3 business inputs (business name, core offer, and call to action) into an air-ready commercial ad in seconds:

  1. AIDA Ad Copywriting: Google Gemma 4 generates a structured script (Attention -> Interest -> Desire -> Action) constrained by speech cadence targets (~2.5 words/second in Spanish…

FastAPI backend, a vanilla JS single-page frontend, MIT license, 7 architecture decision records in docs/adr/, and a pytest suite (32 tests).

How I Built It

  • Open-weight copywriting: Gemma 4 (gemma-4-26b-a4b-it) behind a BaseLLMBackend interface. It runs through Google AI Studio by default, or fully local through Ollama (LLM_BACKEND=ollama).
  • One structured prompt combining role, AIDA constraints, and word budget. Gemma also returns its reasoning parts, which I filter out so only the spoken copy reaches the TTS.
  • Why a rule-based normalizer instead of asking the LLM: regex plus num2words gives the same result every time, and phone numbers get grouped so they sound like an announcer, not a robot.
  • Real duration control: LLM word counts are only an estimate, so I measure the actual MP3 and retry once if needed. Voices read at different speeds, so this mattered more than I expected.
  • FFmpeg mixing with looped CC0 beds, 15% ducking under the voice, and a 2-second fade. If FFmpeg is missing, the app degrades to voice-only with a warning.
  • Deployment and cost protection: a Dockerfile that ships FFmpeg, a render.yaml blueprint with a health check at /api/health, and a per-client rate limiter so a public demo can't drain my API quota.
  • Privacy notice: the footer tells users their text is processed by Google AI Studio and ElevenLabs, and generated audio is deleted automatically after 24 hours (covered by unit tests).

Why Does Open Innovation Matter?

  • Margins. A solo freelancer can't absorb per-token pricing that scales with every script and every client. Open weights keep the copywriting step at zero marginal cost.
  • Portability. I'm not tied to one vendor's endpoint or pricing. The same prompt runs on a hosted Gemma or on a local Ollama instance.
  • Privacy, with a limit I want to be upfront about. With Ollama, the script generation, which handles unreleased promos and discount codes, can stay entirely on the freelancer's laptop. The voice step is the exception: the final text is sent to ElevenLabs, which is a closed service. Making that step open is the natural next step for this project.
  • Fine-tuning potential. With open weights, I could fine-tune on regional radio copy and local slang, which a general-purpose API handles poorly.

Prize Categories

  • Best Use of Gemma: Gemma 4 for AIDA copywriting, with cloud and local Ollama backends.
  • Best Use of ElevenLabs: Multilingual v2 voiceover, curated voices, character-usage tracking, and real-duration calibration.
  • Best Use of Render: deployed on Render with a Dockerfile (FFmpeg included), render.yaml blueprint, and health checks.

Top comments (0)