Output: formats, quality, parameters, movies and voice
One scene file produces every output: a video for publishing, a GIF for a README, a single PNG or SVG frame for a document, or an HTML slide deck for a talk. This guide covers:
kinemo renderand its formats;- quality presets, sizes and transparent backgrounds;
- scene parameters and
--param; - movies (several scenes with transitions);
- narration with
s.voiceand TTS providers, and sound effects withk.sound.
Before rendering, run kinemo check --strict (see Tooling). Rendering builds
the scene the same way and fails on the same errors, so checking first is faster.
kinemo render#
kinemo render scene.py # every scene in the file → out/<scene>.mp4
kinemo render scene.py --scene intro # one scene
kinemo render scene.py --format gif --out docs/img
kinemo render scene.py --format png --at 2.5 # one frame
| Option | Default | Meaning |
|---|---|---|
--scene NAME |
every scene | Which @k.scene function to render |
--format |
mp4 |
mp4, webm, mov, gif, png, svg, slides |
--quality |
final |
draft or final (see Quality) |
--out DIR |
out |
Output folder, created if needed. Files are named after the scene. |
--at T |
end |
For png/svg: seconds, a mark name, or end |
--frames |
off | For png: the whole sequence, out/<scene>/00000.png, ... |
--transparent |
off | Transparent background where the format supports it |
--param NAME=VALUE |
parameter defaults | Scene parameters (repeatable) |
Formats#
| Format | Output | Notes |
|---|---|---|
mp4 |
out/<scene>.mp4 |
H.264, the default. Audio (voice, sounds) is muxed as AAC. |
webm |
out/<scene>.webm |
VP9. With --transparent, the video keeps an alpha channel. |
mov |
out/<scene>.mov |
ProRes 4444, for video editors. Supports --transparent. |
gif |
out/<scene>.gif |
Palette optimized per scene. Keep scenes short. |
png |
out/<scene>.png |
One frame at --at (default: the last frame), or every frame with --frames. Supports --transparent. |
svg |
out/<scene>.svg |
One vector frame at --at, for documents. Images are embedded. |
slides |
out/<scene>_slides/index.html |
One video per section and an HTML player (see Slides). |
mp4 and gif have no alpha channel, so --transparent has no effect on them.
Output is deterministic: the same source, assets and kinemo version give the same bytes, so rendered files can be committed and diffed.
Quality and size#
| Preset | Resolution and frame rate | Used by default in |
|---|---|---|
draft |
540p (960 × 540 for 16:9), at most 30 fps, no extra antialiasing | kinemo snap, kinemo dev |
final |
The scene's size and fps |
kinemo render |
Use --quality draft while iterating on a long render; it is faster.
The size and frame rate come from the scene decorator (or kinemo.toml, see
Configuration):
@k.scene(size="vertical", fps=30)
def short(s: k.Scene): ...
size= |
Pixels | Frame in units |
|---|---|---|
"720p" |
1280 × 720 | 16 × 9 |
"1080p" (default) |
1920 × 1080 | 16 × 9 |
"4k" |
3840 × 2160 | 16 × 9 |
"square" |
1080 × 1080 | 9 × 9 |
"vertical" |
1080 × 1920 | 9 × 16 |
(w, h) |
custom | 9 units on the shorter side |
The frame always measures 9 units on its shorter side, so a scene written for 1080p keeps
its layout at 4k. Square and vertical frames are narrower, so check them with
kinemo check (lint W1001 flags objects outside the safe area).
Slides#
s.mark(name=None, slide=True) defines a slide break at the cursor. --format slides
renders one MP4 per section and writes an index.html that plays each section and waits:
import kinemo as k
@k.scene(size="720p", fps=30)
def talk(s: k.Scene):
title = k.Text("Part 1", size=0.8).place(at="center")
s.play(k.write(title))
s.mark("part2", slide=True)
s.play(title.to(text="Part 2"))
s.mark("part3", slide=True)
s.play(title.to(color=k.BLUE, scale=1.4))
s.wait(0.5)
kinemo render talk.py --format slides # out/talk_slides/index.html + section_01.mp4, ...
kinemo render talk.py --format png --at part2
In the player, Right, Space, Page Down, Enter or a click go to the next section (a first
press skips to the end of a section that is still playing). Left and Page Up go back, and
f toggles full screen. The mark name, when given, is shown in the corner and can be used
as --at for png/svg.
Parameters#
Scene parameters are values you can change without editing the code. They are declared in
params= and reach the scene function as signals:
| Type | Example |
|---|---|
k.Int(lo, hi, default=None) |
k.Int(3, 12, default=5) |
k.Float(lo, hi, default=None) |
k.Float(0.5, 3, default=1.5) |
k.Bool(default=False) |
k.Bool(default=True) |
k.Choice(options, default=None) |
k.Choice(["fast", "slow"]) (default: the first option) |
k.Str(default="") |
k.Str(default="Polygon") |
import kinemo as k
@k.scene(params={"n": k.Int(3, 12, default=5), "heading": k.Str(default="Polygon")})
def polygon(s: k.Scene, n: k.Signal[int], heading: k.Signal[str]):
title = k.Text(heading, size=0.6).place(at="top", margin=0.8)
shape = k.Polygon.regular(n, r=2, fill=k.BLUE, fill_opacity=0.5, name="shape").place(at="center")
label = k.Text(lambda: f"n = {n():.0f}", size=0.4).place(below=shape, gap=0.3)
s.play(k.write(title), k.draw(shape), k.fade_in(label))
s.wait(0.5)
kinemo render polygon.py --param n=8 --param heading=Octagon
kinemo check polygon.py --param n=12 # check, inspect, snap and dev accept --param too
- In video, a parameter takes its
default, or the value from--param. Values are parsed as an integer, then a float, then text. Out-of-range values, unknown names and options that are not in ak.ChoiceareK0105. - For
k.Bool, pass0or1(--param grid=0). - A
k.Choiceof colors can only be chosen in code (default=). From the command line, use choices of numbers or strings. - Use parameters as signals: in props, lambdas and expressions. Reading one with
.nowfreezes it at build time. LintW1301warns, because that breaks the interactive export planned for a later version.
Movies#
k.movie(scenes, transitions=(), *, name="movie") composes several scenes into one output,
with one transition per join:
import kinemo as k
@k.scene(size="720p")
def intro(s: k.Scene):
title = k.Text("Chapter 1", size=0.8).place(at="center")
sun = k.Circle(r=0.8, key="sun", fill=k.YELLOW, fill_opacity=1).place(at="left", margin=2)
s.play(k.write(title), k.grow(sun))
s.wait(0.5)
@k.scene(size="720p")
def body(s: k.Scene):
sun = k.Circle(r=1.2, key="sun", fill=k.YELLOW, fill_opacity=1).place(at="right", margin=2)
s.add(sun)
s.play(sun.to(scale=1.3))
s.wait(0.5)
@k.scene(size="720p")
def outro(s: k.Scene):
s.play(k.write(k.Text("Thanks", size=0.8).place(at="center")))
s.wait(0.5)
movie = k.movie([intro, body, outro], transitions=[k.morph_cut(0.6), k.crossfade(0.5)], name="chapter1")
| Transition | Effect |
|---|---|
k.cut |
Hard cut (the default for joins without a transition) |
k.crossfade(duration=0.5) |
The end of one scene dissolves into the start of the next |
k.morph_cut(duration=0.5) |
Meant for objects with the same key= in neighboring scenes. In 1.0 it renders as a crossfade. |
kinemo render movie.pyrenders everyk.moviein the file as one video,out/<name>.mp4(--format webm,movorgiffor the others). With--scene, it renders that single scene instead.- Movies are video only.
png,svgandslidesapply to single scenes. kinemo check movie.pychecks every scene of the file.- Give the scenes of a movie the same
sizeandfps.
Narration: s.voice#
with s.voice(narration, *, voice=None, gain=1.0):
...
A narrated block. narration is either text, spoken by the TTS provider configured for the
project, or the path of an audio file (.wav, .mp3, .ogg, .flac, .m4a, .aac). The
block lasts at least as long as the audio: if the animations inside are shorter, the
cursor waits for the narration to finish.
Words marked [word]{name} become time marks at the instant they are spoken, so animations
can be synchronized with the voice:
import kinemo as k
@k.scene
def narrated(s: k.Scene):
tri = k.Triangle.right(3, 4, scale=0.6).place(at="center")
with s.voice("Every right [triangle]{tri} hides a relation between its sides."): # kinemo: allow W1401
s.play(k.draw(tri))
s.start(k.indicate(tri), at=s.marks["tri"])
s.wait(0.5)
Without a TTS provider#
When no provider is configured, the voice becomes silence with an estimated duration
(150 words per minute), marks are spread over the estimate, and lint W1401 warns. The
scene can be timed, previewed and checked without a network or a model. The
# kinemo: allow W1401 comment above keeps check --strict passing while you write; remove
it once a provider is set.
Configuring a provider#
Providers are separate packages named kinemo-tts-<name>. Install one and select it in
kinemo.toml:
[tts]
provider = "piper"
[cache]
dir = ".kinemo-cache"
Generated audio is cached in the cache directory, keyed by provider, voice and text, so
re-rendering does not synthesize again. voice= selects a voice of the provider and
gain= scales the volume.
A provider is a class with a name and a synthesize(text, voice, out_path) method that
writes the audio and returns a kinemo.audio.tts.Speech(path, duration, word_times).
word_times (the start of each word, in seconds) makes the [word]{name} marks exact. The
package exposes a factory in the kinemo.tts entry-point group:
# pyproject.toml of kinemo-tts-mytts
[project.entry-points."kinemo.tts"]
mytts = "kinemo_tts_mytts:Provider"
Recorded narration#
An audio file is used as is: the block lasts as long as the file. Marked words are not
available (there is no text to align), so use s.mark for sync points.
The path is checked relative to the working directory, and a string is treated as a file
only when it ends with an audio extension and the file exists. Otherwise it is read as
text to speak, so a typo in the file name shows up as W1401 (no TTS provider) or as the
file name being spoken. Run kinemo from the folder that holds the audio, or pass an
absolute path. The snippet below assumes a narration.wav in the working directory:
import kinemo as k
@k.scene
def narrated_file(s: k.Scene):
title = k.Text("Recorded narration", size=0.6).place(at="center")
with s.voice("narration.wav"):
s.play(k.write(title))
s.wait(0.5)
Sound effects#
k.sound(path, gain=1.0) plays a file at the instant it is scheduled. It takes no time in
the script, so it composes with other animations: s.play(k.sound("click.wav"), k.indicate(button)).
Common mistakes#
Diagnostic What happened Fix W1401s.voice("...")with no TTS provider: silence with an estimated duration.Configure [tts] providerinkinemo.toml, or add# kinemo: allow W1401while drafting.W1301A parameter read with .now, so its value is frozen at build time.Pass the signal itself to props and lambdas. K0105--paramvalue out of range, not one of thek.Choiceoptions, or an unknown parameter name.Check the name and the range in params=.W0110s.play(..., at=s.marks["x"])does not move the cursor.Write s.start(..., at=...).W1001Objects outside the safe area after switching to size="vertical"or"square".The frame is 9 units wide there: use containers, .fit(s.frame.safe), or smaller sizes.W1401s.voice("narration.wav")where the file does not exist (or the working directory is different): the string is read as text to speak.Fix the path, run from the folder that holds the file, or pass an absolute path. none (render error) k.sound("click.wav")with a missing file passescheck, butrenderstops with "audio file not found".Check the path. Unlike k.Image, it is resolved from the working directory, not from the scene file's folder.none (no alpha) --transparentwithmp4orgif.Use webm,movorpng.
See also: Output reference, Parameters,
s.voice, s.mark,
Command line, Configuration.