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:

  1. Position — which point on screen serves as the anchor (anchors / placement)
  2. Composition — horizontal or vertical writing, how many lines to wrap at (writing / maxLines / lineGap / scaleMode)
  3. 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 / focusWord for 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 anchors as an empty array. There would be nowhere to place text, and the lyrics won't display.
  • Don't write x / y as pixel values. They are ratios in 0–1 (0.5, not 960).
  • Don't write a key that matches its default value (maxLines: 1 / lineGap: 0.15 / scaleMode: "fixed" / a placement of "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