# Remotion x VOICEVOX ゆっくり解説テンプレート

Remotion と VOICEVOX を組み合わせて、複数キャラクターが時系列で登場・発話する動画テンプレートです。
サンプルテーマは「ネコミミはなぜかわいいのか？」です。

## 使い方

### 1. 依存関係をインストール
```bash
npm install
```

### 2. VOICEVOX エンジンを起動
VOICEVOX のエンジンを起動してください。既定では `http://host.docker.internal:50021` を参照します。
詳細は公式リポジトリを参照してください。

https://github.com/VOICEVOX/voicevox_engine

### 3. 脚本を編集
`src/data/yukkuri-composition/script.ts` の `characters` と `timeline` を編集します。

```ts
show("sayo", {caption: "ネコミミ代表として、小夜が登場！"});
say("sayo-001", "sayo", "小夜です。ネコミミ代表として、耳のかわいさを証明しに来ました。");
say("zunda-005", "zundamon", "それじゃあ、また次回なのだ！");
```

- `characters`: 表示名、VOICEVOX の `speakerName` / `styleName`、立ち絵設定を定義します。
- `initialVisibleCharacters`: 動画開始時から表示するキャラクターを定義します。
- `show(...)`: キャラクターを画面に登場させ、任意の説明字幕を出します。
- `say(...)`: キャラクターに読み上げさせ、字幕と音声を同期します。
- 行ごとにスタイルを変える場合は `say(..., {voicevox: {styleName: "スタイル名"}})` を使います。

### 3.1 新しいコンポジションを作成
```bash
npm run composition:create -- my-new-video
```

コンポジション名は kebab-case の slug で指定します。
上記の例では Remotion ID `MyNewVideo` と
`src/data/my-new-video/script.ts` が作成されます。

作成後は、`src/data/my-new-video/script.ts` の
`compositionTitle` と `timeline`、必要に応じて
`src/data/my-new-video/characters.ts` のキャラクター定義を編集します。

音声と口パクを生成する場合は、作成された専用コマンドを実行します。

```bash
npm run voice:generate:my-new-video
npm run lipsync:generate:my-new-video
npm run start -- --webpack-poll=1000
```

### 4. 音声を生成
```bash
npm run voice:generate
```

対象一覧と使い方を表示します。このコマンドだけでは音声ファイルを生成しません。
音声が未生成の行は、プレビュー時にテキスト長から尺を推定します。

全コンポジションの音声をまとめて生成する場合は、次を実行します。

```bash
npm run voice:generate:all
```

YukkuriComposition の音声だけを明示して生成する場合は、次も使えます。

```bash
npm run voice:generate:yukkuri-composition
```

`src/data/yukkuri-composition/script.ts` の `say(...)` から
`public/audio/yukkuri-composition/lines/*.wav` を生成し、
`src/data/yukkuri-composition/voicevox-manifest.json` に長さ・話者・スタイル情報を記録します。

ピザ窯サンプルの音声を生成する場合は、次を実行します。

```bash
npm run voice:generate:pizza-kiln
```

PizzaOvenProject01 の音声を生成する場合は、次を実行します。

```bash
npm run voice:generate:pizza-oven-project-01
```

ずんだもん別バージョン立ち絵デモの音声を生成する場合は、次を実行します。

```bash
npm run voice:generate:zundamon-rework-standee-demo
```

### 5. 口パク指示データを生成
Rhubarb Lip Sync CLI を使い、VOICEVOX 音声から口形タイムラインを生成します。

```bash
npm run lipsync:generate
```

対象一覧と使い方を表示します。このコマンドだけでは口パクデータを生成しません。

生成物は、Rhubarb 方式では生 JSON が `public/lipsync/raw/*.rhubarb.json`、
Remotion 用に正規化した JSON が `src/generated/lipsync/*.mouth.json`、
プレビュー時に同期 import する集約 manifest が `src/generated/lipsync/manifest.json` です。
VOICEVOX query 方式のコンポジションでは、VOICEVOX の `audio_query` に含まれるモーラ長から
直接 `src/generated/lipsync/*.mouth.json` を生成します。

全コンポジションの口パクデータをまとめて生成する場合は、次を実行します。

```bash
npm run lipsync:generate:all
```

単体音声だけ再生成する場合は、次のように音声ファイルを指定できます。

```bash
npm run lipsync:generate -- public/audio/yukkuri-composition/lines/zunda-001.wav
```

時系列シナリオ単位で再生成する場合は、対応する VOICEVOX manifest を指定します。

```bash
npm run lipsync:generate -- --source-manifest src/data/yukkuri-composition/voicevox-manifest.json
```

コンポジション別の専用コマンドもあります。

```bash
npm run lipsync:generate:yukkuri-composition
npm run lipsync:generate:pizza-kiln
npm run lipsync:generate:pizza-oven-project-01
npm run lipsync:generate:zundamon-rework-standee-demo
```

処理順は `1. npm run voice:generate:all` または対象別の `voice:generate:*`、
`2. npm run lipsync:generate:all` または対象別の `lipsync:generate:*`、
`3. npm run start` です。音声を作り直したら、口パク指示データも再生成してください。

Rhubarb CLI は次の順で検出します。

- `RHUBARB_BIN` に指定された実行ファイル
- `node_modules/.bin/rhubarb`
- `tools/rhubarb/` または `vendor/rhubarb/` 配下の実行ファイル
- PATH 上の `rhubarb`

Windows / Linux / macOS で実行ファイル名が異なることがあります。
Dev Container で使う場合は Linux 版 Rhubarb を配置し、必要なら
`RHUBARB_BIN=/usr/local/bin/rhubarb` のように指定してください。

既定では Rhubarb の `phonetic` recognizer を使います。音声のみからの推定なので、
日本語の母音完全一致ではなく、動画用に自然に見える口パクを目的にしています。
Rhubarb 口形は次のように丸めます。

```ts
{
  X: "rest",
  A: "closed",
  B: "i",
  C: "e",
  D: "a",
  E: "o",
  F: "u",
  G: "i",
  H: "e",
}
```

`zundamon-rework-standee-demo` は、Rhubarb ではなく VOICEVOX query 方式を使います。
この方式では VOICEVOX の `audio_query` に含まれる `a` / `i` / `u` / `e` / `o` などの
母音とモーラ長から口形タイムラインを作るため、日本語母音の確認デモに向いています。
既存コンポジションは従来どおり Rhubarb 方式です。

### 6. プレビュー
```bash
npm run start
```

Dev Container / Windows 共有フォルダなどでファイル変更検知が不安定な場合は、
Remotion の polling オプションを npm script 経由で渡します。

```bash
npm run start -- --webpack-poll=1000
```

### 7. レンダリング
```bash
npm run render YukkuriZundamon out/video.mp4
```

## 編集ポイント
- 時系列脚本: `src/data/yukkuri-composition/script.ts`
- 音声タイミング: `src/data/yukkuri-composition/voicevox-manifest.json` (自動生成)
- 口パクタイミング: `src/generated/lipsync/manifest.json` (自動生成)
- 映像の構成: `src/yukkuri-composition/index.tsx`
- 立ち絵セット: `src/standee-sets.ts`

## 収録コンポジション
- `YukkuriZundamon`: ずんだもんと小夜のゆっくり解説サンプル
- `PizzaKilnSayo`: 小夜のピザ窯サンプル
- `PizzaOvenProject01`: ピザ窯制作記録サンプル
- `ZundamonReworkStandeeDemo`: `zundamon-ohnegus-rework-baby` の全表情表示と default 表情リップシンク確認デモ

## 字幕方針

本テンプレートは、短編 VOICEVOX ドラマ動画や、実写映像を背景にした解説動画での利用を主用途としています。

そのため、字幕は `@remotion/captions` の `Caption` 型 JSON による単語単位・時刻単位の正式な字幕データとしては扱わず、`src/data/yukkuri-composition/script.ts` の `say(...)` / `show(...)` に紐づく発話単位・シーン単位の表示テキストとして扱います。

`say(...)` の字幕は VOICEVOX で生成した音声尺、または未生成時の推定尺に同期して表示します。SRT/VTT 互換、単語単位ハイライト、自動文字起こし字幕が必要になった場合は、その時点で `@remotion/captions` の導入を検討します。

字幕の任意位置で改行したい場合は、`say(...)` や `subtitle` の文字列内に `\n` を入れます。`\n` は字幕表示だけの改行として扱い、VOICEVOX 読み上げ用テキストからは取り除かれます。

## VOICEVOX設定
- `VOICEVOX_URL` (既定: `http://host.docker.internal:50021`)
- 話者とスタイルは各コンポジションの `src/data/{コンポジション名}/script.ts` の `characters.*.voicevox` で指定します。

## 立ち絵セットの追加・差し替え
立ち絵本体、口パク画像、通常表示時の基本レイアウトは `src/standee-sets.ts` の `standeeSets` にまとめています。
新しい立ち絵を追加する場合は、次の流れで修正します。

### 1. 画像素材を配置
`public/image/` 配下に、立ち絵本体と口パク画像を配置します。`imagePath` や `mouthImageDir` には
`public` からの相対パスを指定します。

```txt
public/image/zundamon_ohnegus_ai_base.png
public/image/zundamon-ohnegus-ai-rhubarb-mouths/
  a.png
  i.png
  u.png
  e.png
  o.png
  closed.png
  rest.png
```

口パク画像は、立ち絵本体と同じキャンバス寸法・同じ位置合わせにしてください。
口だけが差分として重なる前提で、`LipSyncedStandeeImage` が本体画像の上に同じサイズで重ねます。

素材ファイル名は、環境差による参照トラブルを避けるため、英数字・ハイフン・アンダースコア・ドットを基本にします。
表情や口形の意味は、ファイル名・ディレクトリ名・必要に応じた管理用データのどれかで追えるようにしてください。

### 1.1. 表情と口形の管理
現在の実装では、1 つの立ち絵ベースに対して `mouthImageDir` の中に次の 7 種類の口形画像を置く構成を標準とします。

```txt
a.png
i.png
u.png
e.png
o.png
closed.png
rest.png
```

Rhubarb が出力する `X` / `A` / `B` / `C` / `D` / `E` / `F` / `G` / `H` は、
`src/lipsync/rhubarb-map.ts` と `scripts/lipsync-utils.js` で上記の抽象口形へ変換します。
この変換は「音声解析結果を日本語向けの口形名へ丸める」ための共通ルールとして扱い、
表情差分や特定素材のファイル名を直接管理する場所にはしません。

表情ベースが複数ある素材では、表情ごとに同じ抽象口形をそろえるのを基本方針とします。
たとえば `default`、`blink`、`confident` のような表情がある場合は、それぞれの表情に
`a` / `i` / `u` / `e` / `o` / `closed` / `rest` 相当の画像を持たせます。
表情側に存在しない口形がある場合は、`rest` または近い閉口画像へフォールバックできるようにします。

フル画像として表情と口形が一体化している素材は、既存の「本体画像 + 口だけ差分」方式へ無理に分解せず、
表情 ID と口形 ID の組み合わせで画像を選ぶ素材として扱います。
現時点では `mouthImageDir/{mouth}.png` の単一表情ベースが実装済みの安定形です。

将来的には、複数表情ベースのアバターを次のような形で扱えるように拡張する方針です。

```ts
expressions: {
  default: {
    rest: "image/example/default-rest.png",
    closed: "image/example/default-closed.png",
    a: "image/example/default-a.png",
    i: "image/example/default-i.png",
    u: "image/example/default-u.png",
    e: "image/example/default-e.png",
    o: "image/example/default-o.png",
  },
  confident: {
    rest: "image/example/confident-rest.png",
    closed: "image/example/confident-closed.png",
    a: "image/example/confident-a.png",
    i: "image/example/confident-i.png",
    u: "image/example/confident-u.png",
    e: "image/example/confident-e.png",
    o: "image/example/confident-o.png",
  },
}
```

この形では、口パクタイムラインは従来どおり Rhubarb 由来の抽象口形を返し、
脚本や演出側が現在の表情 ID を選び、描画側が `expressions[expressionId][mouth]` を参照します。
これにより、音声解析、表情演出、素材配置を分離して管理できます。

### 2. `src/standee-sets.ts` にセットを追加
`standeeSets` に、素材パスと基本レイアウトを追加します。

```ts
"zundamon_ohnegus_ai": {
  kind: "zundamon",
  imagePath: "image/zundamon_ohnegus_ai_base.png",
  mouthImageDir: "image/zundamon-ohnegus-ai-rhubarb-mouths",
  imageLayout: {
    width: 540,
    maxHeight: 730,
    translateY: 0,
    flipX: true,
  },
},
```

- `kind`: キャラクター種別です。既存は `"zundamon"` / `"sayo"` です。
- `imagePath`: 立ち絵本体のパスです。
- `mouthImageDir`: `a.png` などの口パク画像を置いたディレクトリです。
- `imageLayout.width`: 通常コンポジションでの表示幅です。
- `imageLayout.maxHeight`: 通常コンポジションでの最大表示高さです。
- `imageLayout.translateY`: 通常コンポジションで上下位置を微調整します。
- `imageLayout.flipX`: 左右反転したい場合に `true` にします。

### 3. キャラクター定義で使うセットを選ぶ
YukkuriComposition は `src/data/yukkuri-composition/script.ts`、ピザ窯コンポジションは
`src/data/pizza-kiln/script.ts` の `characters.*.avatar` で、使いたい立ち絵セットを展開します。
対象ファイルで `getStandeeSet` を import して使います。

```ts
avatar: {
  ...getStandeeSet("sayo_ohnegus_ai"),
  accentColor: "#6b5f83",
  nameplatePosition: "none",
  idleAnimationType: "none",
  speakingAnimationType: "rhubarbLipSync",
},
```

`speakingAnimationType: "rhubarbLipSync"` を指定すると、`src/generated/lipsync/manifest.json`
の口形タイムラインに合わせて `mouthImageDir` の画像を切り替えます。

新しい時系列コンポジションでは、`VQChronologicalScenario` の `assetWorkflow` に
VOICEVOX 音声生成と口パク生成のパスをセットで定義します。口形はコンポジションに
固定値を書かず、`speech.id` をキーに `src/generated/lipsync/manifest.json` から参照します。

### 4. コンポジション固有の見栄えを調整
通常の全身表示は `src/standee-sets.ts` の `imageLayout` で調整します。
コンポジションごとに特別な配置がある場合だけ、描画側を調整します。

- YukkuriComposition: `src/yukkuri-composition/index.tsx`
- ピザ窯コンポジション: `src/pizza-kiln-composition.tsx` の `SayoStandee`

ピザ窯コンポジションは、通常背景用の `STAGE_STANDEE_*` と、実写動画右下用の
`CORNER_*` でサイズと切り抜き方を分けています。

### 5. 確認
変更後は、可能な範囲で次を実行します。

```bash
./node_modules/.bin/tsc --noEmit
npm run lint
./node_modules/.bin/remotion still src/index.ts YukkuriZundamon /tmp/yukkuri.png --frame=160
./node_modules/.bin/remotion still src/index.ts PizzaKilnSayo /tmp/pizza-kiln.png --frame=30
```

音声や `say(...)` を変更した場合は、対象別の `voice:generate:*` または `npm run voice:generate:all` と、
対象別の `lipsync:generate:*` または `npm run lipsync:generate:all` も実行してください。
立ち絵画像だけを差し替える場合は、口パクタイミングの再生成は不要です。

以前の `public/audio/zundamon.txt` と `src/data/script.json` は互換用に残していますが、現在は参照しません。
