muvid¶
muvid — tools to make music videos.
Two independent halves:
Narrative pipeline (needs the AI extras:
falaw,lacing,lookbook): transcribe a song, align lyrics, define characters and environments, write a shot script, render and compose. The verbs below are also the CLI. Project model:MusicVideoProjectand the schema dataclasses.Hosted productions (
muvid.Project, needsnw): a music video or a lyric video a host (the reelee studio) places and serves — annw.Projectwhose footage operations aremuvid.footage.service.Visualizer (
muvid.visualize, needs onlyffmpeg+mixing): turn a song and a cover into a still / Ken Burns / audio-reactive music video, plus a thumbnail. Deterministic, no AI, no network.>>> from muvid.visualize import render_audio_video >>> render_audio_video("song.wav", image="cover.png")
The narrative-pipeline names are imported lazily so that import muvid (and
hence import muvid.visualize) does not require the heavy AI extras — the
import of a given name only pulls its dependencies when you actually use it.
- class muvid.CharacterRef(*, name, description='')[source]¶
Bases:
objectPointer to a character folder under
characters/<name>/.The folder contains the canonical card.json + curated reference images. We only carry the name + a quick description here so the project SSOT stays small.
- class muvid.EnvironmentRef(*, name, description='')[source]¶
Bases:
objectPointer to an environment folder under
environments/<name>/.
- class muvid.MusicVideoProject(root)[source]¶
Bases:
objectFilesystem-backed music video project.
All write methods touch the disk immediately; readers always re-read the SSOT (no in-memory cache) so external edits are picked up.
- classmethod init(root, *, title='', song_path=None, copy_song=True, exist_ok=False)[source]¶
Create a fresh project directory.
If
song_pathis given, the audio is copied (or moved ifcopy_song=False) intosong/, probed for duration, and registered inproject.json.- Return type:
- log_decision(kind, **payload)[source]¶
Append a one-line JSON entry to
.muvid/decisions.jsonl.- Return type:
- set_song(source, *, copy=True)[source]¶
Register an audio file as this project’s song.
The file is copied (or moved) to
song/, probed for duration with ffprobe, and recorded inproject.json.- Return type:
- class muvid.ProjectSpec(*, schema_version=1, title='', song=None, characters=(), environments=(), sections=(), shots=(), global_style='', notes='')[source]¶
Bases:
objectThe top-level project SSOT, persisted as
project.json.
- class muvid.SectionSpec(*, id, start_s, end_s, label='', energy='', mood='')[source]¶
Bases:
objectA non-overlapping span of the song with a label.
labelis free-form (“intro”, “verse”, “chorus”, “bridge”, “outro”) so users can use whatever taxonomy fits their song.
- class muvid.ShotSpec(*, id, start_s, end_s, section_id='', render_strategy='image_to_video', environment='', characters=(), description='', camera='', framing='medium', notes='')[source]¶
Bases:
objectA timeline-locked visual unit of the music video.
[start_s, end_s)is half-open. Shots within a project are sorted bystart_sand should be non-overlapping (the validator warns otherwise — overlap can be intentional for transitions but isn’t supported by the basic compositor).
- class muvid.SongInfo(*, audio_path, duration_s, sample_rate=0, bitrate=0, bpm=None)[source]¶
Bases:
objectMetadata for the master audio file.
- muvid.align_lyrics(root, *, aligner='scribe-greedy', **aligner_kwargs)[source]¶
Build
lyrics/alignment.annotfrom transcript + lyrics.md.alignerselects the alignment strategy (seemuvid.align.list_aligners()); extra kwargs are forwarded to the chosen aligner. Defaults toscribe-greedy.Returns the path to the alignment store.
- Return type:
- muvid.default_project_root(name)[source]¶
Where a project called
namebelongs by default, as an absolute path.rootis a required positional on every other verb here and onmuvid.project.MusicVideoProject, deliberately — a pipeline that guessed which project it was operating on would be worse than one that asks. But required everywhere left no code answering where a NEW project should go, so the only written-down answer lived in prose: themuvidskill and theREADMEboth said~/muvid/<song-stem>, which put a real project’s 36 MB in an app-named directory under$HOMEthat nothing owns. This is that answer in code, where it can be tested and where callers — the skill, the README, the CLI, a script — can ask instead of restating it. It creates nothing.The CLI’s
serveis the one exception to “required”: it defaultsrootto the cwd. Seemuvid/paths.py— the default is right, and it was the UI’s missing is-this-a-project precondition that made it write.- Return type:
- muvid.init_project(root, *, title='', song=None)[source]¶
Create a new music video project. Returns the absolute root path.
- Return type:
- muvid.render(root, *, quality='balanced', force=False, budget=None, allow_unpriced=False)[source]¶
Render every shot. Returns the produced mp4 paths.
budget(USD): when set, refuses to start ifestimate_render_cost()exceeds it — **or if any part of the project could not be priced at all**. PassNone(CLI:--budget=-1) to skip the gate entirely.allow_unpriced: proceed despite unpriceable work, after reading what it is. The gate is otherwise TWO conditions, and the second is the one muvid#47 was filed about: a price this code could not determine contributes nothing tototal_amount, so comparing the number alone let an unpriceable shot clear any budget. Unknown is not zero.The escape exists because a threshold and an approval are different things. A hard refusal with no way past it would make
--budgetunusable for a project containing one exotic model — so the default refuses, and a caller who has read the names can accept them. What it must never become is a silent default.
- muvid.render_audio_video(audio, image=None, *, visual='auto', saveas=None, size=(1920, 1080), fps=24, title=None, layout=None, title_style=None, normalize=False, loudness=None, crf=18, preset='medium', audio_bitrate='384k', gop_seconds=2.0, options=None, workdir=None)[source]¶
Render
audiointo a video, usingvisualfor the picture.The video is exactly as long as the audio, 16:9, H.264/yuv420p + AAC — what YouTube asks for. With
normalize=Truethe audio is brought to a fixed EBU R128 loudness with a two-passloudnorm, which is what makes a batch of songs play back at a consistent level.- Parameters:
audio (
str|Path) – The song (.wavis preferred when you have it — YouTube re-encodes regardless, so give it the cleanest input).image (
str|Path|None) – Cover art. Used for the picture, and composed onto a 16:9 canvas.visual (
Union[str,Callable[[VisualContext],VisualPlan|Path|str]]) – A registered strategy name ("still","ken_burns","cqt","bars","spectrum","waves","scope"),"auto", or any callable (seemuvid.visualize.visuals).saveas (
str|Path|None) – Output path (default:<audio-stem>.mp4).fps (
int) – Frame rate.layout (
CoverLayout|None) – How the cover sits on the canvas.title_style (
TitleStyle|None) – How the title is drawn.normalize (
bool) – Loudness-normalize the audio (two-pass EBU R128).loudness (
Loudness|None) – The loudness target; a YouTube-appropriate default is used when omitted.gop_seconds (
float) – Encoder knobs.options (
dict|None) – Strategy-specific options, passed to the visual.workdir (
str|Path|None) – Where intermediates go (a temporary directory by default).
- Return type:
- Returns:
A
RenderResult.- Raises:
ValueError –
sizehas an odd dimension — H.264 at yuv420p (the only pixel format every player decodes) cannot encode one.
- muvid.status(root)[source]¶
Return a summary dict of the project’s current state.
Useful for the skill / UI to show the user where they are in the pipeline. No side effects.
Returns a structured shape with stage progression, per-shot render status, and (when an alignment store exists) a word-confidence histogram. Stable enough to be programmatic; pass through
format_status()for human-readable text.- Return type:
- muvid.transcribe_song(root, *, api_key=None)[source]¶
Run ElevenLabs Scribe on the project’s song.
Writes the raw response to
lyrics/transcript.jsonand a draftlyrics/lyrics.mdwith auto-detected line breaks. The user is expected to editlyrics.mdto fix mishears and add real section tags. Returns the path to the lyrics markdown.- Return type:
Modules
Lyric → audio alignment. |
|
Character cards + reference image curation via lookbook. |
|
Choreo — event-driven visual music, muvid's second subgenre plugin. |
|
Compose all rendered shots into the final music video. |
|
Adapters between muvid's SSOT and sibling-package shapes. |
|
Cost estimation for a muvid project. |
|
Environment cards + canonical establishing image generation. |
|
Surface fal progress events into the muvid project. |
|
Top-level facade — the verbs the CLI / skill / UI all call. |
|
Footage-aligned music video — align several device recordings of one song, assemble. |
|
Bring a finished production into a host's projects dir as a |
|
Lyrics — transcription and markdown round-trip. |
|
Lyric video (kinetic typography) — muvid's first subgenre plugin. |
|
muvid MCP server — the |
|
Montage — a beat-cut video from a pool of stills and clips (subgenre plugin). |
|
The ONE place muvid decides where its data lives. |
|
Project facade — folder layout, persistence, and a small |
|
Render dispatch — turn a single ShotSpec into |
|
Schema for an muvid project — the SSOT data shapes. |
|
Script (screenplay) markdown ↔ ShotSpec list. |
|
muvid subgenres — the plugin surface for another kind of video. |
|
Minimal local web UI for an muvid project. |
|
Turn a song (+ optional cover) into a visualizer music video. |