Media export
The Media Export panel (command palette: “Media export…”, or one of the “Export scene as …” / “Export frame as …” commands) renders a scene with the viewport’s renderer and writes:
| Format | Content | Alpha | Audio |
|---|---|---|---|
| PNG image / PNG sequence (ZIP) | 8-bit RGBA, sRGB-tagged | straight (not premultiplied) | - |
| JPEG image / JPEG sequence (ZIP) | flattened over the background colour (white by default) | - | - |
| Animated GIF | per-frame median-cut palette of 3–256 colours, dithering off by default, loop | binary (alpha ≥ 50 % is opaque) | - |
| MP4 | H.264 (WebCodecs), YUV matrix BT.601 / BT.709 / BT.2020 / FCC, tagged | - (flattened over the background) | AAC (Opus where AAC encoding is unavailable) or MP3 |
| WebM | VP9 (WebCodecs) with a second VP9 stream for alpha (BlockAdditional, AlphaMode 1), BT.601 / BT.709 / BT.2020 |
straight | Opus |
| MOV | QuickTime Animation (RLE, 32-bit ARGB) | straight | 16-bit PCM or MP3 |
| Layered PSD (one frame, model canvas) | the current display (one layer per drawable, optional folders per part, plus the merged image) or all source images as imported | straight per layer | - |
Options
- All scenes (sequences, GIF, video): every scene of the project in one ZIP, a folder of
images or one
.gif/.mp4/.webm/.movper scene, named after the scene (characters file systems refuse become_, repeats get(2),(3)…). With Current frame or a Custom range, scenes shorter than the start frame are left out (and reported), a custom end is clipped to each scene. Videos are streamed straight into the archive (stored entries; the CRC is computed by reading the entry back), so memory stays bounded however long the scenes are. - Sequence numbering: Match scene frame or From a start number; Digits pads with
zeros (automatic: at least 4, never fewer than the largest number needs, at most 10);
Name prefix (default: the file name, or the scene name in an all-scenes ZIP). Files are
<prefix>_<number>.<ext>; a Current frame sequence is the single file<prefix>.<ext>. - Parameter controllers follow their targets (all scene exports, default on): off, the
controlled parameters keep their keyed values (angles
[15, 0, 0]off versus[15, 14.97, 14.95]on in the test scene). - GIF colours 3–256 (the transparent entry counts as one), with or without dithering.
- Output frame rate (video, 1–120 with two decimals; default the scene’s): the scene is
sampled on the output’s own frame grid (frame
jshows scene timej / fps), the last frame is held until the end of the range, so the file lasts exactly the range and the audio, still mixed per scene frame, stays in sync (21 frames at 30 → 15 fps last exactly 0.7 s). - Bitrate: Auto picks a tier by pixel count (< 102 721 px → 400 kbps, < 230 401 → 900, < 345 601 → 1500, < 940 001 → 3200, < 2 300 001 → 7000, < 4 000 001 → 10 000, < 9 000 001 → 15 000, otherwise 20 000); manual 1–240 000 kbps.
- YUV matrix (MP4: BT.601, BT.709, BT.2020, FCC; WebM: no FCC, VP9 has no FCC colour
space; MOV: none, it is RGB). The frames are converted to I420 in wasm with the chosen
matrix (limited range) and handed to WebCodecs as I420
VideoFrames, so the browser does no conversion of its own; the frames’ colour space, the MP4colrbox and the MatroskaColourelement all carry the matrix (primaries and transfer stay BT.709: the pixels are sRGB), so players decode BT.601 / BT.2020 / FCC videos with the right colours. - Audio codec: MP4 AAC or MP3, MOV PCM or MP3 (PCM is the
lossless choice). MP3 is MPEG-1 Layer III at 48 kHz stereo, constant bit rate
32–320 kbps (default 192), encoded in wasm by
crates/media/src/mp3(WebCodecs has no MP3 encoder): polyphase analysis filterbank, MDCT with alias reduction (long blocks), the quantisation rate loop and the standard’s Huffman tables; frames carry no bit reservoir. MP4 stores it asmp4awith object type 0x6B, MOV as.mp3; the encoder and decoder delay (1057 samples) is skipped with the edit list, so the sound starts and ends exactly with the video. - PSD content: Current display (below) or All source images: every art mesh imported
from a PSD /
.cmo3contributes its texture as imported, trimmed to its visible pixels (the importer pads textures), at the canvas position it was imported from, the layer’s PSD rectangle, in folders following the parts (sibling order bottom to top), independent of the pose; hidden parts and meshes stay hidden layers; the merged image composites the visible ones normally. Meshes made in the editor or from a runtime.moc3atlas have no source image.
Frames and durations
- Ranges include both end frames: the whole scene is
frames
0 … duration, the work areaa … b, a custom rangefrom … to. Exportinga … brendersb − a + 1frames; frameiof the file shows scene framea + iati / fpsand lasts1 / fps, so the file lasts(b − a + 1) / fps. - Audio lasts exactly as long as the video. Every unmuted, audible audio track (including
those of nested scenes) is mixed at 48 kHz stereo with its gain and volume curve (ramped
across each frame), resampled from the source rate. Scene frame
ireceives the samples between the rounded timesi / fpsand(i + 1) / fps, so the total isround(48000 · frames / fps)samples; encoder delay is removed with an edit list (MP4/MOV) orCodecDelay/DiscardPadding(WebM). - GIF delays do not drift. GIF stores centiseconds; each delay is the difference of the rounded cumulative end times, so the delays add up to the animation’s duration. Unchanged frames are merged into their predecessor. The GIF frame rate is capped at 50 fps (browsers stretch shorter delays).
- Physics runs at a fixed step of
1 / fpsfrom scene frame 0, so a range starting later continues the motion it would have had.
Size and background
The output is the model canvas × Scale, or the given width/height with the canvas fitted and centred (video sizes round up to even numbers). Anti-alias renders at 2× and filters down. Transparent keeps alpha in formats that have it; JPEG and MP4 are always flattened over the background colour.
PSD
Current display: layers are rendered one drawable at a time with normal blending (their own colours, opacity, masks and offscreen-group opacity baked in) and carry the drawable’s blend mode as the Photoshop layer mode. Alpha compositing modes and the glow add have no Photoshop equivalent; they are reported and approximated in the layers. The merged image is the real composite of the frame (stored the way Photoshop stores it: matted over white with a transparency channel), not an empty preview image. Source images: see PSD content above.
Browser support
Everything runs in a worker and streams into a file of the origin-private file system, so memory does not grow with the length of the export. PNG, JPEG, GIF, MOV and PSD need OffscreenCanvas WebGL2 (Safari 17+, Chrome, Edge, Firefox). MP4 and WebM need WebCodecs encoders for H.264 / VP9 (and AAC / Opus for sound; MP3 needs nothing extra); the panel detects them and marks formats this browser cannot produce, or produces without sound.
Tests
crates/media/tests/ffprobe.rs checks every writer with an independent decoder (FFmpeg,
test-only): MP3 in MP4 / MOV (codec, rate, channels, duration, alignment, SNR), output frame
rates (frame times, duration, audio), YUV tagging and decoding back per matrix, the WebM
Colour element, GIF colour counts, all-scenes ZIPs with streamed movies, source-image PSDs.
crates/animation/tests/controller_follow.rs covers the controller switch,
apps/web/test/mediaOptions.test.ts the naming and option logic, and
apps/web/e2e/media-export.spec.ts the options through the panel.