muvid.lyricvid.tools

The SSOT verbs for the lyric-video subgenre.

Plain functions, JSON-able arguments in, JSON-able dict out, and deliberately agnostic about CLI/MCP/HTTP/agent — following ir.tools, which muvid’s design record already names as the reference for this shape. The CLI (python -m muvid.lyricvid), the MCP tools, the shipped skill and a production frontend all call these, so there is one implementation and one place a behaviour changes.

Nothing here imports a renderer, an LLM client or numpy at module scope, so import muvid.lyricvid.tools stays cheap and safe.

Functions

catalog()

Every installed subgenre, without importing any renderer.

vocabulary()

The closed vocabularies a treatment may draw on.

treatment_schema()

JSON Schema for a treatment spec — also the model's output constraint.

analyze_song(audio, *[, lyrics, subtitles, ...])

Measure a song's words and report what a director needs to know.

propose_treatments(audio, *[, lyrics, ...])

Propose n treatments, ranked, each with its rationale.

validate_treatment(treatment)

Validate a treatment, and return the repaired version alongside.

render_lyric_video(audio, output, *[, ...])

Render a lyric video.

export_captions(timed_text, output_dir, ...)

Write one caption file per language for a saved timing (muvid#145).

muvid.lyricvid.tools.analyze_song(audio, *, lyrics=None, subtitles=None, project=None, aligner=None, max_lines=40)[source]

Measure a song’s words and report what a director needs to know.

Returns the section/line structure, the duration, the words-per-second the treatment has to keep up with, and — importantly — whether the word times were measured or interpolated from line times. A caller that ignores that flag will ship a video that looks subtly out of sync.

Return type:

dict[str, Any]

muvid.lyricvid.tools.catalog()[source]

Every installed subgenre, without importing any renderer.

Return type:

dict[str, Any]

>>> c = catalog()
>>> 'lyric-video' in [s['slug'] for s in c['subgenres']]
True
muvid.lyricvid.tools.export_captions(timed_text, output_dir, tracks, *, offset_s=0.0, lead_s=None, min_s=None, tail_s=None, stem='captions', fmt='srt')[source]

Write one caption file per language for a saved timing (muvid#145).

timed_text is a TimedText JSON file (e.g. glyphs’ output); tracks maps a BCP-47 tag to a named transform ("hepburn", "original") or a {line text: caption} mapping. offset_s shifts every caption — by a title card put in front of the video, say. Returns {"tracks": {language: {"path", "n_captions"}}}; each path goes straight into yb.youtube.CaptionTrack(path, language).

Return type:

dict[str, Any]

muvid.lyricvid.tools.propose_treatments(audio, *, lyrics=None, subtitles=None, project=None, n=3, title='', reference_image=None, use_llm=False, model=None)[source]

Propose n treatments, ranked, each with its rationale.

use_llm=False (the default) runs the heuristic director: no network, no API key, no cost. use_llm=True asks a model, and the returned payload then carries a cost block — unknown cost is reported as unknown, never as zero.

Return type:

dict[str, Any]

muvid.lyricvid.tools.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]

muvid.lyricvid.tools.treatment_schema()[source]

JSON Schema for a treatment spec — also the model’s output constraint.

Return type:

dict[str, Any]

>>> treatment_schema()['type']
'object'
muvid.lyricvid.tools.validate_treatment(treatment)[source]

Validate a treatment, and return the repaired version alongside.

A treatment that is nearly right is repaired rather than rejected, because a mechanical substitution renders something good now where a retry costs a round trip and may fail the same way. Every substitution is reported.

Return type:

dict[str, Any]

>>> r = validate_treatment({'scenes': [{'archetype': 'swirl'}]})
>>> r['valid'], r['repairs']
(False, ["scenes[0].archetype 'swirl' -> 'one_word_centred'"])
muvid.lyricvid.tools.vocabulary()[source]

The closed vocabularies a treatment may draw on.

One copy, read by the prompt, the JSON Schema, the UI and the docs.

Return type:

dict[str, Any]

>>> v = vocabulary()
>>> sorted(v)[:2]
['archetypes', 'cases']