muvid

muvid — tools to make music videos.

Public surface (also the CLI verbs):

init_project, transcribe_song, align_lyrics, add_character, add_character_images, generate_character_images, curate_character, add_environment, render_environment, write_script, parse_script, render_shot, render, compose, status,

Project model:

MusicVideoProject(root) — folder-backed project facade ProjectSpec, SongInfo, SectionSpec, ShotSpec, CharacterRef, EnvironmentRef

class muvid.CharacterRef(*, name: str, description: str = '')[source]

Pointer 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: str, description: str = '')[source]

Pointer to an environment folder under environments/<name>/.

class muvid.MusicVideoProject(root: str | Path)[source]

Filesystem-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: str | Path, *, title: str = '', song_path: str | Path | None = None, copy_song: bool = True, exist_ok: bool = False) MusicVideoProject[source]

Create a fresh project directory.

If song_path is given, the audio is copied (or moved if copy_song=False) into song/, probed for duration, and registered in project.json.

log_decision(kind: str, **payload: Any) None[source]

Append a one-line JSON entry to .muvid/decisions.jsonl.

set_song(source: str | Path, *, copy: bool = True) SongInfo[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 in project.json.

update_spec(**changes: Any) ProjectSpec[source]

Read, replace, write. Returns the new spec.

class muvid.ProjectSpec(*, schema_version: int = 1, title: str = '', song: SongInfo | None = None, characters: tuple[CharacterRef, ...] = (), environments: tuple[EnvironmentRef, ...] = (), sections: tuple[SectionSpec, ...] = (), shots: tuple[ShotSpec, ...] = (), global_style: str = '', notes: str = '')[source]

The top-level project SSOT, persisted as project.json.

class muvid.SectionSpec(*, id: str, start_s: float, end_s: float, label: str = '', energy: str = '', mood: str = '')[source]

A non-overlapping span of the song with a label.

label is free-form (“intro”, “verse”, “chorus”, “bridge”, “outro”) so users can use whatever taxonomy fits their song.

class muvid.ShotSpec(*, id: str, start_s: float, end_s: float, section_id: str = '', render_strategy: Literal['lipsync', 'image_to_video', 'text_to_video', 'animation', 'still'] = 'image_to_video', environment: str = '', characters: tuple[str, ...] = (), description: str = '', camera: str = '', framing: str = 'medium', notes: str = '')[source]

A timeline-locked visual unit of the music video.

[start_s, end_s) is half-open. Shots within a project are sorted by start_s and 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: str, duration_s: float, sample_rate: int = 0, bitrate: int = 0, bpm: float | None = None)[source]

Metadata for the master audio file.

muvid.align_lyrics(root: str | Path, *, aligner: str = 'scribe-greedy', **aligner_kwargs) str[source]

Build lyrics/alignment.annot from transcript + lyrics.md.

aligner selects the alignment strategy (see muvid.align.list_aligners()); extra kwargs are forwarded to the chosen aligner. Defaults to scribe-greedy.

Returns the path to the alignment store.

muvid.init_project(root: str | Path, *, title: str = '', song: str | Path | None = None) str[source]

Create a new music video project. Returns the absolute root path.

muvid.render(root: str | Path, *, quality: str = 'balanced', force: bool = False, budget: float | None = None) list[str][source]

Render every shot. Returns the produced mp4 paths.

budget (USD): when set, refuses to start if estimate_render_cost() exceeds it. Pass None to skip the gate entirely.

muvid.status(root: str | Path) dict[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.

muvid.transcribe_song(root: str | Path, *, api_key: str | None = None) str[source]

Run ElevenLabs Scribe on the project’s song.

Writes the raw response to lyrics/transcript.json and a draft lyrics/lyrics.md with auto-detected line breaks. The user is expected to edit lyrics.md to fix mishears and add real section tags. Returns the path to the lyrics markdown.