ElevenLabs voice search
Give it a character (images, written guidance, or both) and you get a casting brief, a
list of library voices that match it and are verified for eleven_v4, and a shortlist
of 3 to audition (elevenlabs-tts-v4). Only when the person wants the voice matched to
a face or a detailed character is it 12, ranked by ear with gemini-rank-voices.
Inputs
- The character's images (a character sheet, a portrait) and/or guidance (a brief, a document of personality notes, a line of text).
- The job's language (default
en) and an accent only if the guidance or the person names one. - The script, when there is one: the audition passage is taken from its copy.
- Voices auditioned in earlier passes for this character, to skip.
Write the brief
Load references/casting-brief.md and write brief.json for the character: library
filters, three to five search queries, the ideal voice in sound terms, an avoid list,
the performance guide and the audition passage (part of the script's copy).
Run it
The search is ElevenLabs' shared voice library endpoint, /v1/shared-voices, not a
model. hn4a passes it through as ElevenLabs documents it, free; call it with
provider_request and save each page:
provider_request("elevenlabs",
"/v1/shared-voices?search=warm mentor&gender=female&age=middle_aged&language=en&page_size=100&page=0",
cwd="<job>", save_to="voices/<character>/pass_a/search/warm_mentor_middle_aged_p0.json")
Parameters: search (matches names and descriptions), gender (male, female,
neutral), age (young, middle_aged, old), accent (free text, lowercase
labels), language, optionally use_cases and descriptives, page_size (up to
100), page from 0.
Search plan:
- For each age bucket in the brief, run each search query, plus one search with no query (the bucket's unsearched results). Accent only when the brief has one.
- Keep only voices verified for eleven_v4 in the job's language: a voice whose
verified_languageshas an entry withmodel_ideleven_v4and thatlanguage. The search can't filter by model, and only about 29% of English library voices are verified for v4, sparse on the first pages. - So page on: 100 per page, up to 5 pages per search, until that search has kept 30 verified voices or a page comes back short.
- Deduplicate by
voice_idin order of first appearance, drop voices from earlier passes, and save the list ascandidates.json.
Then shortlist 3 (or 12 for ranking) on metadata alone (the rules are in references/casting-brief.md)
and save shortlist.json.
What you get
candidates.json: each voice with voice_id, name, gender, age, accent,
descriptive, use_case, description, preview_url, cloned_by_count,
usage_character_count_1y and verified_languages. shortlist.json: 3 voice ids
(or 12), best first, with a reason each.
Library voices speak by voice_id without being added to the account, so casting
never changes it.
Tips and traps
- Unverified voices still render on v4 but can drift from their sample. A pass cast before this filter existed had no v4-verified voice among its picks.
- The English library is mostly American; an unfiltered search will reflect that.
- Each shortlisted voice's
preview_urlis the creator's own sample, a public file (free, no key, no TTS spent). Save it for the first listen:exec("ffmpeg", ["-y", "-i", "<preview_url>", "-c", "copy", "<pass>/previews/<voice_id>.mp3"], cwd). - Nothing fits after listening: run another pass (
pass_b) with a note in the brief ("warmer, less polished, a touch older"); skip every voice already auditioned.
Check it
Every candidate passes the v4 check; the shortlist has no duplicates, no voice from an earlier pass, and nothing the brief's avoid list rules out.


