LayoutSpec Specification — Building Your Own "Lyric Placement"
This document is a translation of the Japanese original. The Japanese version is authoritative — if the two differ, the Japanese version takes precedence.
This document is the complete specification for LayoutSpec, movlyric's definition of lyric placement (layout). You use it from the app's Style Editor → "Placement" tab → Placement preset → "Custom" → "Create with AI…".
There are two similarly named but different things. Don't mix them up.
| What it decides | Specification |
|---|---|
| Where on screen the lyrics go (this document) | LAYOUT_SPEC (Lyric Placement) |
| How the lyric characters move (entering, exiting, flickering) | ANIMATION_SPEC (Character Motion) |
| The shapes/lines drawn behind the lyrics | GRAPHIC_SPEC (Background Effects) |
The goal is that an AI (or a human) who has read only this document can write JSON that the app accepts as-is. A prompt template for handing to an AI appears at the end of this document. You can also paste this entire document in as part of the prompt, and it will still work.
1. Overview
LayoutSpec decides only "where on screen, and how, one phrase's worth of lyrics is arranged." There is nothing that changes over time (motion is AnimationSpec's job).
It determines three things:
- Position — which point on screen serves as the anchor (
anchors/placement) - Composition — horizontal or vertical writing, how many lines to wrap at (
writing/maxLines/lineGap/scaleMode) - Disorder — whether to break from a straight arrangement (
scatter/baselineRotateDeg/path/charScaleRange/focusWord)
Anything that uses randomness is fixed per song. The same song and the same style always produce the same result,
and preview and export match exactly (placement: "random" / scatter / charScaleRange /
focusWord.pick: "random" are all derived from the style's seed).
2. Complete JSON Format Specification
2.1 Root: LayoutSpec
{
"writing": "horizontal",
"anchors": [{ "x": 0.5, "y": 0.5, "align": "center" }],
"placement": "fixed"
}
| Key | Required | Type | Meaning |
|---|---|---|---|
writing |
Yes | "horizontal" | "vertical" |
Horizontal / vertical writing (vertical flows top→bottom, columns right→left) |
anchors |
Yes | Array of Anchor (1 or more) | Placement candidates |
placement |
Yes | "fixed" | "cycle" | "random" |
How phrases are assigned to candidates |
maxLines |
Integer ≥ 1 | Number of lines to wrap at (columns for vertical writing). Default 1 = no wrapping | |
lineGap |
Number ≥ 0 | Line spacing (column spacing), as a ratio of font size. Default 0.15 | |
scaleMode |
"fixed" | "fill" |
fill = a large composition that fills the screen width (or height for vertical writing). Default fixed |
|
scatter |
Scatter | Per-character scattering | |
baselineRotateDeg |
Number | The tilt of the whole phrase, in degrees. Default 0 | |
path |
Path | A non-straight arrangement (horizontal writing only) | |
charScaleRange |
[min, max] |
Per-character size variation (horizontal writing only) | |
focusWord |
FocusWord | Enlarge a single word (horizontal writing only) |
Don't write a key whose value matches the default. For example, omit maxLines: 1 or scaleMode: "fixed"
entirely (writing them means the same thing as omitting them, and just bloats the saved data).
2.2 Anchor (a placement candidate)
{ "x": 0.5, "y": 0.88, "align": "center" }
| Key | Required | Type | Meaning |
|---|---|---|---|
x |
Yes | 0..1 | From the screen's left edge (0) to right edge (1) |
y |
Yes | 0..1 | From the screen's top edge (0) to bottom edge (1) |
align |
Yes | "start" | "center" | "end" |
Which side of the anchor the lyrics extend toward |
align means left/center/right alignment for horizontal writing, and top/center/bottom alignment for vertical writing.
When placing lyrics near the screen edge, match align to it. For example, x: 0.08 combined with
align: "center" will cause a long phrase to run off the left edge of the screen. To keep it flush against the left edge, use align: "start".
2.3 placement (assigning candidates to phrases)
| Value | Meaning |
|---|---|
"fixed" |
Always uses the first candidate (use this if there's only one candidate) |
"cycle" |
Cycles through the candidates in phrase order |
"random" |
Picks randomly from the candidates (fixed per song) |
With only one candidate, all three produce the same result. This only matters once you have two or more candidates.
2.4 Scatter (per-character scattering)
{ "offsetRatio": 0.25, "rotateDeg": 8 }
| Key | Required | Type | Meaning |
|---|---|---|---|
offsetRatio |
Yes | Number ≥ 0 | The amount of positional offset, as a ratio of font size |
rotateDeg |
Yes | Number ≥ 0 | The amount of rotational variance, in degrees |
Both are fixed per song. Around 0.3 / 12 already reads as quite scattered.
2.5 Path (a non-straight arrangement, horizontal writing only)
{ "kind": "wave", "amplitude": 0.3, "period": 8 }
| Key | Required | Type | Meaning |
|---|---|---|---|
kind |
Yes | "wave" | "arc" | "steps" |
Wave / arc / steps |
amplitude |
Yes | Number | The amount of variance, as a ratio of font size |
period |
Number > 0 | How many characters make up one cycle. Defaults to 8 for wave / 3 for steps |
For wave and arc, each character's rotation follows the tangent of the curve (it tilts along the path).
arc doesn't use period (it draws a single arc across the whole phrase).
For multiple lines, each line is treated independently.
2.6 charScaleRange (per-character size variation, horizontal writing only)
[0.85, 1.3]
Two elements, [min, max]. Each character gets a scale factor fixed per song.
It affects both character size and advance width, so line-width calculation, wrapping, and center alignment all remain consistent, including the variation.
2.7 FocusWord (enlarge a single word, horizontal writing only)
{ "scale": 1.4, "pick": "longest" }
| Key | Required | Type | Meaning |
|---|---|---|---|
scale |
Yes | Number > 0 | The scale factor for the target word |
pick |
Yes | "longest" | "random" |
The longest word by character count (first one wins on ties) / random (fixed per song) |
When combined with charScaleRange, the scale factors multiply together.
3. Common Mistakes (Prohibited)
- Don't write
path/charScaleRange/focusWordfor vertical writing. The engine only interprets these for horizontal writing, so writing them silently does nothing (there's no error, so it's easy to miss). - Don't leave
anchorsas an empty array. There would be nowhere to place text, and the lyrics won't display. - Don't write
x/yas pixel values. They are ratios in 0–1 (0.5, not960). - Don't write a key that matches its default value (
maxLines: 1/lineGap: 0.15/scaleMode: "fixed"/ aplacementof"cycle"that's effectively meaningless with just one candidate, etc.). - Don't write values that react to time or audio. LayoutSpec has no formulas or animation. If you want motion, that's AnimationSpec's job (ANIMATION_SPEC (Character Motion)).
- Don't write nonexistent properties, comments, or trailing commas.
4. Examples
4.1 One line near the bottom of the screen (the karaoke standard)
{
"writing": "horizontal",
"anchors": [{ "x": 0.5, "y": 0.88, "align": "center" }],
"placement": "fixed"
}
4.2 Alternating top and bottom, wrapping at 2 lines
{
"writing": "horizontal",
"anchors": [
{ "x": 0.5, "y": 0.3, "align": "center" },
{ "x": 0.5, "y": 0.7, "align": "center" }
],
"placement": "cycle",
"maxLines": 2,
"lineGap": 0.4
}
Each phrase alternates top→bottom→top and so on.
4.3 Vertical writing, 3 columns from the right
{
"writing": "vertical",
"anchors": [{ "x": 0.82, "y": 0.15, "align": "start" }],
"placement": "fixed",
"maxLines": 3,
"lineGap": 0.5
}
Vertical writing flows top to bottom, with columns added right to left. align: "start" aligns the tops.
4.4 A screen-filling composition, tilted
{
"writing": "horizontal",
"anchors": [{ "x": 0.5, "y": 0.5, "align": "center" }],
"placement": "fixed",
"scaleMode": "fill",
"baselineRotateDeg": -6
}
scaleMode: "fill" enlarges the phrase to fill the screen width, so its size varies with character count.
4.5 Scattered into the four corners
{
"writing": "horizontal",
"anchors": [
{ "x": 0.2, "y": 0.25, "align": "start" },
{ "x": 0.8, "y": 0.4, "align": "end" },
{ "x": 0.25, "y": 0.62, "align": "start" },
{ "x": 0.75, "y": 0.8, "align": "end" }
],
"placement": "random",
"scatter": { "offsetRatio": 0.3, "rotateDeg": 12 },
"charScaleRange": [0.85, 1.3]
}
Edge anchors have align set to start / end to match, so text doesn't run off the screen.
4.6 Emphasizing one word along a wave
{
"writing": "horizontal",
"anchors": [{ "x": 0.5, "y": 0.5, "align": "center" }],
"placement": "fixed",
"path": { "kind": "wave", "amplitude": 0.35, "period": 6 },
"focusWord": { "scale": 1.5, "pick": "longest" }
}
5. AI Prompt Template
Paste this entire document, and follow it with the block below.
Copy from here
You are a lyric layout designer for movlyric. Strictly following the "LayoutSpec Specification" pasted above, create a single LayoutSpec JSON.
Output rules:
- Output JSON only (no explanation, preamble, or closing remarks; wrapping the JSON in a code fence is fine)
- The only fields allowed at the root are writing, anchors, placement, maxLines, lineGap, scaleMode, scatter, baselineRotateDeg, path, charScaleRange, focusWord
- writing, anchors, and placement are required. Only write the others when needed
- **Don't write a key whose value matches the default** (maxLines:1 / lineGap:0.15 / scaleMode:"fixed")
- writing is either "horizontal" or "vertical"
- anchors has 1–8 items. Each element is {"x":0..1, "y":0..1, "align":"start"|"center"|"end"}
- x and y are **ratios of the screen** (not pixels). 0.5 is the center
- When placing near the screen edge, match align to start / end (leaving it as center will run off-screen)
- placement is "fixed" / "cycle" / "random". Use "fixed" when there's only one anchor
- scatter is {"offsetRatio":≥0, "rotateDeg":≥0}
- path is {"kind":"wave"|"arc"|"steps", "amplitude":number, "period":>0 (optional)}
- charScaleRange is a 2-element array [min, max]
- focusWord is {"scale":>0, "pick":"longest"|"random"}
- **When writing is "vertical", don't write path / charScaleRange / focusWord** (they would be ignored)
- Values or formulas that react to time, audio, or randomness cannot be written (motion belongs to the separate AnimationSpec)
- Do not write nonexistent properties, comments, or trailing commas
Lyric placement I want to create:
{describe the placement you want here}
(Only if you're modifying an existing layout, paste the current JSON below. For a new creation, delete this entire section.)
Current JSON:
{current JSON}
Copy up to here