# Recast

> REST で、分析済みの動画を自分のキャストで Recast します。実行の見積もりと購入、インタラクティブなゲートへの回答、オーディオのリミックス、作成したスクリプトのインポートができます。

Source: https://nodaro.ai/ja/docs/developers/api/recast

**Recast API** は、分析済みの動画を、あなた自身のキャストで再生成します。実行の見積もりを出してプランを購入し、シーンごとにレンダリングします。インタラクティブな実行では、その途中でキャスト、シーンの静止画、音楽を選べます。映画を JSON のスクリプトとして書いてインポートすることもできるので、元の動画がまったくなくても Recast できます。[recast.nodaro.ai](https://recast.nodaro.ai) も、このエンジンで動いています。

Recast は Nodaro Cloud でのみ動作し、セルフホスティング環境では、これらのルートは `404` を返します。セルフホスティング環境では代わりに、ワークフローの中で [**動画分析**（Video Analysis）](https://nodaro.ai/docs/nodes/video/video-analysis)ノードを使って動画を分解し、[**動画生成**（Generate Video）](https://nodaro.ai/docs/nodes/video/generate-video)でシーンを再生成してください。これらのルートの認証には、Bearer トークンを使います。[認証](https://nodaro.ai/docs/developers/api/authentication)を参照してください。

## エンドポイント
| メソッド | パス | 内容 | 料金 |
| --- | --- | --- | --- |
| `POST` | `/v1/recast/estimate` | 実行の見積もりを出します。 | 無料 |
| `POST` | `/v1/recast` | 実行を作成します。このときにプランを購入します。 | 見積もったプランの料金 |
| `GET` | `/v1/recast/:id` | 実行をポーリングし、保留中のゲートを読み取ります。 | 無料 |
| `POST` | `/v1/recast/:id/start` | `planned` の実行のレンダリングを開始します。 | プランの料金に含まれます |
| `POST` | `/v1/recast/:id/select` | 保留中のゲートに回答します。 | 無料 |
| `POST` | `/v1/recast/:id/estimate-rescore` | 新しいサウンドトラックか、新しいミックスの見積もりを出します。 | 無料 |
| `POST` | `/v1/recast/:id/rescore` | 見積もったオーディオの変更を適用します。 | 見積もりの料金 |
| `GET` | `/v1/video-analysis/authoring-skill` | スクリプトを書くためのガイドを取得します。 | 無料 |
| `POST` | `/v1/video-analysis/import/validate` | スクリプトを検証します。 | 無料 |
| `POST` | `/v1/video-analysis/import` | スクリプトを、完了済みの分析としてインポートします。 | 無料 |

## 実行の見積もりと作成
実行は、分析ジョブから始まります。分析ジョブは、[**動画分析**](https://nodaro.ai/docs/nodes/video/video-analysis)ノードで分析した動画か、[インポートしたスクリプト](#import-a-script-as-a-movie)のどちらかです。まず、見積もりを出します。`POST /v1/recast/estimate` は、実行の作成に使う設定を受け取り、`{ totalCredits, breakdown }` を返します。

次に、`POST /v1/recast` で実行を作成し、そのプランを購入します。戻り値は `{ recastId }` です。ボディには `workflowId` が必要です。これは、実行を関連付ける、あなたが所有するワークフローの ID です。`workflowId` がないと、このルートは `400 workflow_id_required` を返します。存在しない ID や、ほかのユーザーのワークフローの ID を指定すると、`404 workflow_not_found` を返します。

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/recast/estimate \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "analysisJobId": "6c1e8a3f-9b2d-4f5a-8e7c-3d1b9a5f2e6c", "resolution": "720p", "interactive": true }'

curl -X POST https://app.nodaro.ai/v1/recast \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"workflowId": "8d3f5b7a-1c9e-4a2d-b6f8-4e2a7c9d1b3f",
"analysisJobId": "6c1e8a3f-9b2d-4f5a-8e7c-3d1b9a5f2e6c",
"resolution": "720p",
"interactive": true,
"clientCapabilities": ["sheet-gate"]
}'
```

**TypeScript SDK**

```ts

const client = createClient({
baseUrl: 'https://app.nodaro.ai',
auth: new StaticTokenAuth(process.env.NODARO_API_KEY!),
})

const quote = await client.recast.estimate({ analysisJobId, resolution: '720p', interactive: true })
console.log(quote.totalCredits, quote.breakdown)

const { recastId } = await client.recast.create({
workflowId,
analysisJobId,
resolution: '720p',
interactive: true,
clientCapabilities: ['sheet-gate'],
})
```

**CLI**

```bash
nodaro recast estimate --analysis-job <jobId> --resolution 720p --json
nodaro recast create --workflow <workflowId> --analysis-job <jobId> --resolution 720p --json
```

<TypeTable
type={{
workflowId: { type: 'string (uuid)', description: '作成時のみ。実行を関連付ける、あなたが所有するワークフローです。', required: true },
analysisJobId: { type: 'string (uuid)', description: 'Recast する分析です。動画分析ノードか、スクリプトのインポートで作成したものです。', required: true },
fidelity: { type: 'string', description: '実行が分析にどれだけ忠実に従うかです。インポートしたスクリプトでは、書かれたとおりにレンダリングする faithful を使います。' },
rightsAttested: { type: 'boolean', description: '作成時のみ。インポートしたスクリプトを faithful でレンダリングする場合は、true が必須です。' },
resolution: { type: 'string', description: 'レンダリングの解像度です。たとえば 480p、720p、1080p です。' },
segmentSec: { type: 'string', description: 'レンダリングで、シーンをパートにまとめる方法です。秒数ではなく、まとめ方の名前です。scenes-max（「長め」。つなぎ目が最も少ない）、scenes（「短め」）、max（モデルが許容する最長のパート。シーンごとにはまとめない）のいずれかです。' },
renderMethod: { type: 'string', description: 'セグメントのレンダリング方法です。たとえば extend や keyframes です。' },
provider: { type: 'string', description: '動画モデルです。' },
interactive: { type: 'boolean', description: 'ゲートで停止し、キャスト、静止画、音楽を選べるようにします。追加料金がかかり、その分は見積もりに含まれます。' },
clientCapabilities: { type: 'string[]', description: '作成時のみ。クライアントが回答できるゲートの種類です。たとえば sheet-gate です。' },
}}
/>

レンダリングの設定をまとめて再利用するには、`recast-render` プリセットとして保存します。[プリセット](https://nodaro.ai/docs/developers/api/presets#recast-render-presets)を参照してください。

## 実行の進行を確認する
`GET /v1/recast/:id` は、`{ status, interactive?, capabilities?, audio? }` を返します。ステータスは `planning`、`planned`、`generating` と進み、最後に `completed` か `failed` になります。`planned` の実行は、`POST /v1/recast/:id/start` を待ちます。このルートはレンダリングを開始し、`{ gvpJobId? }` を返します。開始のルートは冪等で、追加の料金はかかりません。レンダリングの料金は、プランの見積もりにすでに含まれているためです。

**curl**

```bash
curl https://app.nodaro.ai/v1/recast/2e9b4d6f-8a1c-4e3b-9f5d-7c2a4e6b8d1f \
  -H "Authorization: Bearer $NODARO_API_KEY"

curl -X POST https://app.nodaro.ai/v1/recast/2e9b4d6f-8a1c-4e3b-9f5d-7c2a4e6b8d1f/start \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

**TypeScript SDK**

```ts
const run = await client.recast.get(recastId)
if (run.status === 'planned') await client.recast.start(recastId)
```

**CLI**

```bash
nodaro recast status <recastId> --json
nodaro recast start <recastId>
```

## インタラクティブなゲートに回答する
インタラクティブな実行は、サーバーが進めます。選択が不要なステップはすべて Nodaro が進めるので、あなたはポーリングしてゲートに回答するだけです。回答を待っているゲートがあるときは、ステータスの `interactive.next` にそのゲートが示されます。ゲートは、次の順番で開きます。

| `gate` | 選ぶもの |
| --- | --- |
| `cast` | キャストメンバー 1 人につき、ポートレート 1 枚です。 |
| `sheet` | 人物の場合のみで、実行で提示されたときに選びます。選んだ顔を共有する 3 枚のアイデンティティシートのうちの 1 枚です。そのため、選ぶのは体型と服装です。 |
| `anchors` | シーンのセグメントの静止画です。 |
| `music` | 映画の 1 つの区間に使う音楽です。 |

ゲートが開くのは、作成時に `clientCapabilities` で宣言した種類（たとえば `sheet-gate`）だけです。それ以外のゲートは自動で決定されるので、クライアントが回答できない質問を受け取ることはありません。

回答には、`POST /v1/recast/:id/select` を使います。選択は無料です。

| フィールド | 内容 |
| --- | --- |
| `gate` | `cast`、`sheet`、`anchors`、`music` のいずれかです。 |
| `picks` | `cast` と `sheet` で使います。あなたの選択を、保留中のゲートが示す形式で指定します。 |
| `segment`, `anchorPicks` | `anchors` で使います。セグメントと、選んだ静止画を表す `{ start?, end? }` です。 |
| `section`, `musicPick` | `music` で使います。区間と、選んだトラックです。 |
| `finishAuto` | `true` にすると、このゲートと残りのすべてのゲートを、自動評価に任せます。 |

```ts
await client.recast.resolveGate(recastId, { gate: 'cast', picks })
await client.recast.resolveGate(recastId, { gate: 'music', section: 0, musicPick: 1, finishAuto: true })
```

放置されたインタラクティブな実行も安全です。実行は待機を続け、期限が過ぎると自動で確定します。

## サウンドトラックやミックスを変更する
テイクが完了した後は、動画をもう一度レンダリングせずに、音楽を差し替えたり、ミックスのバランスを調整し直したりできます。この機能を使えるのは、ステータスに `capabilities.audioLayers: 1` が含まれ、テイクに `audio` マニフェストがある場合だけです。

```ts
interface RecastAudioManifestV1 {
version: 1
revision: string
mode: 'bed' | 'replace'
present: { music?: true; video?: true }
layers: { music?: { url: string }; video?: { url: string } }
bakedEffectiveGain: { music?: number; video?: number }
pendingRescore?: {
jobId: string
requestId: string
state: 'pending' | 'running'
expectedAudioRevision: string
requestedEffectiveGain: { music?: number; video?: number }
}
}
```

- `present` は、テイクにあるオーディオのレーンを示します。`music` と、`bed` モードでは元の `video` の音声です。
- `layers` は、ブラウザーで再生できるプレビューファイルがあるレーンだけを示します。`layers` にないレーンも、ダウンロードには含まれていることがあります。
- `bakedEffectiveGain` は、現在のファイルでの各レーンのレベルを、パーセントで示します。
- ステータスの `resultUrl` が、受け取る唯一の動画 URL です。

### 見積もってから適用する
見積もりと適用には、同じ操作を渡します。音楽の差し替えは 1 つまで送れます。`audioUrl` か、`brief` を付けた 1 つ以上の `sections` のどちらかです。それに加えて、希望する `mix` の全体を送ります。ミックスだけを送ることもできます。

```json
{
"expectedAudioRevision": "server-revision",
"sections": [{ "index": 0, "brief": "Sparse analogue pulse" }],
"mix": {
"music": { "gain": 60, "muted": false },
"video": { "gain": 85, "muted": false }
}
}
```

1. **見積もる**：`POST /v1/recast/:id/estimate-rescore` は無料で、`{ credits, audioRevision, noOp }` を返します。残高が足りない場合でも、料金を返します。
2. **適用する**：`POST /v1/recast/:id/rescore` は、同じボディに `requestId`（UUID）を加えたものを受け取ります。`expectedAudioRevision` も同じ値にします。戻り値は `{ recastId, jobId }` で、何も変わらない場合は `{ recastId, noOp: true, audioRevision }` です。no-op の場合、クレジットは確保されず、ジョブも作成されません。
3. **確認する**：ステータスをポーリングします。`audio.pendingRescore` は操作を示し、再読み込みしても消えません。新しいリビジョンが公開されるか、操作が失敗すると消えます。次の操作の前に、ステータスをもう一度読み取ってください。

ゲインは 0〜200 のパーセントで、ミュートしたレーンは 0 として扱われます。指定できるのは、`present` にあるレーンと、このリクエストで追加する音楽だけです。`replace` モードのテイクには、`video` のレーンがありません。また、結果のすべてのレーンを無音にすることはできません。`requestId` を再利用するのは、まったく同じリクエストを再試行するときだけにしてください。

音楽を差し替えるときは、`mix` の全体も送ってください。`mix` を省略できるのは、結果が決まった標準のレベルと一致する場合だけです。標準のレベルは、`bed` モードでは音楽 35 と動画 100、`replace` モードでは音楽 100 です。現在のレベルがそれ以外の場合は、`409 legacy_mix_mismatch` が返されます。

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/recast/2e9b4d6f-8a1c-4e3b-9f5d-7c2a4e6b8d1f/rescore \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
"requestId": "4f6a8c1e-3b5d-4e7f-9a2c-6d8b1f3e5a7c",
"expectedAudioRevision": "server-revision",
"mix": { "music": { "gain": 60, "muted": false }, "video": { "gain": 85, "muted": false } }
}'
```

**TypeScript SDK**

```ts
const status = await client.recast.get(recastId)
const revision = status.audio?.revision
if (status.capabilities?.audioLayers === 1 && revision) {
const operation = {
expectedAudioRevision: revision,
mix: { music: { gain: 60, muted: false }, video: { gain: 85, muted: false } },
}
const quote = await client.recast.estimateRescore(recastId, operation)
if (!quote.noOp) {
await client.recast.rescore(recastId, { ...operation, requestId: crypto.randomUUID() })
}
}
```

## スクリプトを映画としてインポートする
映画を JSON ドキュメントとして書けば、元の動画なしで Recast できます。多くの場合、このドキュメントは言語モデルの助けを借りて書きます。3 つのルートは、どれも無料です。

### 作成ガイドを読む
`GET /v1/video-analysis/authoring-skill` は、ガイドを Markdown で返します。ドキュメントのフィールド、使える値、制限、オーディオのルール、検証済みの作例が含まれます。このガイドを、スクリプトを書くモデルに渡してください。

### スクリプトが有効になるまで検証する
`{ "script": { … } }` を付けて `POST /v1/video-analysis/import/validate` を呼び出すと、`{ valid, errors, warnings }` が返されます。各エラーには `path` と `message` があり、多くの場合は、修正のループ向けに書かれた `hint` もあります。`valid` が `true` になるまで、各パスを修正して検証を繰り返します。

### インポートする
`{ "script": { … }, "rightsAttested": true }` を付けて `POST /v1/video-analysis/import` を呼び出すと、スクリプトが完了済みの分析として保存され、`{ jobId, created, warnings, json }` が返されます。`json` は、サーバーが導き出すフィールドを加えた、あなたのドキュメントです。これを正式なドキュメントとして保管してください。同じスクリプトをもう一度インポートすると、同じ `jobId` が `created: false` とともに返されます。

### Recast する
その `jobId` を `analysisJobId` に指定し、`fidelity: "faithful"` と `rightsAttested: true` を付けて、実行を作成します。

`rightsAttested: true` は必須です。作成した Recast は、ブランド名も含めて書かれたとおりにレンダリングされるため、この値で、スクリプトがあなた自身の作品であることを確認します。この値がないと、インポートは `403 rights_attestation_required` を返します。

ドキュメントは、次の部分でできています。

| 部分 | 内容 |
| --- | --- |
| `meta` | `durationSec`、`width`、`height`、`aspectRatio`（`16:9` または `9:16` で、幅と高さに合わせます）、そして必須の `title` です。`title` は、プロジェクトの名前になります。 |
| `look` | 任意。映画全体のルックです。 |
| `slots` | キャストと、舞台となる場所です。それぞれに `role`（`person`、`object`、`background` のいずれか）があります。 |
| `scenes` | シーンです。0 から隙間なく番号を付け、それぞれ 8 秒以下にします。全体の長さは、4 秒からプラットフォームの実行上限までです。 |

実行上限を超えるドキュメントは拒否されます。途中で切り詰められることはありません。`sceneNumber`、`slotRefs`、`visualResolved` は書かないでください。これらはサーバーが導き出すもので、あなたが書いた値は無視されます。エディターで **JSON をコピー**を使ってコピーした分析が、そのままインポートできるのも、このためです。

**curl**

```bash
curl https://app.nodaro.ai/v1/video-analysis/authoring-skill \
  -H "Authorization: Bearer $NODARO_API_KEY" > recast-authoring.md

curl -X POST https://app.nodaro.ai/v1/video-analysis/import \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{ \"script\": $(cat script.json), \"rightsAttested\": true }"
```

**TypeScript SDK**

```ts
const guide = await client.recast.authoringSkill()
const check = await client.recast.validateScript(script)
if (check.valid) {
const { jobId } = await client.recast.importScript(script, { rightsAttested: true })
}
```

**CLI**

```bash
nodaro recast skill > recast-authoring.md
nodaro recast validate --file script.json
nodaro recast import --file script.json --rights-attested --json
```

## MCP から使う
AI アシスタントは、`get_recast_authoring_skill`、`validate_recast_script`、`import_recast_script`、`start_recast`、`get_recast_status`、`resolve_recast_gate` を使って、同じ流れを実行します。`start_recast` は最初に料金を示し、確認のためにもう一度呼び出されたときにだけ、クレジットを使います。[MCP での Recast](https://nodaro.ai/docs/mcp/recast) を参照してください。

## エラー
| ステータス | コード | 意味 |
| --- | --- | --- |
| `400` | `workflow_id_required` | `workflowId` なしで `POST /v1/recast` が送信されました。 |
| `400` | `validation_error`, `duplicate_section`, `unknown_section`, `all_audio_silent` | リクエストまたはオーディオの操作が不正です。 |
| `402` | `insufficient_credits` | アカウントのクレジットが、プランまたはオーディオの変更の料金に足りません。 |
| `403` | `rights_attestation_required` | スクリプトのインポートに `rightsAttested: true` がありませんでした。 |
| `404` | `workflow_not_found` | ワークフローが存在しないか、あなたのものではありません。 |
| `404` | `not_found` | 実行が存在しないか、インスタンスがセルフホスティング環境です。 |
| `409` | `audio_layers_unavailable`, `audio_layer_unavailable`, `audio_preview_unavailable` | テイクにリビジョン管理されたオーディオがないか、指定したレーンが存在しないか、そのレーンに使えるプレビューがありません。 |
| `409` | `rescore_sections_unavailable`, `legacy_mix_mismatch` | このテイクでは音楽の区間を差し替えられないか、`mix` なしの差し替えが現在のレベルと一致しません。 |
| `409` | `stale_audio_revision`, `rescore_in_progress`, `idempotency_conflict` | オーディオが変更されたか、別の変更が実行中か、`requestId` が別のリクエストに再利用されました。ステータスを読み取ってから、再試行してください。 |

## Frequently asked questions

### Recast とは何ですか？

Recast は、分析済みの動画を、あなた自身のキャストでシーンごとに再生成します。元になるのは、Nodaro が分析した実際の動画か、あなたが JSON で書いてインポートした脚本です。recast.nodaro.ai も、このエンジンで動いています。

### 支払う前に、Recast の料金を知るにはどうすればよいですか？

実行の作成に使うのと同じ設定で、POST /v1/recast/estimate を呼び出します。クレジットの合計と内訳が返され、この呼び出しは無料です。その後、POST /v1/recast でプランを購入します。

### 元の動画がなくても、映画を作れますか？

はい。映画を JSON のスクリプトとして書き、無料で検証してから、インポートします。インポートで作成される分析を、fidelity を faithful にして Recast するので、すべてのシーンが書かれたとおりにレンダリングされます。

### インタラクティブな実行が、シートのゲートで止まらないのはなぜですか？

ゲートが開くのは、作成のリクエストの clientCapabilities で、クライアントがそのゲートに回答できると宣言した場合だけです。宣言されていない種類のゲートは、自動で決定されます。

### Recast は、セルフホスティング環境で使えますか？

いいえ。Recast のルートは Nodaro Cloud でのみ動作し、セルフホスティング環境では 404 を返します。
