muvid.lyricvid

Lyric video (kinetic typography) — muvid’s first subgenre plugin.

Turn a song into a typographic music video: the words appear in time with the singing, laid out by an archetype chosen for the song.

from muvid.lyricvid import tools tools.render_lyric_video(‘song.wav’, ‘out.mp4’, lyrics=’lyrics.md’)

or from the command line:

python -m muvid.lyricvid render song.wav out.mp4 --lyrics lyrics.md

The pipeline is four seams, each with a default that genuinely works:

timed_text

measured word times — reuses muvid’s own aligner and lacing store rather than growing a second transcription path.

director

the treatment decision. Default is a heuristic that reads the lyrics and costs nothing; an LLM creative director is opt-in.

scene

every position and time, computed in Python from measurement. No model ever emits a coordinate or a timestamp.

renderer

ass by default (frame-exact, no browser, leaves an editable subtitle file); web for effects ASS cannot express.

This module is a PEP 562 lazy facade, like muvid/__init__.py: importing it pulls nothing heavy, and each attribute is resolved on first use.

class muvid.lyricvid.Canvas(*, width=1920, height=1080, fps=30)[source]

Bases: object

Output geometry. Sizes in the scene are relative to height.

class muvid.lyricvid.Scene(*, canvas, duration, background, cues, typography, meta=<factory>)[source]

Bases: object

Everything a renderer needs, and nothing it has to interpret.

meta: dict[str, Any]

Provenance, for reporting and for tests.

class muvid.lyricvid.TimedText(*, sections, duration=0.0, source='unknown')[source]

Bases: object

The whole song’s text, timed.

>>> tt = from_words([('hello', 0.0, 0.5), ('world', 0.5, 1.0)], duration=1.0)
>>> [w.text for w in tt.words()]
['hello', 'world']
>>> tt.sections[0].label
'*'
property measured: bool

True when every word time was measured rather than interpolated.

False for an empty text: “all of nothing was measured” is the kind of vacuous truth that reads as reassurance in a report.

source: str

Where the timing came from, for provenance and for honest reporting.

class muvid.lyricvid.TreatmentSpec(*, spec_version='1.0', title='', direction=<factory>, scenes=<factory>)[source]

Bases: object

A complete, renderable treatment.

>>> s = TreatmentSpec()
>>> validate(s)
[]
>>> TreatmentSpec.from_dict(s.to_dict()) == s
True
classmethod from_dict(d)[source]

Build from a plain mapping — TOTAL over what a model or a remote caller sends.

Missing keys default. Wrong-shaped values are coerced toward the field’s type rather than raised on: a string where a list was expected becomes a one-element list (applies_to: "chorus" used to become ('c','h','o','r','u','s')), a numeric string becomes a number, an unknown key is dropped, a None sub-object is the default. What cannot be coerced falls to the default and repair() reports the enum-level substitutions afterwards.

Return type:

TreatmentSpec

>>> s = TreatmentSpec.from_dict({'scenes': [None, {'applies_to': 'chorus',
...     'timing': 'fast', 'shape': 'circle'}],
...     'direction': {'palette': {'foo': 1, 'bg': '#000000'},
...                   'motion_vocabulary': 'pop',
...                   'typography': {'weight': '700', 'tracking': None}}})
>>> s.scenes[1].applies_to, s.direction.motion_vocabulary
(('chorus',), ('pop',))
>>> s.direction.typography.weight, s.direction.typography.tracking
(700, 0.0)
>>> s.direction.palette.bg, s.scenes[1].shape
('#000000', None)
to_dict()[source]

A JSON-native dict: tuples become lists, so what this emits is exactly what json_schema() validates and what a file round-trips.

Return type:

dict[str, Any]

muvid.lyricvid.compile_scene(treatment, timed_text, *, canvas=None)[source]

Turn a treatment plus timed text into a fully-resolved Scene.

Every number in the result was computed here, from measurement. Nothing the model wrote reaches a renderer as a coordinate or a time.

Return type:

Scene

>>> from muvid.lyricvid.timed_text import from_words
>>> tt = from_words([('one', 0.0, .5), ('two', .5, 1.0)], duration=1.0)
>>> s = compile_scene(spec_mod.TreatmentSpec(), tt)
>>> len(s.cues), s.cues[0].text
(2, 'one')
muvid.lyricvid.render_lyric_video(audio, output, *, lyrics=None, subtitles=None, project=None, timed_text=None, treatment=None, renderer='auto', title='', persona=None, aligner=None, width=1920, height=1080, fps=30, workdir=None, captions=None)[source]

Render a lyric video. The one verb that produces a file.

captions ({language: transform}, or that as JSON) also writes one .srt per language beside it — see export_captions().

Everything else in this module exists so that a caller can decide what to render before paying for it.

Return type:

dict[str, Any]

Modules

captions

Caption tracks: one subtitle file per language, timed per sung line (muvid#145).

director

The creative director — a song in, one or more TreatmentSpecs out.

glyph_align

Per-glyph timing: when was each written character sung? (muvid#142)

manifest

The lyric-video subgenre's manifest — muvid's own first plugin.

pictures

Pictures for a lyric video: a mosaic that builds up as its words are sung (muvid#151).

pipeline

The lyric-video pipeline — the one path from a song to a finished video.

render_ass

The default lyric-video renderer: a Scene as ASS.

render_web

The web backend: a Scene as a deterministic HTML page, screenshotted frame by frame and muxed with the song.

scene

The renderer-neutral scene — every number computed, no renderer opinions.

shape

Packing words INSIDE a shape — the shape-word-cloud construction.

spec

The treatment spec — what a director (human or model) decides, as data.

timed_text

The timed text tree — song → sections → lines → words, with measured times.

tools

The SSOT verbs for the lyric-video subgenre.