SuperAPI AI Directory

generate-voiceover-elevenlabs-v3

General & other

View repository

Updated 10-16 05:49

About this skill

generate-voiceover-elevenlabs-v3

Purpose

Render a v3-tagged script (typically produced by format-script-to-style) into an mp3 using ElevenLabs' eleven_v3 model, picking an approved voice from VOICES.md, then applying ffmpeg atempo post-process to hit the desired pace.

Why a v3-specific skill (vs. the existing create-voiceover-elevenlabs): v3 interprets audio tags expressively but silently ignores the speed voice_setting (see memory feedback_v3_ignores_speed.md). Pace control must be post-processed. This skill bakes that in.

Inputs

  • tagged_script: the v3-tagged script text (from the calling agent or format-script-to-style).
  • target_duration_seconds (optional): if provided, the skill computes the required atempo factor and applies it. If absent, no post-process is applied.
  • Voice selection: interactive — the skill presents the approved voices from VOICES.md and asks the user to choose. The chosen voice's stored v3 settings are used as defaults.
  • output_dir: where to save outputs. Defaults to <project>/working/voiceover/ or skills/test-runs/<timestamp>/generate-voiceover-elevenlabs-v3/ for test runs.

Workflow

Prefer the bundled runnable script when invoked programmatically:

python3 skills/atoms/voiceover/generate-voiceover-elevenlabs-v3/scripts/generate.py \
  --voice-id <id> \
  --script-file <tagged-script.md> \
  --target-duration <seconds> \
  --output-dir <dir> \
  --name <basename>

The script handles env loading, the v3 API call, ffprobe duration measurement, atempo computation/clamping, and manifest writing. The interactive workflow below is what an agent should do when invoking the skill via chat (vs. the script-direct path):

  1. Load and present voices. Read /Users/0xhbam/Desktop/Cursor/content-goose/VOICES.md. Show the list to the user and ask which to use. Extract the chosen voice's voiceId and the v3 settings line if present.
  2. Confirm the script. Show the tagged script to the user and confirm before any paid API call. (Per feedback_media_pipeline_gates.md: show before render.)
  3. Resolve the ElevenLabs API key. The key lives in gtm-goose/.env as ELEVEN_LABS_API_KEY (underscore). Alias to ELEVENLABS_API_KEY for the call:
    export ELEVENLABS_API_KEY=$(grep '^ELEVEN_LABS_API_KEY' /Users/0xhbam/Desktop/Cursor/gtm-goose/.env | sed 's/.*=[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
    
  4. Call ElevenLabs v3 TTS:
    curl -sS -X POST "https://api.elevenlabs.io/v1/text-to-speech/<VOICE_ID>?output_format=mp3_44100_128" \
      -H "xi-api-key: $ELEVENLABS_API_KEY" \
      -H "Content-Type: application/json" \
      -d "$(python3 -c "import json,sys; print(json.dumps({'text': sys.argv[1], 'model_id': 'eleven_v3', 'voice_settings': <voice_settings_from_VOICES.md>}))" "$TAGGED_SCRIPT")" \
      -o "<output_dir>/<name>.mp3"
    
    Use mp3_44100_128 (higher formats are tier-gated; see learning from 2026-05-16 test).
  5. Measure rendered duration:
    ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "<name>.mp3"
    
  6. Apply atempo post-process if target_duration_seconds was supplied:
    • Compute atempo_factor = rendered_duration / target_duration_seconds.
    • Clamp to [1.0, 1.25]. Above 1.25 voice starts to sound chipmunky; surface to user instead of pushing further (alternative: trim script and re-render).
    • If atempo_factor < 1.05, skip the post-process (negligible difference).
    • Apply:
      ffmpeg -y -i "<name>.mp3" -filter:a "atempo=<factor>" -b:a 192k "<name>_atempo<factor>.mp3"
      
  7. Write a manifest alongside the output with: voice_id, voice_name, model, voice_settings, tagged_script, rendered_duration, target_duration, atempo_factor (or null), output_paths.
  8. Report final duration vs. target to the user.

Output

  • <name>.mp3 — raw v3 render
  • <name>_atempo<factor>.mp3 — atempo-corrected render (only if target_duration_seconds was supplied and factor > 1.05)
  • <name>.manifest.json — generation parameters and final paths

Quality Checks

  • Voice selected is present in VOICES.md.
  • HTTP 200 from the ElevenLabs API; output mp3 is valid (use file and ffprobe to confirm non-zero duration).
  • If target_duration_seconds supplied: final mp3 duration is within ±10% of target.
  • atempo factor stayed within the clamp range — if it didn't, the skill stopped and surfaced.
  • API key was loaded from gtm-goose/.env (not hardcoded, not asked from the user).

Failure Modes

  • 403 subscription_required on output_format — drop to mp3_44100_128. Higher formats are Creator-tier-gated.
  • 401 invalid_api_key — key was not loaded correctly. Re-check the env-var name (note: it's ELEVEN_LABS_API_KEY with underscore in the source .env).
  • Voice id not in VOICES.md — ask the user to confirm and offer to add it to VOICES.md with clone-voice-elevenlabs or search-voices-elevenlabs.
  • Rendered duration is much longer than target (atempo factor > 1.25 required) — stop and offer to trim the script or switch model to eleven_multilingual_v2.
  • Audio tags appear unspoken in the rendered audio — the model didn't interpret them as tags. Likely cause: tag syntax error or tag stacking. Re-check the recipe.
  • Source script contains 4-byte unicode (some emojis) that breaks JSON serialization — strip emojis from the script body before calling.