Creating subtitles

Subtitles are text on screen that follows what is being said. In Imaginary Arts they are built from timestamped words: every voiceover and every transcript knows when each word starts and ends, so captions land on the exact frame a word is spoken. You never type caption timings by hand.

From words to captions

Captions are added with add_dynamic_text and a DynamicText:

from imaginaryarts import DynamicText, DynamicTextFlow, TextStyle, Fonts, Color, Shadow, Position

video.add_dynamic_text(DynamicText(
    words=placed.words,
    text_style=TextStyle(
        font=Fonts.PlayfairDisplay,
        size=70,
        color=Color(system="white"),
        shadow=Shadow.md,
        bold=True,
    ),
    flow=DynamicTextFlow.WordByWord(max_words=30, max_lines=1),
    position=Position.MIDDLE,
))

The four parts:

ParameterWhat it is
wordsThe words to caption, with absolute timestamps in the video.
text_styleFont, size, color, shadow and so on. See Typography.
flowHow the words appear on screen.
positionWhere on the frame the captions sit.

Where the words come from

words must be timestamped in the video, not in the raw audio file. That matters for a voiceover, which is one long recording that you then place on the timeline in pieces; a word that is 2.0 seconds into the recording may be 5.3 seconds into the video.

  • After placing a voiceover with tile() or add_voiceover(...), each placed scene has .words with video timestamps. Pass those.
  • A transcript from create_stt is already in video time when the footage it was transcribed from starts at zero, which is the usual case. Pass voiceover.words directly.

See Timing for how placement works.

Flows

FlowBehaviour
DynamicTextFlow.WordByWord(max_words=30, max_lines=1)Every word of a group sits in its final position from the start and fades in the moment it is spoken. The reference look for short videos.
DynamicTextFlow.SentenceBySentence(max_words=30, max_lines=1)Shows a whole sentence at a time, switching when the next sentence begins. Calmer, and easier to read for longer speech; give it max_lines=2 for real footage.

max_words caps how many words are on screen in one group and max_lines how many lines that group may wrap to. Groups are cut at natural points so a group never ends mid-phrase where it can be avoided.

Each scene's last caption group holds on screen until the next scene's first word, so there are no flickering gaps between them.

Positions

Position.TOP, Position.MIDDLE and Position.BOTTOM place the caption block at 8%, 46% and 92% of the frame height. MIDDLE is the conventional caption line for vertical video; BOTTOM is where a watermark or title usually goes, so keep the two apart.

Captions from your own footage

For a talking-head video the flow is: transcribe the footage, then caption it from the transcript.

footage = VideoFile("assets/interview.mp4")
speech = create_stt(footage.audio, project=project)

video.add_video(footage, at=0)
video.add_dynamic_text(DynamicText(
    words=speech.words,
    text_style=TextStyle(font=Fonts.Barlow, size=60, color=Color(system="white"), bold=True),
    flow=DynamicTextFlow.SentenceBySentence(max_lines=2),
    position=Position.BOTTOM,
))

See Creating speech-to-text.

Styling notes

  • Captions are rendered by the video encoder as real text with proper font rendering, not as images pasted on top, so they stay crisp at any size.
  • By default a TextStyle renders captions in lowercase, which is the reference look. Set lowercase=False to keep your casing.
  • A drop shadow (Shadow.sm, .md or .lg) is what keeps white text readable over bright footage. There is no outline; the shadow does that job.