an.audio.lipsync#
Lip-sync provider protocol + viseme dataclasses.
Viseme conventions are renderer-agnostic in the IR; concrete providers in later phases (Rhubarb, Azure, MFA) emit their own letter/number encodings, which the cutout adapter then maps to mouth-slot attachments.
Two protocols live here:
LipSyncProvider— the head-to-toe pipeline (“audio + transcript → visemes”). Owns its own transcription if it needs one. Most cutout callers go through this.WordTimingProvider— the narrower “audio → word timings” contract. Lets external callers (e.g.muvid, with its own lyric alignment store) skip a redundant transcription pass and feed timings directly intoWordTimingsLipSync.
Module Attributes
One word's slice in time. |
Functions
|
Distribute viseme keyframes across word boundaries. |
Classes
|
Audio + transcript → aligned viseme track. |
The lip-sync provider of a core without a genre: no mouth shapes at all. |
|
|
A single mouth-shape keyframe. |
|
Aligned viseme sequence produced by a LipSyncProvider. |
|
Audio → |
- class an.audio.lipsync.LipSyncProvider(*args, **kwargs)[source]#
Bases:
ProtocolAudio + transcript → aligned viseme track.
A provider that fills
VisemeTrack.wordsalso declaresemits_word_timings = True(a plain class attribute, absent means False). The audio pipeline reads it to decide whether a line whoseword_timingsare missing needs re-alignment — without the flag, a provider that cannot supply words would re-align every line forever.
- class an.audio.lipsync.NullLipSync[source]#
Bases:
objectThe lip-sync provider of a core without a genre: no mouth shapes at all.
A genre that animates mouths (
cutan) registers real providers (offline,rhubarb,whisper); without one, a line’s audio is still synthesized and the track it gets is empty – nothing draws a mouth anyway.>>> NullLipSync().align(AudioClip(bytes_=b"", duration=0.5), "hi").duration 0.5
- class an.audio.lipsync.Viseme(time, code, intensity=1.0)[source]#
Bases:
objectA single mouth-shape keyframe.
- class an.audio.lipsync.VisemeTrack(visemes=<factory>, convention='rhubarb', duration=0.0, words=None)[source]#
Bases:
objectAligned viseme sequence produced by a LipSyncProvider.
wordscarries the word timings the provider aligned from, when it had any (whisper,WordTimingsLipSync);Nonefor providers that never see words (offline, Rhubarb — whose JSON is mouth cues only). Retained since an#96 rather than discarded after the viseme conversion: captions (Wave 8) and any consumer that wants to know which word a mouth shape belongs to read them from the IR.
- an.audio.lipsync.WordTiming#
One word’s slice in time. Tuple form keeps providers cheap.
- class an.audio.lipsync.WordTimingProvider(*args, **kwargs)[source]#
Bases:
ProtocolAudio →
[(word, start_s, end_s), ...].Implementations may run a transcriber (Whisper / Scribe) or look up pre-computed timings (
muvid’s lacing store). Returned tuples are expected to be in ascending start-time order; gaps between words are fine and represent silence the lipsync provider should rest through.
- an.audio.lipsync.word_timings_to_visemes(words, *, total_duration, char_to_viseme, rest_viseme='X', min_gap_for_rest=0.2)[source]#
Distribute viseme keyframes across word boundaries.
Used by
WhisperLipSyncandWordTimingsLipSync. The algorithm: per word, walk its characters in order, look up each character’s viseme code, dedupe consecutive identical codes, and space the resulting keyframes evenly across the word’s[start, end]interval. In gaps wider thanmin_gap_for_rest— between two words, or after the last one beforetotal_duration— insert a single rest keyframe just after the previous word ended.Times are clamped to
[0, total_duration]since some transcribers occasionally round the last word’s end past the audio’s actual length.