LayoutSpec 사양서 — 나만의 "가사 배치" 만들기

이 문서는 일본어 원문의 번역본입니다. 일본어판이 정본이며, 내용에 차이가 있을 경우 일본어판이 우선합니다.

이 문서는 movlyric의 가사 배치(레이아웃)의 정의인 "LayoutSpec"의 완전한 사양입니다. 앱의 "스타일 에디터 → 「배치」 탭 → 배치 프리셋 → 「사용자 지정」 → AI로 만들기…"에서 사용합니다.

이름이 비슷한 별개의 것이 2개 있습니다. 혼동하지 마세요.

무엇을 정하는가 사양서
가사를 화면의 어디에 둘지(이 문서) LAYOUT_SPEC(가사 배치)
가사 글자가 어떻게 움직일지(등장·퇴장·명멸) ANIMATION_SPEC(문자 움직임)
가사의 뒤에 그리는 도형·선 GRAPHIC_SPEC(배경 이펙트)

이 문서만 읽은 AI(또는 사람)가 앱에 그대로 붙여 넣어 통과하는 JSON을 작성할 수 있게 하는 것이 목적입니다. 문서 끝에 AI에게 전달할 프롬프트 템플릿이 있습니다. 이 문서 전체를 프롬프트의 일부로 붙여 넣어도 작동합니다.


1. 개요

LayoutSpec은 "구절 하나 분량의 가사를 화면의 어디에, 어떻게 배열할지"만을 정합니다. 시간에 따라 변화하는 요소는 일절 없습니다(움직임은 AnimationSpec의 담당).

정해지는 것은 다음 3가지입니다.

  1. 위치 — 화면의 어느 점을 기준으로 둘지(anchors / placement)
  2. 짜임 — 가로쓰기인지 세로쓰기인지, 몇 줄까지 줄바꿈할지(writing / maxLines / lineGap / scaleMode)
  3. 흐트러뜨림 — 똑바로 배열하지 않고 흐트러뜨릴지(scatter / baselineRotateDeg / path / charScaleRange / focusWord)

난수를 사용하는 항목은 모두 곡마다 고정됩니다. 같은 곡·같은 스타일이면 매번 같은 결과가 되고, 미리보기와 내보내기도 일치합니다(placement: "random" / scatter / charScaleRange / focusWord.pick: "random"은 모두 스타일의 seed로 정해집니다).


2. JSON 형식의 완전한 사양

2.1 루트: LayoutSpec

{
  "writing": "horizontal",
  "anchors": [{ "x": 0.5, "y": 0.5, "align": "center" }],
  "placement": "fixed"
}
키 필수 타입 의미
writing ✅ "horizontal" | "vertical" 가로쓰기 / 세로쓰기(위→아래·열은 오른쪽→왼쪽)
anchors ✅ Anchor의 배열(1개 이상) 배치 후보
placement ✅ "fixed" | "cycle" | "random" 구절을 후보에 어떻게 배정할지
maxLines 정수 1 이상 줄바꿈 줄 수(세로쓰기는 열 수). 기본값 1 = 줄바꿈하지 않음
lineGap 숫자 0 이상 줄 간격(열 간격). 글자 크기에 대한 비율. 기본값 0.15
scaleMode "fixed" | "fill" fill = 화면 너비(세로쓰기는 높이) 가득 채우는 큰 짜임. 기본값 fixed
scatter Scatter 글자마다의 흐트러뜨림
baselineRotateDeg 숫자 구절 전체의 기울기(도). 기본값 0
path Path 직선이 아닌 배열 방식(가로쓰기 전용)
charScaleRange [최소, 최대] 글자마다 크기의 흔들림(가로쓰기 전용)
focusWord FocusWord 단어 1개만 확대(가로쓰기 전용)

기본값과 같은 값의 키는 쓰지 마세요. 예를 들어 maxLines: 1이나 scaleMode: "fixed"는 쓰지 않고 생략합니다(생략과 같은 의미이며, 쓰면 저장 내용이 쓸데없이 늘어납니다).

2.2 Anchor(배치 후보)

{ "x": 0.5, "y": 0.88, "align": "center" }
키 필수 타입 의미
x ✅ 0..1 화면의 왼쪽 끝 0 ~ 오른쪽 끝 1
y ✅ 0..1 화면의 위쪽 끝 0 ~ 아래쪽 끝 1
align ✅ "start" | "center" | "end" 앵커를 기준으로 가사를 어느 쪽으로 뻗게 할지

align은 가로쓰기라면 왼쪽 정렬 / 가운데 정렬 / 오른쪽 정렬, 세로쓰기라면 위쪽 정렬 / 가운데 정렬 / 아래쪽 정렬입니다.

화면 끝에 둘 때는 align을 맞추세요. 예를 들어 x: 0.08에 align: "center"를 지정하면 긴 구절이 화면 왼쪽으로 삐져나갑니다. 왼쪽 끝에 붙이고 싶다면 align: "start"입니다.

2.3 placement(구절마다의 배정)

값 의미
"fixed" 항상 1번째 후보를 사용(후보가 1개면 이것)
"cycle" 구절 순서대로 후보를 순환함
"random" 후보 중에서 무작위로 선택(곡마다 고정)

후보가 1개일 때는 3가지 모두 같은 결과가 됩니다. 후보를 2개 이상 썼을 때만 의미를 가집니다.

2.4 Scatter(글자마다의 흐트러뜨림)

{ "offsetRatio": 0.25, "rotateDeg": 8 }
키 필수 타입 의미
offsetRatio ✅ 숫자 0 이상 위치의 어긋남 폭. 글자 크기에 대한 비율
rotateDeg ✅ 숫자 0 이상 회전의 흔들림 폭(도)

둘 다 곡마다 고정입니다. 0.3 / 12 정도면 상당히 흐트러진 인상이 됩니다.

2.5 Path(직선이 아닌 배열 방식·가로쓰기 전용)

{ "kind": "wave", "amplitude": 0.3, "period": 8 }
키 필수 타입 의미
kind ✅ "wave" | "arc" | "steps" 파동 / 호 / 계단
amplitude ✅ 숫자 흔들림 폭. 글자 크기에 대한 비율
period 숫자 0보다 큼 몇 글자로 한 바퀴 도는지. 생략 시 wave 8 / steps 3

wave와 arc는 글자의 회전이 곡선의 접선을 따라갑니다(배열을 따라 기울어짐). arc는 period를 사용하지 않습니다(구절 전체로 하나의 호를 그림). 여러 줄일 때는 각 줄마다 독립적으로 적용됩니다.

2.6 charScaleRange(글자 크기의 흔들림·가로쓰기 전용)

[0.85, 1.3]

[최소, 최대]의 2요소. 글자마다 곡마다 고정된 배율이 정해집니다. 글자 크기와 보내기 폭 양쪽에 영향을 주므로, 줄 폭 계산·줄바꿈·가운데 정렬도 흔들림까지 포함해 일관됩니다.

2.7 FocusWord(단어 1개만 확대·가로쓰기 전용)

{ "scale": 1.4, "pick": "longest" }
키 필수 타입 의미
scale ✅ 숫자 0보다 큼 대상 단어의 배율
pick ✅ "longest" | "random" 글자 수 최장(동수면 첫 번째) / 무작위(곡마다 고정)

charScaleRange와 함께 쓰면 배율은 곱셈이 됩니다.


3. 흔한 실수(금지 사항)

  • 세로쓰기에 path / charScaleRange / focusWord를 쓰지 않기. 엔진이 가로쓰기에서만 해석하므로, 써도 아무 일도 일어나지 않습니다(오류도 나지 않아 알아차릴 수 없습니다).
  • anchors를 빈 배열로 하지 않기. 배치할 곳이 없어져 가사가 표시되지 않습니다.
  • x / y에 픽셀 값을 쓰지 않기. 0~1의 비율입니다(960이 아니라 0.5).
  • 기본값과 같은 키를 쓰지 않기(maxLines: 1 / lineGap: 0.15 / scaleMode: "fixed" / placement가 사실상 무의미한 후보 1개에서의 "cycle" 등).
  • 시간이나 소리에 반응하는 값을 쓰지 않기. LayoutSpec에 수식·애니메이션은 없습니다. 움직이고 싶다면 AnimationSpec(ANIMATION_SPEC(문자 움직임))의 담당입니다.
  • 존재하지 않는 속성·주석·끝 쉼표를 쓰지 않기.

4. 예시

4.1 화면 아래쪽에 한 줄(가라오케의 정석)

{
  "writing": "horizontal",
  "anchors": [{ "x": 0.5, "y": 0.88, "align": "center" }],
  "placement": "fixed"
}

4.2 위아래로 나누어 2줄까지 줄바꿈

{
  "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
}

구절마다 위→아래→위… 순으로 바뀝니다.

4.3 세로쓰기로 오른쪽부터 3열

{
  "writing": "vertical",
  "anchors": [{ "x": 0.82, "y": 0.15, "align": "start" }],
  "placement": "fixed",
  "maxLines": 3,
  "lineGap": 0.5
}

세로쓰기는 위에서 아래로 흐르고, 열은 오른쪽에서 왼쪽으로 늘어납니다. align: "start"로 위쪽 끝을 맞추고 있습니다.

4.4 화면 가득한 큰 짜임을 비스듬히

{
  "writing": "horizontal",
  "anchors": [{ "x": 0.5, "y": 0.5, "align": "center" }],
  "placement": "fixed",
  "scaleMode": "fill",
  "baselineRotateDeg": -6
}

scaleMode: "fill"은 구절을 화면 너비 가득까지 확대하므로, 글자 수에 따라 크기가 달라집니다.

4.5 네 귀퉁이에 흩어 흐트러뜨리기

{
  "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]
}

끝쪽 앵커에는 align을 start / end로 맞춰, 삐져나오지 않도록 하고 있습니다.

4.6 파도치게 해서 단어 1개를 강조

{
  "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용 프롬프트 템플릿

이 문서 전체를 붙여 넣은 다음, 이어서 아래를 붙여 넣으세요.

여기서부터 복사

당신은 movlyric의 가사 레이아웃 설계자입니다. 위에 붙여 넣은 "LayoutSpec 사양서"를 엄밀히 따라, LayoutSpec의 JSON을 하나 작성해 주세요.

출력 규칙:
- JSON만 출력한다(설명문·서두·맺음말은 쓰지 않는다. JSON을 코드 펜스로 감싸는 것은 가능)
- 루트에 쓸 수 있는 것은 writing, anchors, placement, maxLines, lineGap, scaleMode, scatter, baselineRotateDeg, path, charScaleRange, focusWord뿐이다
- writing, anchors, placement는 필수. 그 외에는 필요할 때만 쓴다
- **기본값과 같은 키는 쓰지 않는다**(maxLines:1 / lineGap:0.15 / scaleMode:"fixed")
- writing은 "horizontal" / "vertical" 중 하나
- anchors는 1~8개. 각 요소는 {"x":0..1, "y":0..1, "align":"start"|"center"|"end"}
- x, y는 **화면에 대한 비율**이다(픽셀이 아니다). 0.5가 중앙
- 화면 끝에 붙일 때는 align을 start / end로 맞춘다(center인 채로는 삐져나온다)
- placement는 "fixed" / "cycle" / "random". anchors가 1개면 "fixed"로 한다
- scatter는 {"offsetRatio":0 이상, "rotateDeg":0 이상}
- path는 {"kind":"wave"|"arc"|"steps", "amplitude":숫자, "period":0보다 큼(선택)}
- charScaleRange는 [최소, 최대]의 2요소
- focusWord는 {"scale":0보다 큼, "pick":"longest"|"random"}
- **writing이 "vertical"일 때는 path / charScaleRange / focusWord를 쓰지 않는다**(무시되기 때문)
- 시간·소리·난수에 반응하는 값이나 수식은 쓸 수 없다(움직임은 별도 사양인 AnimationSpec의 담당)
- 존재하지 않는 속성·주석·끝 쉼표를 쓰지 않는다

만들고 싶은 가사 배치:
{여기에 만들고 싶은 배치를 적는다}

(기존 레이아웃을 수정하고 싶을 때만, 아래에 현재 JSON을 붙여 넣으세요. 새로 만드는 경우에는 이 절 전체를 삭제하세요)
현재 JSON:
{현재 JSON}

여기까지 복사