# Recast

> TypeScript から Recast の実行を見積もり、購入し、進行を確認します。ゲートに回答し、作成したスクリプトをインポートし、完成した実行の音楽ミックスを変更します。

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

**`client.recast`** は Recast を実行します。分析済みの元の動画を、自分自身のキャストで再生成し、自分で書いたスクリプト、つまり「movie as JSON」をレンダリングすることもできます。実行を見積もって購入し、進行を確認しながら、キャスト、シート、静止画、音楽の各ゲートに回答します。これらのメソッドは、[Recast の REST API](https://nodaro.ai/docs/developers/api/recast) を呼び出します。スクリプトの書き方については、[MCP での Recast](https://nodaro.ai/docs/mcp/recast) を参照してください。

Recast は Nodaro Cloud で動作します。セルフホスティング環境では、これらのメソッドは `NotFoundError` をスローします。

## メソッド
| メソッド | 内容 |
| --- | --- |
| [`authoringSkill()`](#authoringskill) | スクリプトの作成ガイドを読み取ります |
| [`validateScript(script)`](#validatescriptscript) | 作成したスクリプトを検証します |
| [`importScript(script, opts)`](#importscriptscript-opts) | 作成したスクリプトを、分析として保存します |
| [`estimate(input)`](#estimateinput) | 実行を無料で見積もります |
| [`create(input)`](#createinput) | プランを購入して、実行を作成します |
| [`get(recastId)`](#getrecastid) | 実行のステータスと保留中のゲートを読み取ります |
| [`start(recastId, opts?)`](#startrecastid-opts) | 計画済みの実行のレンダリングを開始します |
| [`resolveGate(recastId, input)`](#resolvegaterecastid-input) | 保留中のゲートに回答します |
| [`estimateRescore(recastId, input)`](#estimaterescorerecastid-input) | 新しい音楽ミックスまたは音楽トラックを見積もります |
| [`rescore(recastId, input)`](#rescorerecastid-input) | 見積もったオーディオの変更を適用します |

## 作成したスクリプト
### authoringSkill()
作成ガイドを、Markdown として返します（`GET /v1/video-analysis/authoring-skill`）。スクリプトドキュメント、その語彙と制限、オーディオのルール、検証済みの作例を扱います。無料です。

```ts
authoringSkill(): Promise<string>
```

```ts
const guide = await client.recast.authoringSkill()
```

このガイドを、スクリプトを書く言語モデルに渡してください。

### validateScript(script)
作成したスクリプトを検証し、`{ valid, errors, warnings }` を返します（`POST /v1/video-analysis/import/validate`）。無料で、何も保存しません。

```ts
validateScript(script: Record<string, unknown>): Promise<{
valid: boolean
errors: Array<{ path: string; message: string; hint?: string }>
warnings: string[]
}>
```

<TypeTable
type={{
script: { type: 'Record<string, unknown>', required: true, description: "スクリプトドキュメントです。" },
}}
/>

```ts
let check = await client.recast.validateScript(script)
while (!check.valid) {
script = await fixWithModel(script, check.errors) // each error has a path, a message and usually a hint
check = await client.recast.validateScript(script)
}
```

各エラーは、問題のある `path` を示し、多くの場合、言語モデル向けに書かれた `hint` も付きます。`valid` が `true` になるまで、修正と検証を繰り返します。

### importScript(script, opts)
有効なスクリプトを、完了済みの分析ジョブとして保存します（`POST /v1/video-analysis/import`）。その `jobId` を、実行の `analysisJobId` として使います。無料です。

```ts
importScript(script: Record<string, unknown>, opts: { rightsAttested: true }): Promise<{
jobId: string
created: boolean
warnings: string[]
json: Record<string, unknown>
}>
```

<TypeTable
type={{
script: { type: 'Record<string, unknown>', required: true, description: "有効なスクリプトドキュメントです。" },
rightsAttested: { type: 'true', required: true, description: "その作品を自分が所有していることを確認します。これがないと、インポートは ForbiddenError で失敗します。" },
}}
/>

```ts
const { jobId: analysisJobId, json } = await client.recast.importScript(script, { rightsAttested: true })
```

作成したスクリプトによる実行は、書かれたとおりにレンダリングされるため、自分が権利を持つ作品だけをインポートしてください。`json` は、サーバーが追加したフィールドを含む自分のドキュメントです。自分が渡した入力より、こちらを優先してください。同じスクリプトをもう一度インポートすると、`created: false` が返ります。

## 実行
### estimate(input)
実行の料金を、クレジットで見積もります（`POST /v1/recast/estimate`）。無料です。ボディは `create()` と同じですが、`workflowId`、`rightsAttested`、`clientCapabilities` は含みません。

```ts
estimate(input: EstimateRecastInput): Promise<{ totalCredits?: number; breakdown?: Record<string, number> }>
```

<TypeTable
type={{
analysisJobId: { type: 'string', required: true, description: "Recast する動画分析、またはインポートしたスクリプトです。" },
fidelity: { type: 'string', description: "実行が元の分析にどれだけ忠実に従うかです。作成したスクリプトでは faithful を使います。" },
resolution: { type: 'string', description: "出力の解像度です。" },
segmentSec: { type: 'number', description: "セグメントの長さの設定です。" },
renderMethod: { type: 'string', description: "レンダリングの方法です。" },
interactive: { type: 'boolean', description: "選択のためにゲートで停止します。" },
provider: { type: 'string', description: "動画モデルです。" },
}}
/>

```ts
const quote = await client.recast.estimate({ analysisJobId, fidelity: "faithful" })
console.log(quote.totalCredits, quote.breakdown)
```

### create(input)
実行を作成し、**プランを購入します**（`POST /v1/recast`）。先に `estimate()` で見積もってください。

```ts
create(input: CreateRecastInput): Promise<{ recastId: string }>
```

<TypeTable
type={{
workflowId: { type: 'string', required: true, description: "自分が所有する既存のワークフローです。実行はこれに紐付きます。" },
analysisJobId: { type: 'string', required: true, description: "Recast する動画分析、またはインポートしたスクリプトです。" },
rightsAttested: { type: 'boolean', description: "ソースの権利を持っていることを確認します。" },
clientCapabilities: { type: 'string[]', description: "自分のクライアントが回答できるゲートの種類で、たとえば sheet-gate です。宣言しなかったゲートは、自動で決定されます。" },
'...': { type: 'EstimateRecastInput', description: "estimate() のフィールドです。" },
}}
/>

```ts
const { recastId } = await client.recast.create({
workflowId,
analysisJobId,
fidelity: "faithful",
rightsAttested: true,
interactive: true,
clientCapabilities: ["sheet-gate"],
})
```

`workflowId` がないと、呼び出しは `400 workflow_id_required` で失敗します。存在しないワークフローや、ほかのユーザーのワークフローは 404 で失敗します。

### get(recastId)
実行を読み取ります（`GET /v1/recast/:id`）。進行状況を確認するには、ポーリングします。インタラクティブな実行では、`interactive.next` が、保留中のステップまたはゲートを示します。

```ts
get(recastId: string): Promise<RecastRunSnapshot>
```

<TypeTable
type={{
recastId: { type: 'string', required: true, description: "実行の ID です。" },
}}
/>

```ts
const run = await client.recast.get(recastId)
console.log(run.status, run.interactive)
```

スナップショットには `status`、`interactive`、`capabilities` があり、オーディオのレーンが分かれている完成した実行には `audio` もあります。[音楽ミックスを変更する](#change-the-music-mix)を参照してください。

### start(recastId, opts?)
`planned` 状態の実行のレンダリングを開始します（`POST /v1/recast/:id/start`）。プランの見積もりにすでに含まれており、繰り返し呼び出しても何も変わりません。

```ts
start(recastId: string, opts?: { segmentSec?: number; provider?: string }): Promise<{ gvpJobId?: string }>
```

<TypeTable
type={{
recastId: { type: 'string', required: true, description: "実行の ID です。" },
segmentSec: { type: 'number', description: "セグメントの長さの設定です。" },
provider: { type: 'string', description: "動画モデルです。" },
}}
/>

```ts
const { gvpJobId } = await client.recast.start(recastId)
```

### resolveGate(recastId, input)
インタラクティブな実行の、保留中のゲートに回答します（`POST /v1/recast/:id/select`）。それ以外のステップはすべてプラットフォームが自分で進めるので、クライアントは `get()` をポーリングして、ゲートに回答するだけです。回答自体は無料です。

```ts
resolveGate(recastId: string, input: ResolveRecastGateInput): Promise<Record<string, unknown>>
```

<TypeTable
type={{
recastId: { type: 'string', required: true, description: "実行の ID です。" },
gate: { type: '"cast" | "sheet" | "anchors" | "music"', description: "回答するゲートです。" },
picks: { type: 'unknown', description: "cast または sheet のゲートでの選択です。" },
segment: { type: 'number', description: "そのゲートが属するセグメントです。" },
anchorPicks: { type: '{ start?: number; end?: number }', description: "anchors のゲートでの、静止画の選択です。" },
section: { type: 'number', description: "そのゲートが属する音楽の区間です。" },
musicPick: { type: 'number | string', description: "music のゲートでの選択です。" },
finishAuto: { type: 'boolean', description: "このゲートと残りのすべてのゲートを、自動評価に任せます。" },
}}
/>

```ts
await client.recast.resolveGate(recastId, { gate: "music", musicPick: 1 })
await client.recast.resolveGate(recastId, { finishAuto: true })
```

ゲートが開くのは、`create()` で `clientCapabilities` に宣言した種類だけです。それ以外は、プラットフォームが自動で決定します。

## 音楽ミックスを変更する
完成した実行は、音楽と動画自体の音声を、別々のレーンとして保持できます。`get()` が `capabilities.audioLayers` に `1` を返す場合、`audio` マニフェストがそれらを表します。

- **`revision`** は、現在のオーディオを識別します。どの変更にも必要です。
- **`present`** は、実行が持つレーン（`music` と `video`）を一覧にします。
- **`layers`** は、存在するレーンのプレビューファイルを保持します。
- **`bakedEffectiveGain`** は、現在のダウンロードの各レーンのレベルを示します。
- **`pendingRescore`** は、まだ進行中の変更を示します。ページを再読み込みしても消えません。

リビジョンを勝手に作らず、プレビューがないことからミックスを推測しないでください。

### estimateRescore(recastId, input)
オーディオの変更を見積もります。新しいミックス、新しい音楽トラック、またはその両方です（`POST /v1/recast/:id/estimate-rescore`）。無料で、`{ credits, audioRevision, noOp }` を返します。

```ts
estimateRescore(recastId: string, input: EstimateRecastRescoreInput): Promise<{ credits: number; audioRevision: string; noOp: boolean }>
```

<TypeTable
type={{
recastId: { type: 'string', required: true, description: "実行の ID です。" },
expectedAudioRevision: { type: 'string', required: true, description: "自分が読み取った audio.revision です。" },
mix: { type: '{ music?: { gain, muted }, video?: { gain, muted } }', description: "希望する完全なミックスです。レーンごとの gain とミュートの切り替えです。" },
audioUrl: { type: 'string', description: "音楽を差し替える、新しい音楽トラックです。" },
sections: { type: 'Array<{ index: number; brief: string }>', description: "作り直す音楽の区間で、それぞれに短い brief を付けます。audioUrl か sections のどちらかを使います。" },
}}
/>

### rescore(recastId, input)
オーディオの変更を適用します（`POST /v1/recast/:id/rescore`）。見積もったときと同じ操作に、新しい `requestId`（UUID）を加えて送信します。`requestId` を再利用するのは、ネットワーク障害の後に同じリクエストを再試行するときだけにしてください。

```ts
rescore(recastId: string, input: EstimateRecastRescoreInput & { requestId: string }): Promise<
| { recastId: string; jobId: string }
| { recastId: string; noOp: true; audioRevision: string }
>
```

<TypeTable
type={{
recastId: { type: 'string', required: true, description: "実行の ID です。" },
requestId: { type: 'string', required: true, description: "この変更用の、新しい UUID です。" },
'...': { type: 'EstimateRecastRescoreInput', description: "estimateRescore に送ったのと同じ操作です。" },
}}
/>

```ts
const { total: available } = await client.credits.balance()
const run = await client.recast.get(recastId)

if (run.capabilities?.audioLayers === 1 && run.audio) {
const operation = {
expectedAudioRevision: run.audio.revision,
mix: {
music: { gain: 55, muted: false },
video: { gain: 100, muted: false },
},
}
const quote = await client.recast.estimateRescore(recastId, operation)
if (!quote.noOp && available >= quote.credits) {
await client.recast.rescore(recastId, { ...operation, requestId: crypto.randomUUID() })
}
}
```

変更はジョブとして実行されます。`audio.pendingRescore` が消えて `audio.revision` が変わるまで、`get(recastId)` をポーリングしてください。何も変わらない変更は、ジョブを作らずに `noOp: true` を返します。

- 1 回の操作には、ミックス、1 つの音楽の差し替え（`audioUrl` または `sections`）、またはミックス付きの差し替えのいずれかを指定します。
- 差し替えを行う場合は、希望する完全なミックスを送ってください。省略できるのは、正確に以前のデフォルトのレベルと一致する場合だけで、それ以外は `409 legacy_mix_mismatch` で失敗します。
- 古くなった `expectedAudioRevision` は `409 stale_audio_revision` で失敗し、実行中にさらに変更を送ると `409 rescore_in_progress` で失敗します。実行をもう一度読み取ってから、もう一度見積もってください。
- 無効な操作は、`validation_error`、`unknown_section`、`duplicate_section`、`all_audio_silent` などの 400 で失敗します。
- 見積もりがクレジットを確保することはなく、残高が足りない場合でも料金を返します。

## Frequently asked questions

### Recast は何をしますか？

Recast は、分析済みの元の動画を、自分自身のキャストで再生成します。Nodaro は分析からその実行を計画し、セグメントごとにレンダリングし、その途中でキャスト、シート、静止画、音楽を選べるようにします。

### Recast の実行を作成すると、クレジットがかかりますか？

はい。create はプランを購入するので、まず無料の client.recast.estimate で見積もってください。resolveGate でゲートに回答するのは無料です。

### 実際の動画の代わりに、自分のスクリプトを Recast できますか？

はい。validateScript で、有効になるまでスクリプトを検証し、rightsAttested を true にして importScript します。インポートは jobId を返すので、これを新しい実行の analysisJobId として使います。

### 完成した Recast の実行の音楽レベルを変更するには、どうすればよいですか？

get で実行を読み取り、新しいミックスを estimateRescore で見積もり、同じ操作を新しい requestId とともに rescore に送信します。get をポーリングして、オーディオのリビジョンが変わるまで待ちます。
