AnimationSpec v2 仕様書 — 自作の「文字の動き」

この文書は movlyric の文字の動き(歌詞アニメーション)の定義「AnimationSpec v2」の完全仕様です。 アプリの「スタイルエディタ →「動き」タブ → 文字の動き →「カスタム」→ AIで作る…」から使います(背景に図形を描く「背景エフェクト」は別物で、 そちらは GRAPHIC_SPEC(背景エフェクト) が仕様です)。 v1(モーションのみ)で書かれた JSON はそのまま v2 として有効です。v2 で色・縁取り・発光・軸別の伸縮が追加されました。 この文書だけを読んだ AI(または人間)が、アプリにそのまま貼って通る JSON を書けることを目的とします。 文書末尾に AI へ渡すプロンプトテンプレートがあります。この文書全体をプロンプトの一部として貼り付けても機能します。

1. 概要

movlyric は歌詞(フレーズ)を時間に合わせて画面に表示する動画エンジンです。 1つのフレーズは次の3段階で動きます。

  1. 入場(in): フレーズ表示窓の先頭(フレーズ発声開始の約1秒前)から、文字/単語が現れる動き
  2. 表示中(定常): 入場が終わった状態で静止。任意で activePulse(sin 波の脈動)を掛けられる
  3. 退場(out): 表示窓の末尾(フレーズ発声終了の約1秒後)に向けて消える動き

AnimationSpec はこの in / out / activePulse を keyframe ベースの宣言的 JSON で記述します。 コードや式は書けません。書けるのは以下の仕様にあるフィールドだけです。

2. JSON 形式の完全仕様

2.1 ルート: AnimationSpec

{ "in": <Phase>, "out": <Phase>, "activePulse": <Pulse> }
フィールド 必須 説明
in Phase 必須 入場フェーズ
out Phase 必須 退場フェーズ(退場演出が不要でも省略不可。無変化にしたい場合は §2.7 参照)
activePulse Pulse 任意 表示中の脈動。不要ならキーごと省略する(null は不可)

ルート直下に書けるキーはこの3つだけです。

2.2 Phase(in / out 共通)

フィールド 必須 値域 説明
durationMs 整数 必須 50〜4000 1ユニット(文字/単語)のフェーズ所要時間(ミリ秒)。小数不可
easing 文字列 必須 §3 の31種のいずれか フェーズ全体に1回適用されるイージング(keyframe 区間ごとには指定できない)
stagger Stagger 必須 ユニット間の時間差。時間差なしでも {"unit":"none","delayMs":0,"from":"start"} のように3フィールドすべて必須
keyframes Keyframe[] 必須 2〜8個 動きの列。t は 0..1 の狭義昇順(同値も不可)、先頭は必ず t=0、末尾は必ず t=1

2.3 Keyframe

フィールド 必須 値域 意味・単位
t 数値 必須 0〜1 フェーズ内の正規化時刻(0=開始、1=終了)
alpha 数値 任意 0〜1 不透明度(0=透明、1=不透明)
scale 数値 任意 0.1〜3 拡大率(1=等倍)
dx 数値 任意 -2〜2 横オフセット。単位は文字サイズ(fontSize)比(1.0=文字1つ分)。正=右
dy 数値 任意 -2〜2 縦オフセット。単位は文字サイズ比。正=下(例: 下から入場は正→0)
rotate 数値 任意 -360〜360 回転。単位は度。正=時計回り
scaleX 数値 任意 0.1〜3 横だけの拡大率。scale と掛け合わされる
scaleY 数値 任意 0.1〜3 縦だけの拡大率。scale と掛け合わされる
tint 整数 任意 0〜16777215 文字色(0xRRGGBB の10進または16進表記)
strokeWidth 数値 任意 0〜20 縁取りの太さ(px)。0 で縁取りなし
strokeColor 整数 任意 0〜16777215 縁取りの色
glow 数値 任意 0〜40 発光の強さ(ぼかし半径 px)。0 で発光なし
glowColor 整数 任意 0〜16777215 発光の色。省略すると文字色で光る(自己発光)

書けるプロパティは上の13個だけです。他の名前(x, y, opacity, color, blur, shadow など)は存在しません。 単位(dx/dy=文字サイズ比、rotate=度)はアプリ内のアニメーションエディタの表示単位(横/縦=文字サイズ比、回転=度)と同一です。

「未指定」の意味はプロパティごとに違います。 ここを取り違えると意図しない見た目になります。

プロパティ 未指定のとき
alpha / scale / scaleX / scaleY 1(変化なし)
dx / dy / rotate 0(変化なし)
tint 文字色を変えない(スタイルのカラオケ発声色がそのまま効く)
strokeWidth / strokeColor スタイルの縁取りをそのまま使う(0 にはならない)
glow / glowColor 発光なし

つまり色や縁取りを変えたくないなら、そのプロパティを書かないでくださいtint を書くと、その文字は歌唱に合わせた色替え(未発声→発声済み)をしなくなり、指定した色で固定されます。

scaleXscaleY を別々に動かすと、潰れて伸びる動き(squash & stretch)が作れます。弾む・着地する表現では、これがあると動きが柔らかくなります。

2.4 Stagger(時間差)

フィールド 必須 値域 説明
unit 文字列 必須 "none" / "char" / "word" 時間差の単位。none=フレーズ全体が一斉、char=文字ごと、word=単語ごと
delayMs 整数 必須 0〜500 ユニット1つ分の遅延(ミリ秒)。小数不可。unit が none でも 0 を明記する
from 文字列 必須 "start" / "end" / "center" 起点。start=先頭から、end=末尾から、center=中央から外へ(偶数個は中央2つが同時に最初)

n 番目に動くユニットの遅延は 順序×delayMs。ルビ(ふりがな)は常に親単語と同じ位相で動きます。 in と out で別々の stagger を指定できます(例: 入場は先頭から、退場は末尾から)。

2.5 Pulse(activePulse、任意)

フィールド 必須 値域 説明
property 文字列 必須 "scale" / "alpha" 揺らす対象
amplitude 数値 必須 0〜0.5 揺れ幅
periodMs 整数 必須 200〜4000 周期(ミリ秒)。小数不可

動作: 各ユニットの入場完了後〜退場開始前だけ1 + amplitude × sin(2π × nowMs / periodMs) を property に乗算します。 nowMs は動画の絶対時刻なので、脈動の位相はフレーズをまたいで連続します(決定論的 = プレビューと書き出しが一致)。 注意: property:"alpha" で定常 alpha が 1 のとき、1 を超える半周期はクランプされるため「暗くなる方向だけ」が見えます(明滅表現としてはそれで自然です)。

2.6 補間の意味論(疎 keyframe)

  • プロパティごとに独立して補間します。あるプロパティは「そのプロパティを持つ keyframe」だけを結んで線形補間します。
    • 例: alpha は t=0 と t=1 だけに書き、scale は t=0 / 0.5 / 1 に書く、というように疎に書けます。
  • そのプロパティを持つ最初の keyframe より手前は先頭の値、最後の keyframe より後は末尾の値を保持します(最近傍。補外はしません)。
  • どの keyframe にも書かれていないプロパティの扱いは §2.3 の「未指定のとき」の表に従います。モーション系は恒等値(alpha=1, scale=1, scaleX=1, scaleY=1, dx=0, dy=0, rotate=0)ですが、色・縁取りは「上書きしない」(スタイルの値がそのまま残る)点が違います。
  • easing は「フェーズの進行 0→1」に1回だけ適用され、その eased 値で全プロパティを補間します。

2.7 in と out の合成・定常状態

in と out は常に同時評価され、alpha と scale は乗算、dx / dy / rotate は加算で合成されます。したがって:

  • in の t=1 = 表示中の定常状態。通常は恒等値(alpha 1, scale 1, dx 0, dy 0, rotate 0)にします。
  • out の t=0 は必ず恒等値にしてください。そうしないと表示中の見た目が out の値でずれ続けます。
  • 退場演出を無くしたい場合は out を「無変化」にします: keyframes[{"t":0,"alpha":1},{"t":1,"alpha":1}] のように同じ値で書けば動きません(t は昇順を守ること)。

合成後の alpha は最終的に 0..1 にクランプされます。

2.8 タイムラインと短いフレーズでの圧縮

  • 表示窓はフレーズ発声区間の前後にそれぞれ約1000msの余白を持ちます。in は窓の先頭から始まり、out は「最後のユニットの t=1 がちょうど窓の末尾」になるよう逆算して配置されます。
  • フェーズ全体の所要時間は durationMs + 最大stagger遅延(順序最大×delayMs) です。これが表示窓より長い場合、タイムライン全体が等倍圧縮されます(窓が短くても文字が出ないままにならない)。文字数が多い歌詞で char stagger に大きい delayMs を使うと圧縮されやすいので、durationMs + 文字数×delayMs が 2000ms 程度に収まる値を推奨します。

3. イージング一覧(31種)

easing に書ける名前は以下の31種のみです(大文字小文字も厳密一致。ease-ineaseOutBack のような別表記は不可)。

名前 説明
linear 等速
inQuad / outQuad / inOutQuad 2次。ゆるやかな加速/減速/両方
inCubic / outCubic / inOutCubic 3次。標準的な加速/減速/両方
inQuart / outQuart / inOutQuart 4次。強めの加速/減速/両方
inQuint / outQuint / inOutQuint 5次。かなり強い加速/減速/両方
inSine / outSine / inOutSine sin 波。最も柔らかい加速/減速/両方
inExpo / outExpo / inOutExpo 指数。急激な加速/減速/両方
inCirc / outCirc / inOutCirc 円弧。終端で急な加速/減速/両方
inBack / outBack / inOutBack 行き過ぎて戻る(反動)
inElastic / outElastic / inOutElastic バネのような振動
inBounce / outBounce / inOutBounce ボールが弾むようなバウンド

重要(エンジンの実挙動): back / elastic 系は途中で進行が 0..1 の範囲外に出ますが、補間は最近傍クランプのため出力は端点 keyframe の値で留まります(t=1 の値を超えて飛び出すことはありません)。 「行き過ぎてから戻る」見た目を確実に出したい場合は、中間 keyframe で明示的に書いてください(例: scale を 0 → 1.15 → 1)。

4. よくある間違い(禁止事項)

以下は検証エラーまたは意図しない見た目になります。必ず避けてください

  • 値域違反: scale: 03.5alpha: 1.2dx: 2.5durationMs: 305000delayMs: 600amplitude: 0.8periodMs: 100 などはすべて不可。§2 の値域表に厳密に従う
  • 存在しないプロパティ: keyframe に書けるのは §2.3 の13個だけ(t, alpha, scale, scaleX, scaleY, dx, dy, rotate, tint, strokeWidth, strokeColor, glow, glowColor)。x, y, opacity, color, blur, skew, shadow などは不可。keyframe ごとの easing 指定も不可
  • 色を整数以外で書く: "#ff0066""red" は不可。0xRRGGBB を整数で書く(16進リテラル 0xff0066 または10進 16711782
  • 変えたくないプロパティを 0 で書く: strokeWidth: 0 は「縁取りを消す」指定。スタイルの縁取りを残したいなら書かない
  • t の順序違反: t の降順・同値の重複は不可(狭義昇順)。先頭 t=0 と末尾 t=1 を忘れない
  • keyframes の個数: 1個以下・9個以上は不可(2〜8個)
  • 小数の禁止箇所: durationMs / delayMs / periodMs は整数のみ
  • easing 名の表記揺れ: §3 の31種と厳密一致で書く
  • in / out の省略: 両方必須。stagger の3フィールド(unit / delayMs / from)も常に必須
  • out の t=0 を恒等値以外にする: 表示中の見た目がずれる(§2.7)
  • JSON 以外の混入: コメント、末尾カンマ、式("dy": "0.5 * 2" など)は不可。値はすべて数値リテラルか許可された文字列

5. 作例

3つともこのままアプリに貼って通る完全な JSON です。

5.1 ふわっと浮かび上がる

文字が少し下から柔らかくフェードインし、退場は上に抜けながらフェードアウト。

{
  "in": {
    "durationMs": 700,
    "easing": "outSine",
    "stagger": { "unit": "char", "delayMs": 40, "from": "start" },
    "keyframes": [
      { "t": 0, "alpha": 0, "dy": 0.4 },
      { "t": 1, "alpha": 1, "dy": 0 }
    ]
  },
  "out": {
    "durationMs": 600,
    "easing": "inSine",
    "stagger": { "unit": "char", "delayMs": 30, "from": "start" },
    "keyframes": [
      { "t": 0, "alpha": 1, "dy": 0 },
      { "t": 1, "alpha": 0, "dy": -0.3 }
    ]
  }
}

5.2 文字が回転しながら弾んで登場

小さく回転した状態から、行き過ぎ(scale 1.15)を中間 keyframe で明示して弾む入場。退場は末尾の文字から逆回転で縮む。 alpha は疎指定の例: in では t=0.6 までに 1 に達し、以降は末尾値 1 を保持します。

{
  "in": {
    "durationMs": 800,
    "easing": "outBack",
    "stagger": { "unit": "char", "delayMs": 60, "from": "start" },
    "keyframes": [
      { "t": 0, "alpha": 0, "scale": 0.3, "rotate": -180 },
      { "t": 0.6, "alpha": 1, "scale": 1.15, "rotate": 20 },
      { "t": 1, "scale": 1, "rotate": 0 }
    ]
  },
  "out": {
    "durationMs": 500,
    "easing": "inBack",
    "stagger": { "unit": "char", "delayMs": 40, "from": "end" },
    "keyframes": [
      { "t": 0, "alpha": 1, "scale": 1, "rotate": 0 },
      { "t": 1, "alpha": 0, "scale": 0.3, "rotate": 180 }
    ]
  }
}

5.3 単語ごとに下からスライドイン + 表示中ゆっくり明滅

単語単位の stagger で下から順に現れ、表示中は activePulse でゆっくり明滅。退場はフレーズ全体が一斉にフェードアウト。

{
  "in": {
    "durationMs": 600,
    "easing": "outCubic",
    "stagger": { "unit": "word", "delayMs": 120, "from": "start" },
    "keyframes": [
      { "t": 0, "alpha": 0, "dy": 0.8 },
      { "t": 1, "alpha": 1, "dy": 0 }
    ]
  },
  "out": {
    "durationMs": 400,
    "easing": "inQuad",
    "stagger": { "unit": "none", "delayMs": 0, "from": "start" },
    "keyframes": [
      { "t": 0, "alpha": 1 },
      { "t": 1, "alpha": 0 }
    ]
  },
  "activePulse": { "property": "alpha", "amplitude": 0.25, "periodMs": 1600 }
}

5.4 ネオンのように光りながら登場

glow で発光させ、glowColor で光の色を指定。退場では発光を 0 へ収束させる。tint を書いていないので、文字色は歌唱に合わせた色替えがそのまま効く。

{
  "in": {
    "durationMs": 500,
    "easing": "outCubic",
    "stagger": { "unit": "char", "delayMs": 40, "from": "start" },
    "keyframes": [
      { "t": 0, "alpha": 0, "glow": 0, "glowColor": 65535 },
      { "t": 0.6, "alpha": 1, "glow": 34, "glowColor": 65535 },
      { "t": 1, "alpha": 1, "glow": 20, "glowColor": 65535 }
    ]
  },
  "out": {
    "durationMs": 400,
    "easing": "inQuad",
    "stagger": { "unit": "none", "delayMs": 0, "from": "start" },
    "keyframes": [
      { "t": 0, "alpha": 1, "glow": 20, "glowColor": 65535 },
      { "t": 1, "alpha": 0, "glow": 0, "glowColor": 65535 }
    ]
  }
}

5.5 色が変わりながら現れる

tint を動かすと文字色が補間される。tint を書いたフレーズは歌唱に合わせた色替えをしなくなるので、色は全部この spec で決める。

{
  "in": {
    "durationMs": 700,
    "easing": "outQuad",
    "stagger": { "unit": "char", "delayMs": 30, "from": "center" },
    "keyframes": [
      { "t": 0, "alpha": 0, "tint": 16711808 },
      { "t": 1, "alpha": 1, "tint": 16777215 }
    ]
  },
  "out": {
    "durationMs": 400,
    "easing": "inQuad",
    "stagger": { "unit": "none", "delayMs": 0, "from": "start" },
    "keyframes": [
      { "t": 0, "alpha": 1 },
      { "t": 1, "alpha": 0 }
    ]
  }
}

5.6 潰れて伸びながら着地(squash & stretch)

scaleXscaleY を逆方向に動かす。落ちてくる間は縦長、着地の瞬間に横へ潰れ、そこから等倍へ戻す。

{
  "in": {
    "durationMs": 550,
    "easing": "outBack",
    "stagger": { "unit": "char", "delayMs": 45, "from": "start" },
    "keyframes": [
      { "t": 0, "alpha": 0, "dy": -1.2, "scaleX": 0.75, "scaleY": 1.35 },
      { "t": 0.7, "alpha": 1, "dy": 0, "scaleX": 1.3, "scaleY": 0.7 },
      { "t": 1, "alpha": 1, "dy": 0, "scaleX": 1, "scaleY": 1 }
    ]
  },
  "out": {
    "durationMs": 350,
    "easing": "inQuad",
    "stagger": { "unit": "none", "delayMs": 0, "from": "start" },
    "keyframes": [
      { "t": 0, "alpha": 1 },
      { "t": 1, "alpha": 0, "scaleY": 0.6 }
    ]
  }
}

6. AI 用プロンプトテンプレート

以下の枠内をコピーして AI に渡してください。この仕様書全体を先に貼り付け、その直後にテンプレートを続ける使い方を推奨します(仕様書なしでテンプレート単体を渡すと AI が値域や easing 名を誤ります)。

ここからコピー

あなたは movlyric の歌詞アニメーション設計者です。上に貼り付けた「AnimationSpec v2 仕様書」に厳密に従い、AnimationSpec の JSON を1つ作成してください。

出力規則:
- JSON のみを出力する(説明文・前置き・後書きは書かない。JSON をコードフェンスで囲むのは可)
- ルートは {"in": ..., "out": ...} で、必要な場合のみ "activePulse" を加える
- keyframes は 2〜8個、t は 0..1 の狭義昇順で、先頭は必ず t=0、末尾は必ず t=1
- keyframe に書けるのは t, alpha(0..1), scale(0.1..3), scaleX(0.1..3), scaleY(0.1..3), dx(-2..2), dy(-2..2), rotate(-360..360), tint(0..16777215), strokeWidth(0..20), strokeColor(0..16777215), glow(0..40), glowColor(0..16777215) のみ。単位: dx/dy は文字サイズ比(正=右/下)、rotate は度、strokeWidth/glow は px、色は 0xRRGGBB の整数
- **変えたくないプロパティは書かない**。tint を書くと歌唱に合わせた色替えが止まり指定色で固定される。strokeWidth/strokeColor を書かなければスタイルの縁取りがそのまま残る(書かない = 0 ではない)
- 光らせたいときは glow(オフセットのない発光)を使う。glowColor を省くと文字色で光る
- 潰して伸ばす動き(squash & stretch)は scaleX と scaleY を逆方向に動かして作る
- durationMs(整数 50..4000) / stagger.delayMs(整数 0..500) / activePulse.periodMs(整数 200..4000) を守る。stagger は unit("none"/"char"/"word")・delayMs・from("start"/"end"/"center") の3つすべて必須
- easing は仕様書のイージング一覧にある31種の名前のみ(厳密一致)
- out の t=0 の keyframe は恒等値(alpha 1, scale 1, dx 0, dy 0, rotate 0 に相当)にする
- 存在しないプロパティ・コメント・末尾カンマ・式を書かない

作りたい動き:
{ここに作りたい動きを書く}

(既存のアニメーションを修正したい場合のみ、以下に現在の JSON を貼ってください。新規作成の場合はこの節ごと削除してください)
現在の JSON:
{現在の JSON}

ここまでコピー