# ה-API של Recast

> הריצו Recast על סרטון שנותח עם ליהוק משלכם, ב-REST API: אומדן וקנייה של הרצה, מענה לנקודות בחירה, מיקס חדש לאודיו או ייבוא תסריט שכתבתם.

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

**ה-API של Recast** יוצר מחדש סרטון שנותח, עם הליהוק שלכם. מקבלים אומדן מחיר להרצה, קונים את התוכנית שלה, מרנדרים אותה סצנה אחר סצנה, ובהרצה אינטראקטיבית בוחרים בדרך את הליהוק, את תמונות הסטילס של הסצנות ואת המוזיקה. אפשר גם לכתוב סרט כתסריט JSON ולייבא אותו, כך שהרצת Recast לא צריכה סרטון מקור בכלל. Recast הוא המנוע שמאחורי [recast.nodaro.ai](https://recast.nodaro.ai).

Recast רץ רק ב-Nodaro Cloud; בהתקנות באירוח עצמי הנתיבים מחזירים `404`. בהתקנה באירוח עצמי, פרקו סרטון לסצנות עם רכיב [ניתוח וידאו](https://nodaro.ai/docs/nodes/video/video-analysis), וצרו מחדש את הסצנות שלו עם רכיב [יצירת וידאו](https://nodaro.ai/docs/nodes/video/generate-video) בתוך תהליך. הנתיבים מקבלים אסימון Bearer. ראו את דף ה-API [אימות](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`, המזהה של תהליך שבבעלותכם שההרצה משויכת אליו. בלי השדה, הנתיב מחזיר `400 workflow_id_required`; עם מזהה לא מוכר או של מישהו אחר, `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: 'ביצירה בלבד. נדרש, עם הערך true, לרינדור faithful של תסריט שיובא.' },
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`. ראו בדף ה-API [ערכות](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` | פורטרט אחד לכל אלמנט בליהוק. |
| `sheet` | רק לאדם, כשההרצה מציעה זאת: אחד מתוך 3 גיליונות זהות שבכולם מופיעות הפנים שנבחרו, כך שבוחרים את הגוף ואת הבגדים. |
| `anchors` | תמונות הסטילס של אחד מחלקי הסצנות. |
| `music` | המוזיקה לקטע בסרט. |

נקודת בחירה נפתחת רק בסוגים שבקשת היצירה שלכם הצהירה עליהם ב-`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 היחידה של הסרטון שתקבלו.

### אומדן מחיר, ואז החלה
האומדן וההחלה מקבלים את אותה פעולה. שלחו לכל היותר החלפת מוזיקה אחת, או `audioUrl` או רשומה אחת או יותר ב-`sections`, כל אחת עם `brief`, ובנוסף את ה-`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 }` כששום דבר לא משתנה. על פעולה שלא משנה כלום לא משוריינים קרדיטים, והיא לא יוצרת משימה.
3. **מעקב.** בדקו את הסטטוס מדי כמה שניות. השדה `audio.pendingRescore` מציג את הפעולה, נשאר גם אחרי טעינה מחדש, ונעלם כשהגרסה החדשה מתפרסמת או כשהפעולה נכשלת. בדקו שוב את הסטטוס לפני הפעולה הבאה.

ערכי `gain` הם אחוזים מ-0 עד 200; רצועה מושתקת נחשבת ל-0. ציינו רק רצועות שמופיעות ב-`present`, או מוזיקה שהבקשה הזו מוסיפה. לטייק במצב `replace` אין רצועת `video`, ואסור שהתוצאה תשאיר את כל הרצועות שקטות. השתמשו שוב ב-`requestId` רק כדי לנסות שוב את אותה בקשה בדיוק.

עם החלפת מוזיקה, שלחו את ה-`mix` המלא. אפשר להשמיט אותו רק כשהתוצאה תואמת לרמות הקבועות הרגילות: מוזיקה 35 ווידאו 100 במצב `bed`, או מוזיקה 100 במצב `replace`. בכל רמה נוכחית אחרת, הבקשה נדחית עם `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 בלי סרטון מקור. שלושת הנתיבים חינמיים.

### קריאת המדריך לכתיבה
הנתיב `GET /v1/video-analysis/authoring-skill` מחזיר את המדריך כ-Markdown: השדות של המסמך, הערכים המותרים, המגבלות, כללי האודיו ודוגמה שעברה אימות. תנו אותו למודל שכותב את התסריט שלכם.

### בדיקה עד שהתסריט תקין
הנתיב `POST /v1/video-analysis/import/validate` עם `{ "script": { … } }` מחזיר `{ valid, errors, warnings }`. לכל שגיאה יש `path` ו-`message`, ובדרך כלל גם `hint` שנכתב בשביל לולאת תיקונים. תקנו את השגיאה בכל `path` ובדקו שוב, עד שהשדה `valid` הוא `true`.

### ייבוא התסריט
הנתיב `POST /v1/video-analysis/import` עם `{ "script": { … }, "rightsAttested": true }` שומר את התסריט כניתוח שהושלם ומחזיר `{ 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`, שהוא חובה ונותן לפרויקט את שמו. |
| `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` מציג קודם את המחיר, ומוציא קרדיטים רק כשקוראים לו שוב כדי לאשר. ראו את הדף [Recast דרך MCP](https://nodaro.ai/docs/mcp/recast).

## שגיאות
| סטטוס | קוד | משמעות |
| --- | --- | --- |
| `400` | `workflow_id_required` | הבקשה ל-`POST /v1/recast` נשלחה בלי `workflowId`. |
| `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` שימש שוב לבקשה אחרת. בדקו את הסטטוס ונסו שוב. |

## שאלות נפוצות

### מה זו הרצת Recast?

הרצת Recast יוצרת מחדש סרטון שנותח, סצנה אחר סצנה, עם הליהוק שלכם. המקור יכול להיות סרטון אמיתי ש-Nodaro ניתח, או תסריט שכתבתם כ-JSON וייבאתם. Recast הוא המנוע שמאחורי recast.nodaro.ai.

### איך יודעים כמה תעלה הרצת Recast לפני שמשלמים?

קראו לנקודת הקצה POST /v1/recast/estimate עם אותן הגדרות שאיתן תיצרו את ההרצה. נקודת הקצה מחזירה את הסכום הכולל בקרדיטים ופירוט שלו, והקריאה לא עולה כלום. אחר כך הקריאה ל-POST /v1/recast קונה את התוכנית.

### האם אפשר ליצור סרט בלי סרטון מקור?

כן. כתבו את הסרט כתסריט JSON, בדקו אותו בחינם וייבאו אותו. הייבוא יוצר ניתוח, ועליו מריצים Recast עם הערך faithful בשדה fidelity, כך שכל סצנה מרונדרת בדיוק כפי שנכתבה.

### למה ההרצה האינטראקטיבית שלי אף פעם לא נעצרת בנקודת הבחירה של גיליון הזהות?

בהרצת Recast אינטראקטיבית, נקודת בחירה נפתחת רק כשבקשת היצירה הצהירה בשדה clientCapabilities שהלקוח שלכם יכול לענות עליה. בסוגים של נקודות בחירה שלא הוצהרו, ההכרעה אוטומטית.

### האם Recast עובד בהתקנה באירוח עצמי?

לא. נתיבי Recast רצים רק ב-Nodaro Cloud, ובהתקנות באירוח עצמי הם מחזירים 404.
