Creating a voiceover

A voiceover is narration generated from text. create_voiceover turns a script into two things at once: an audio file of a voice reading it, and the timestamp of every word in that recording. The audio is what you hear; the timestamps are what let you caption it and time the rest of the video to it.

The basic call

from imaginaryarts import create_voiceover, Voices

voiceover = create_voiceover(
    "Most people quit right before it starts working.",
    voice=Voices.AmericanNarrator,
    project=project,
)

That gives you an AIVoiceover. The things you will use on it:

MemberWhat it is
.audioThe path to the recording, a .wav in the project's assets folder.
.durationIts length in milliseconds.
.wordsEvery word in order, each with .text, .start and .end in milliseconds.
.scenesThe script's scenes as timed segments (see below).
.voice_idThe ElevenLabs voice ID it was read with.
.tile(...), .find(...)Placing it on the timeline and searching it. See Timing.

Like everything the library generates, the voiceover is cached. Running the script again with the same text and voice reuses the recording; changing a single word re-reads the script, which costs cents.

Three shapes of script

What you pass as the script decides how much timing structure you get back. The words are always timestamped; the question is whether you also get scene and clause boundaries.

A string. One continuous read. You get word timestamps and nothing else.

create_voiceover("One sentence. Then another.", voice=Voices.BritishNarrator, project=project)

A list of strings. Each item is a scene. Besides the words, you get a timed segment per scene: when the scene's first word starts and its last word ends. This is the shape to use when each part of the narration goes with its own picture or clip, because you can then place each scene's visual exactly where its narration is.

create_voiceover(
    [
        "Thales of Miletus was the first person we know of to ask what everything is made of.",
        "His answer was water.",
        "He was wrong, but the question was the beginning of science.",
    ],
    voice=Voices.BritishNarrator,
    project=project,
)

A list of lists. Each inner list is a scene, and each string inside it is a clause: a part of the scene you may want to place or pause on independently. A quote followed by its attribution is the classic case. You get word, clause and scene timing.

create_voiceover(
    [
        ["I have missed more than nine thousand shots in my career."],
        ["Twenty-six times I was trusted to take the game-winning shot, and missed."],
        ["And that is why I succeed.", "Michael Jordan"],
    ],
    voice=Voices.AmericanNarrator,
    project=project,
)

A scene can also be None, which marks a silent scene: a moment with a visual but no narration. It keeps its place in .scenes so the indexes still line up with your list of visuals.

One recording, not many

The whole script is read in a single take. This is deliberate: reading each scene separately makes the voice drift between them, clips the ends of words, and can sound like a different narrator from one scene to the next. One continuous read keeps the timbre steady.

The gaps between scenes, the pause before the first word and the hold after the last, are added later when you place the voiceover on the timeline, not baked into the audio. That is what tile() does, and it is why the same recording can be laid out with different pacing without re-recording. See Timing.

Voices

VoiceCharacter
Voices.AmericanNarratorA warm, measured American male voice. Documentary narration.
Voices.BritishNarratorA calm, RP-accented British voice. Quotes, history, anything that wants gravity.
Voices.AmericanExplainerBrighter and quicker. Explainers, news-style scripts.
Voices.ElevenLabsID("...")Any ElevenLabs voice, by its voice ID, including voices you have cloned or designed in your own ElevenLabs account.

The presets are ElevenLabs voices chosen and tuned to stay stable across a long read. A voice is part of the cache key, so switching voices re-generates the recording.

The full signature

create_voiceover(
    script,                    # str | list[str] | list[list[str]]; None marks a silent scene
    voice=Voices.BritishNarrator,
    *,
    project,
    name=None,                 # a label for the cached files; renaming doesn't regenerate
    take=1,                    # bump to re-record the same script
) -> AIVoiceover

AIVoiceover and the transcript returned by create_stt share the same interface, TimestampedVoice, so anything that works on one works on the other. See Creating speech-to-text.