-
Notifications
You must be signed in to change notification settings - Fork 3
LIP File Format
LIP (Lip Synchronization) files drive mouth animation for voiced dialogue. Each file contains a compact series of keyframes that map timestamps to discrete viseme (mouth shape) indices so that the engine can interpolate character lip movement while playing the companion WAV line.
- LIP files are always binary (
"LIP V1.0"signature) and contain only animation data. - They are paired with WAV voice-over resources of identical duration; the LIP
lengthfield must match the WAVdataplayback time for glitch-free animation. - Keyframes are sorted chronologically and store a timestamp (float seconds) plus a 1-byte viseme index (0–15).
- The layout is identical across
vendor/reone,vendor/xoreos,vendor/Kotor.NET,vendor/KotOR.js, andvendor/mdlops, so the header/keyframe offsets below are cross-confirmed against those implementations.
Implementation: Libraries/PyKotor/src/pykotor/resource/formats/lip/
| Name | Type | Offset | Size | Description |
|---|---|---|---|---|
| File Type | char[4] | 0x00 | 4 | Always "LIP "
|
| File Version | char[4] | 0x04 | 4 | Always "V1.0"
|
| Sound Length | float32 | 0x08 | 4 | Duration in seconds (must equal WAV length) |
| Entry Count | uint32 | 0x0C | 4 | Number of keyframes immediately following |
Reference: vendor/reone/src/libs/graphics/format/lipreader.cpp:27-42
Keyframes follow immediately after the header; there is no padding.
| Name | Type | Offset (per entry) | Size | Description |
|---|---|---|---|---|
| Timestamp | float32 | 0x00 | 4 | Seconds from animation start |
| Shape | uint8 | 0x04 | 1 | Viseme index (0–15) |
- Entries are stored sequentially and must be sorted ascending by timestamp.
- Libraries average multiple implementations to validate this layout (
vendor/reone,vendor/xoreos,vendor/KotOR.js,vendor/Kotor.NET).
Reference: vendor/KotOR.js/src/resource/LIPObject.ts:93-146
KotOR reuses the 16-shape Preston Blair phoneme set. Every implementation agrees on the byte value assignments; KotOR.js only renames a few labels but the indices match.
| Value | Shape | Description |
|---|---|---|
| 0 | NEUTRAL | Rest/closed mouth |
| 1 | EE | Teeth apart, wide smile (long “ee”) |
| 2 | EH | Relaxed mouth (“eh”) |
| 3 | AH | Mouth open (“ah/aa”) |
| 4 | OH | Rounded lips (“oh”) |
| 5 | OOH | Pursed lips (“oo”, “w”) |
| 6 | Y | Slight smile (“y”) |
| 7 | STS | Teeth touching (“s”, “z”, “ts”) |
| 8 | FV | Lower lip touches teeth (“f”, “v”) |
| 9 | NG | Tongue raised (“n”, “ng”) |
| 10 | TH | Tongue between teeth (“th”) |
| 11 | MPB | Lips closed (“m”, “p”, “b”) |
| 12 | TD | Tongue up (“t”, “d”) |
| 13 | SH | Rounded relaxed (“sh”, “ch”, “j”) |
| 14 | L | Tongue forward (“l”, “r”) |
| 15 | KG | Back of tongue raised (“k”, “g”, “h”) |
Reference: Libraries/PyKotor/src/pykotor/resource/formats/lip/lip_data.py:50-169
-
Interpolation: The engine interpolates between consecutive keyframes; PyKotor exposes
LIP.get_shapes()to compute the left/right visemes plus blend factor.
Reference:Libraries/PyKotor/src/pykotor/resource/formats/lip/lip_data.py:342-385 -
Sorting: When adding frames, PyKotor removes existing entries at the same timestamp and keeps the list sorted.
Reference:Libraries/PyKotor/src/pykotor/resource/formats/lip/lip_data.py:305-323 -
Duration Alignment: LIP
lengthis updated to the max timestamp so exported animations stay aligned with their WAV line.
Reference:Libraries/PyKotor/src/pykotor/resource/formats/lip/lip_data.py:267-323 -
Generation: Automated pipelines (MDLOps, KotORBlender) map phonemes to visemes via
LIPShape.from_phoneme(), and the same mapping table appears in the vendor projects referenced above to keep authoring tools consistent.
-
Binary Reader:
Libraries/PyKotor/src/pykotor/resource/formats/lip/io_lip.py -
Data Model:
Libraries/PyKotor/src/pykotor/resource/formats/lip/lip_data.py - Reference Implementations:
The references above implement the same header layout and keyframe encoding, ensuring PyKotor stays compatible with the other toolchains.