srctools.choreo

Text format is the .vcd format saved by Faceposer, and includes a number of additional attributes to save the editor configuration. Binary format is contained in the scenes.image file, only storing the data required to actually play the scene.

Note that scenes from scenes.image are not named - they are looked up by a hash/checksum of the VCD filename, so the filenames must already be known to identify an entry.

Basic usage

An example for reading and writing VCDs and scenes.image:

from srctools.choreo import *
from srctools.tokenizer import Tokenizer

with open('some_scene.vcd') as file:
    scene = Scene.parse_text(Tokenizer(file))
with open('some_scene_copy.vcd', 'w') as file:
    scene.export_text(file)

with open('scenes.image', 'rb') as file:
    image = parse_scenes_image(file)
with open('new_scenes.image', 'wb') as file:
    save_scenes_image_sync(file, image)

Scenes.image

Entry instances represent the additional metadata contained in an image file.

class srctools.choreo.Entry(
filename: str,
checksum: CRC,
duration_ms: int,
last_speak_ms: int,
sounds: list[str],
data: Scene | tuple[bytes, list[str]],
)

An entry in scenes.image, containing useful metadata about a scene.

The data attribute may be accessed to parse the scene.

filename: str

The filename of the choreo scene. If parsed from scenes.image, only a CRC is available. When set, this automatically recalculates the checksum.

checksum: CRC
duration_ms: int
last_speak_ms: int
sounds: list[str]
classmethod from_scene(filename: str, scene: Scene) Self

Produce an entry from an existing scene.

property duration: float

Return the duration in seconds.

property last_speak: float

Return the last-speak time in seconds.

property data: Scene

The scene for this entry. When accessed this will parse the data if required.

srctools.choreo.checksum_filename(filename: str) CRC

Normalise the filename, then checksum it.

type srctools.choreo.CRC = int

This typing.NewType represents computed CRC checksums.

srctools.choreo.parse_scenes_image(
file: FileRSeek[bytes],
) dict[CRC, Entry]

Parse the scenes.image file, extracting all the choreo data.

srctools.choreo.save_scenes_image_sync(
file: FileWBinarySeek,
scenes: dict[CRC, Entry] | Iterable[Entry],
*,
version: Literal[2, 3] = 3,
encoding: str = 'latin1',
) None

Write a new scenes.image file.

Binary scenes use a common string pool, meaning that all unparsed scenes must share their pool to be copied over directly.

Scene Tree

Scenes consist of many Events, organised into Channels which themselves are directed at specific Actor NPCs.

class srctools.choreo.Scene(
*,
events: list[Event] = ...,
actors: list[Actor] = ...,
ramp: Curve = ...,
ignore_phonemes: bool = False,
text_crc: int = 0,
map_name: str = '',
fps: int = 60,
time_zoom_lookup: dict[int, int] = ...,
use_frame_snap: bool = False,
scale_settings: dict[str, str] = ...,
)

A choreo scene.

events: list[Event]
actors: list[Actor]
ramp: Curve
ignore_phonemes: bool
text_crc: int
map_name: str
fps: int
time_zoom_lookup: dict[int, int]
use_frame_snap: bool
scale_settings: dict[str, str]
classmethod parse_binary(
file: FileR[bytes],
string_pool: list[str],
) Self

Parse the BVCD form of this data.

export_binary(add_to_pool: Callable[[str], int]) bytes

Write out BVCD data for this scene.

classmethod parse_text(tokenizer: BaseTokenizer) Self

Parse a scene from a text VCD file.

This does not calculate the CRC value.

export_text(file: FileWText) None

Write this to a text VCD file.

iter_events(
type_filter: EventType | None = None,
) Iterator[Event]

Iterate over all events, including those in actors.

If a filter is provided, only events of that type are produced.

duration(type_filter: EventType | None = None) float

Calculate the duration of the events in the scene.

If a filter is provided, only those events are accepted.

for ... in used_sounds() Iterator[str]

Yield sounds used by events.

class srctools.choreo.Actor(
name: str,
active: bool = True,
channels: list[Channel] = ...,
faceposer_model: str = '',
)

An actor in a choreo scene.

name: str
active: bool
channels: list[Channel]
faceposer_model: str
classmethod parse_binary(
file: FileR[bytes],
string_pool: list[str],
) Self

Parse the BVCD form of this data.

classmethod parse_text(tokenizer: BaseTokenizer) Self

Parse text data. The ‘actor’ string should have already been parsed.

export_binary(
file: FileWBinary,
add_to_pool: Callable[[str], int],
) None

Write this to a binary BVCD block.

export_text(file: FileWText, indent: str) None

Write this to a text VCD file.

class srctools.choreo.Channel(name: str, active: bool = True, events: list[Event] = ...)

A channel defines a set of events that an actor performs.

name: str
active: bool
events: list[Event]
classmethod parse_binary(
file: FileR[bytes],
string_pool: list[str],
) Self

Parse the BVCD form of this data.

classmethod parse_text(tokenizer: BaseTokenizer) Self

Parse text data. The ‘channnel’ string should have already been parsed.

export_binary(
file: FileWBinary,
add_to_pool: Callable[[str], int],
) None

Write this to a binary BVCD block.

export_text(file: FileWText, indent: str) None

Write this to a text VCD file.

Events

There are many types of events. Most share common configuration and use the base Event class, but some have additional options and require a specific subclass.

class srctools.choreo.EventType

Bases: Enum

Kinds of events.

Unspecified = 0
Section = 1
Expression = 2
LookAt = 3
MoveTo = 4
Speak = 5
Gesture = 6
Sequence = 7
Face = 8
FireTrigger = 9
FlexAnimation = 10
SubScene = 11
Loop = 12
Interrupt = 13
StopPoint = 14
PermitResponses = 15
Generic = 16
Camera = 17
Script = 18
class srctools.choreo.EventFlags

Bases: Flag

Flags for an event.

NoFlags = 0x0
ResumeCondition = 0x1
LockBodyFacing = 0x2
FixedLength = 0x4
Active = 0x8
ForceShortMovement = 0x10
PlayOverScript = 0x20
class srctools.choreo.Event(*, name: str, type: ~typing.Literal[EventType.Unspecified, EventType.Section, EventType.Expression, EventType.LookAt, EventType.MoveTo, EventType.Sequence, EventType.Face, EventType.FireTrigger, EventType.FlexAnimation, EventType.SubScene, EventType.Interrupt, EventType.StopPoint, EventType.PermitResponses, EventType.Generic, EventType.Camera, EventType.Script], flags: ~srctools.choreo.EventFlags = <EventFlags.NoFlags: 0>, parameters: tuple[str, str, str], start_time: float, end_time: float = -1.0, ramp: ~srctools.choreo.Curve, tag_name: str | None = None, tag_wav_name: str | None = None, dist_to_targ: float = 0, relative_tags: list[~srctools.choreo.Tag] =..., timing_tags: list[~srctools.choreo.TimingTag] =..., absolute_playback_tags: list[~srctools.choreo.AbsoluteTag] =..., absolute_shifted_tags: list[~srctools.choreo.AbsoluteTag] =..., flex_anim_tracks: list[~srctools.choreo.FlexAnimTrack] =..., default_curve_type: ~srctools.choreo.CurveType = CurveType(first=<Interpolation.DEFAULT: 0>, second=<Interpolation.DEFAULT: 0>), pitch: int = 0, yaw: int = 0)

An event is an action that occurs in a choreo scene’s timeline.

name: str
type: Final[EventType]
flags: EventFlags
parameters: tuple[str, str, str]
start_time: float
end_time: float
ramp: Curve
tag_name: str | None
tag_wav_name: str | None
dist_to_targ: float
relative_tags: list[Tag]
timing_tags: list[TimingTag]
absolute_playback_tags: list[AbsoluteTag]
absolute_shifted_tags: list[AbsoluteTag]
flex_anim_tracks: list[FlexAnimTrack]
default_curve_type: CurveType
pitch: int
yaw: int
property has_end_time: bool

Events have no end time if they are set to -1.

classmethod parse_binary(
file: FileR[bytes],
string_pool: list[str],
) Event

Parse the BVCD form of this data.

export_binary(
file: FileWBinary,
add_to_pool: Callable[[str], int],
) None

Write this to a binary BVCD block.

classmethod parse_text(tokenizer: BaseTokenizer) Event

Parse text data. The ‘event’ string should have already been parsed.

export_text(file: FileWText, indent: str) None

Write this to a text VCD file.

class srctools.choreo.GestureEvent(*, name: str, flags: ~srctools.choreo.EventFlags = <EventFlags.NoFlags: 0>, parameters: tuple[str, str, str], start_time: float, end_time: float = -1.0, ramp: ~srctools.choreo.Curve, tag_name: str | None = None, tag_wav_name: str | None = None, dist_to_targ: float = 0, relative_tags: list[~srctools.choreo.Tag] =..., timing_tags: list[~srctools.choreo.TimingTag] =..., absolute_playback_tags: list[~srctools.choreo.AbsoluteTag] =..., absolute_shifted_tags: list[~srctools.choreo.AbsoluteTag] =..., flex_anim_tracks: list[~srctools.choreo.FlexAnimTrack] =..., default_curve_type: ~srctools.choreo.CurveType = CurveType(first=<Interpolation.DEFAULT: 0>, second=<Interpolation.DEFAULT: 0>), pitch: int = 0, yaw: int = 0, gesture_sequence_duration: float)

Additional parameters for Gesture events.

type: Final[Literal[EventType.Gesture]]
gesture_sequence_duration: float
class srctools.choreo.LoopEvent(*, name: str, flags: ~srctools.choreo.EventFlags = <EventFlags.NoFlags: 0>, parameters: tuple[str, str, str], start_time: float, end_time: float = -1.0, ramp: ~srctools.choreo.Curve, tag_name: str | None = None, tag_wav_name: str | None = None, dist_to_targ: float = 0, relative_tags: list[~srctools.choreo.Tag] =..., timing_tags: list[~srctools.choreo.TimingTag] =..., absolute_playback_tags: list[~srctools.choreo.AbsoluteTag] =..., absolute_shifted_tags: list[~srctools.choreo.AbsoluteTag] =..., flex_anim_tracks: list[~srctools.choreo.FlexAnimTrack] =..., default_curve_type: ~srctools.choreo.CurveType = CurveType(first=<Interpolation.DEFAULT: 0>, second=<Interpolation.DEFAULT: 0>), pitch: int = 0, yaw: int = 0, loop_count: int = 0)

Additional parameters for Loop events.

type: Final[Literal[EventType.Loop]]
loop_count: int
class srctools.choreo.SpeakEvent(*, name: str, flags: ~srctools.choreo.EventFlags = <EventFlags.NoFlags: 0>, parameters: tuple[str, str, str], start_time: float, end_time: float = -1.0, ramp: ~srctools.choreo.Curve, tag_name: str | None = None, tag_wav_name: str | None = None, dist_to_targ: float = 0, relative_tags: list[~srctools.choreo.Tag] =..., timing_tags: list[~srctools.choreo.TimingTag] =..., absolute_playback_tags: list[~srctools.choreo.AbsoluteTag] =..., absolute_shifted_tags: list[~srctools.choreo.AbsoluteTag] =..., flex_anim_tracks: list[~srctools.choreo.FlexAnimTrack] =..., default_curve_type: ~srctools.choreo.CurveType = CurveType(first=<Interpolation.DEFAULT: 0>, second=<Interpolation.DEFAULT: 0>), pitch: int = 0, yaw: int = 0, caption_type: ~srctools.choreo.CaptionType = CaptionType.Master, cc_token: str = '', suppress_caption_attenuation: bool = False, use_combined_file: bool = False, use_gender_token: bool = False)

Additional parameters for Speak events.

type: Final[Literal[EventType.Speak]]
caption_type: CaptionType
cc_token: str
suppress_caption_attenuation: bool
use_combined_file: bool
use_gender_token: bool
playback_caption() str | None

Return the caption token to use, if this event should display one.

Miscellaneous Classes

class srctools.choreo.Interpolation(*values)

Kinds of interpolation.

DEFAULT = 0
CATMULL_ROM_NORMALIZE_X = 1
EASE_IN = 2
EASE_OUT = 3
EASE_IN_OUT = 4
BSP_LINE = 5
LINEAR = 6
KOCHANEK_BARTELS = 7
KOCHANEK_BARTELS_EARLY = 8
KOCHANEK_BARTELS_LATE = 9
SIMPLE_CUBIC = 10
CATMULL_ROM = 11
CATMULL_ROM_NORMALIZE = 12
CATMULL_ROM_TANGENT = 13
EXPONENTIAL_DECAY = 14
HOLD = 15
class srctools.choreo.CurveType(first: Interpolation, second: Interpolation)

A pair of interpolation types.

first: Interpolation
second: Interpolation
classmethod parse_text(text: str) CurveType

Parse text in the form ‘curve_AAA_to_curve_BBB’.

classmethod parse_binary(value: int) CurveType

Parse two interpolation types, packed into a two-byte value.

export_binary() int

Return the two interpolation types packed into a two-byte value.

class srctools.choreo.CaptionType

Bases: Enum

Kind of closed captions.

Master = 0
Slave = 1
Disabled = 2
class srctools.choreo.ExpressionSample(
time: float,
value: float,
curve_type: ~srctools.choreo.CurveType = CurveType(first=<Interpolation.DEFAULT: 0>,
second=<Interpolation.DEFAULT: 0>),
)

Keyframes for animations.

time: float
value: float
curve_type: CurveType
class srctools.choreo.Tag(name: str, value: float)

A tag labels a particular location in an event.

name: str

The name for this location.

value: float

Value for the tag. Ranges from 0-1, but is encoded into binary with 8 bits of precision.

classmethod parse_binary(
file: FileR[bytes],
string_pool: list[str],
double: bool,
) list[Self]

Parse a list of tags from the file. If double is set, the value is 16-bit not 8-bit.

classmethod export_binary(
file: FileWBinary,
add_to_pool: Callable[[str], int],
tags: list[Self],
) None

Write this to a binary BVCD block.

classmethod for ... in parse_text(
tokenizer: BaseTokenizer,
) Iterator[Self]

Parse a list of tags from a text file.

classmethod export_text(
file: FileWText,
indent: str,
tags: list[Self],
block_name: str,
) None

Export a list of tags into a text VCD file.

class srctools.choreo.TimingTag(name: str, value: float, locked: bool = False)

Flex animation timing tags additionally can be locked.

locked: bool
class srctools.choreo.AbsoluteTag(name: str, value: float)

Absolute tags have an increased range and precision.

value: float

Value for the tag. Ranges from 0-1, but is encoded into binary with 8 bits of precision.

name: str

The name for this location.

class srctools.choreo.CurveEdge(
active: bool,
zero_pos: float = 0.0,
curve_type: ~srctools.choreo.CurveType = CurveType(first=<Interpolation.DEFAULT: 0>,
second=<Interpolation.DEFAULT: 0>),
)

Curve data, only saved in the text file.

active: bool
zero_pos: float
curve_type: CurveType
classmethod parse_text(tokenizer: BaseTokenizer) Self

Parse text data. The leftedge/rightedge string should have already been parsed.

class srctools.choreo.Curve(
ramp: list[~srctools.choreo.ExpressionSample] =...,
left: ~srctools.choreo.CurveEdge = CurveEdge(active=False,
zero_pos=0.0,
curve_type=CurveType(first=<Interpolation.DEFAULT: 0>,
second=<Interpolation.DEFAULT: 0>)),
right: ~srctools.choreo.CurveEdge = CurveEdge(active=False,
zero_pos=0.0,
curve_type=CurveType(first=<Interpolation.DEFAULT: 0>,
second=<Interpolation.DEFAULT: 0>)),
)

Scene or event ramp data.

BIN_FMT: ClassVar[Struct] = Struct('<fB')
ramp: list[ExpressionSample]
left: CurveEdge
right: CurveEdge
classmethod parse_binary(file: FileR[bytes]) Self

Parse the BVCD form of this data.

export_binary(file: FileWBinary) None

Write this to a binary BVCD block.

classmethod parse_text(tokenizer: BaseTokenizer) Self

Parse text data. The ‘ramp’ string should have already been parsed.

export_text(file: FileWText, indent: str, name: str) None

Write this to a text VCD file.

class srctools.choreo.FlexAnimTrack(
name: str,
active: bool = True,
min: float = 0.0,
max: float = 1.0,
mag_track: list[~srctools.choreo.ExpressionSample] =...,
dir_track: list[~srctools.choreo.ExpressionSample] | None = None,
left: ~srctools.choreo.CurveEdge = CurveEdge(active=False,
zero_pos=0.0,
curve_type=CurveType(first=<Interpolation.DEFAULT: 0>,
second=<Interpolation.DEFAULT: 0>)),
right: ~srctools.choreo.CurveEdge = CurveEdge(active=False,
zero_pos=0.0,
curve_type=CurveType(first=<Interpolation.DEFAULT: 0>,
second=<Interpolation.DEFAULT: 0>)),
)

Flex controller animation data.

name: str
active: bool
min: float
max: float
mag_track: list[ExpressionSample]
dir_track: list[ExpressionSample] | None
left: CurveEdge
right: CurveEdge
classmethod parse_binary(
file: FileR[bytes],
string_pool: list[str],
) FlexAnimTrack

Parse the BVCD form of this data.

export_binary(
file: FileWBinary,
add_to_pool: Callable[[str], int],
) None

Write this to a binary BVCD block.

export_text(
file: FileWText,
indent: str,
default_curve: CurveType,
) None

Write this to a text VCD file.