# הפקות Studio

> בניית הפקות Studio מתוכנית עם ה-SDK ל-TypeScript, עריכה בפעולות, יצירת תמונות סטילס וקליפים, בדיקת פריימים מתוכננים, שיתוף והעתקה.

Source: https://nodaro.ai/he/docs/developers/sdk/studio

**הפקת Studio** היא תהליך של Nodaro שבהגדרות שלו נמצאים השוטים של סרט. לכל שוט יש תמונת סטילס של הפריים, קליפ מונפש אופציונלי, והתוכנית, המראות, שיוכי הליהוק והקול שיצרו אותם. המשאב **`client.studio`** קורא וכותב הפקות, כך שסקריפט, עוזר AI ואפליקציית ה-Studio עובדים על אותה הפקה. המשאב **`client.shots`** שומר את רשומות השוטים המשותפות שמאחורי קישורי שיתוף. המתודות קוראות לנקודות הקצה שבדף ה-API [הפקות Studio](https://nodaro.ai/docs/developers/api/studio-productions). המדריך לכתיבת הפקות נמצא בדף [הפקות Studio דרך MCP](https://nodaro.ai/docs/mcp/studio-productions).

הפקות Studio הן יכולת של Nodaro Cloud. בפריסה שלא מספקת את הנתיבים שלהן, כל מתודה זורקת `NotFoundError`. כדי לבדוק פעם אחת, קראו למתודה `client.studio.productions.list()`: פריסה שתומכת בהפקות מחזירה עמוד ריק, ופריסה שלא תומכת בהן זורקת `NotFoundError`.

## שתי קבוצות של מתודות
למשאב `client.studio` יש שתי שכבות. שתיהן עובדות על אותן הפקות.

| שכבה | למה היא מתאימה | מה חוזר |
| --- | --- | --- |
| `client.studio.productions.*` | מסמך ההפקה: יצירה מתוכנית, עריכה בפעולות, יצירת תמונות סטילס וקליפים, הוספת קול ומוזיקה, שיתוף והעתקה | המטען עצמו |
| `client.studio.*` | פריימים מתוכננים: יכולות, יצירה ובדיקה של פריימים, חבילות, שמירות של העורך ושיתוף בקישור עם בדיקות גרסה | מעטפת `{ data }` של ה-API |

**למעטפות יש טיפוסים, ולמסמך ההפקה אין.** הפקה, שוט ופעולה הם JSON פתוח, `Record<string, unknown>`. לכל שדה שהקוד שלכם מקבל לפיו החלטות יש טיפוס: `version`, `rebased`, `receipts`, `warnings`, ה-`credits` של אומדן מחיר וה-`jobIds` של הרצה. רשימת הפעולות מגיעה מהשרת: קבלו אותה מ-`skill()`.

## המתודות של client.studio.productions
| מתודה | מה היא עושה |
| --- | --- |
| [`skill()`](#productionsskill) | קריאת המדריך לכתיבה, הקטלוג, סכמת התוכנית ומדריך הפעולות |
| [`validatePlan(plan)`](#productionsvalidateplanplan) | בדיקת תוכנית, בחינם |
| [`list(opts?)`](#productionslistopts) | רשימת ההפקות שלכם |
| [`get(productionId, opts?)`](#productionsgetproductionid-opts) | קריאת הפקה |
| [`exportPlan(productionId, opts?)`](#productionsexportplanproductionid-opts) | תכנון ותמחור של שלבי הייצוא |
| [`create(input?)`](#productionscreateinput) | יצירת הפקה, מתוכנית או בלעדיה |
| [`ops(productionId, input)`](#productionsopsproductionid-input) | החלת אצווה של פעולות, או תצוגה מקדימה שלה |
| [`reconcile(productionId)`](#productionsreconcileproductionid) | הכנסת יצירות שהסתיימו להפקה |
| [`importPlan(productionId, plan, opts?)`](#productionsimportplanproductionid-plan-opts) | הוספת הסצנות של תוכנית להפקה |
| [`describe(productionId, input)`](#productionsdescribeproductionid-input) | הפיכת בריף לסצנות |
| [`generate()`, `generateStill()`, `generateClip()`](#generate-stills-and-clips) | יצירת תמונת סטילס לשוט, או הנפשה שלו |
| [`frame(productionId, input)`](#productionsframeproductionid-input) | שליפת פריים מהקליפ של שוט |
| [`voice(productionId, input)`](#productionsvoiceproductionid-input) | הקראת השורה של שוט |
| [`revoice(productionId, input)`](#productionsrevoiceproductionid-input) | החלפת הקולות בקליפ של שוט |
| [`music(productionId, input)`](#productionsmusicproductionid-input) | יצירת הפסקול של הסרט |
| [`share()`, `unshare()`, `clone()`](#share-and-copy) | פתיחה או סגירה של קישור השיתוף, או העתקה של ההפקה |

## client.studio.productions
### productions.skill()‎
מחזירה את המדריך לכתיבה, את הקטלוג המלא, את ה-JSON Schema של התוכנית ואת מדריך הפעולות, כפי שהם נוצרים מהגרסה שהשרת מריץ. המתודה לא עולה כלום.

```ts
skill(): Promise<{ skill: string; catalog: string; schema: Record<string, unknown>; operating: string; generatedFrom: object }>
```

```ts
const { skill, schema, operating } = await client.studio.productions.skill()
```

השדה `operating` מפרט את הפעולות שהמתודה `ops()` מקבלת. קבלו אותו בזמן ריצה, במקום לקבע את רשימת הפעולות בקוד.

### productions.validatePlan(plan)‎
בודקת תוכנית לפני שהיא הופכת להפקה. המתודה לא עולה כלום, לא שומרת כלום ומתאימה את השמות שבליהוק לספרייה שלכם. תקנו את השגיאות שב-`errors` ובדקו שוב, עד שהשדה `valid` הוא `true`, ואז קראו ל-`create({ plan })`.

```ts
validatePlan(plan: Record<string, unknown>): Promise<{
valid: boolean
errors: Array<{ path: string; message: string; hint?: string }>
warnings: Array<{ path: string; message: string; hint?: string }>
summary?: { name?: string; scenes: number; shots: number; cast: number; bound: number }
}>
```

<TypeTable
type={{
plan: { type: 'Record<string, unknown>', required: true, description: "תוכנית ההפקה, במבנה של skill().schema." },
}}
/>

```ts
const check = await client.studio.productions.validatePlan(plan)
if (!check.valid) console.log(check.errors)
```

השדה `summary.bound` סופר את האלמנטים בליהוק שהותאמו לדמות בספרייה שלכם.

### productions.list(opts?)‎
מחזירה את ההפקות שלכם, מהחדש לישן.

```ts
list(opts?: { limit?: number; cursor?: string; includeArchived?: boolean }): Promise<{ data: StudioProduction[]; nextCursor?: string }>
```

<TypeTable
type={{
limit: { type: 'number', description: "גודל העמוד." },
cursor: { type: 'string', description: "הערך nextCursor מהעמוד הקודם." },
includeArchived: { type: 'boolean', default: 'false', description: "לכלול גם את ההפקות שבארכיון, שלוח הבקרה מסתיר." },
}}
/>

```ts
const { data: productions } = await client.studio.productions.list({ limit: 20 })
```

### productions.get(productionId, opts?)‎
מחזירה הפקה. המתודה רק מחזירה נתונים, והיא אף פעם לא מכניסה להפקה משימה שהסתיימה, ולכן כשאתם מחכים למשימה, קראו קודם ל-`reconcile()`.

```ts
get(productionId: string, opts?: { detail?: "summary" | "full"; shotId?: string }): Promise<StudioProduction>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
detail: { type: '"summary" | "full"', default: '"summary"', description: "הערך summary מחזיר כמויות ואת הכתובות הפעילות. הערך full מוסיף כל תוצאה, עם ההקשר שיצר אותה." },
shotId: { type: 'string', description: "קריאה של שוט אחד בלבד: הקריאה הזולה אחרי יצירה." },
}}
/>

```ts
const production = await client.studio.productions.get(productionId, { detail: "full" })
```

### productions.exportPlan(productionId, opts?)‎
מחזירה לפי הסדר את השלבים שמרכיבים את הסרט, עם המחירים שלהם. המתודה לא מריצה כלום ולא מוציאה קרדיטים: הריצו את השלבים בעצמכם, עם המתודות הרגילות להרצת רכיבים.

```ts
exportPlan(productionId: string, opts?: { upscale?: boolean }): Promise<{
canExport: boolean
steps: Array<{ id: string; label: string; node: string; creditModel: string; credits: number | null; params: Record<string, unknown> }>
resultStepId: string | null
estimate: number | null
unpriced: string[]
}>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
upscale: { type: 'boolean', default: 'false', description: "מוסיף לתוכנית גם גימור ב-4K. הגימור עולה יותר, ולכן ההחלטה תמיד שלכם." },
}}
/>

```ts
const plan = await client.studio.productions.exportPlan(productionId)
console.log(plan.canExport, plan.estimate)
for (const step of plan.steps) console.log(step.id, step.label, step.node, step.credits)
```

השדה `canExport` הוא `false` כשיש בהפקה פחות משני קליפים. השדה `estimate` הוא `null` כשלשלב כלשהו אין מחיר, כי סכום חלקי היה מציג עלות נמוכה מהעלות בפועל, והשדה `unpriced` מציין את המודלים של השלבים האלה. השדה `node` של כל שלב הוא סוג רכיב, כמו `merge-video-audio`, `combine-videos` או `video-upscale`.

### productions.create(input?)‎
יוצרת הפקה, ויכולה גם להכניס אליה תוכנית באותה קריאה.

```ts
create(input?: { name?: string; plan?: Record<string, unknown> }): Promise<{
production: StudioProduction
warnings?: Array<{ path: string; message: string; hint?: string }>
summary?: { shotsAdded: number; castEnrolled: number; castBound: number }
}>
```

<TypeTable
type={{
name: { type: 'string', description: "השם של ההפקה." },
plan: { type: 'Record<string, unknown>', description: "תוכנית שנבדקה, להכנסה להפקה." },
}}
/>

```ts
const { production, summary } = await client.studio.productions.create({ name: "Rome chase", plan })
```

### productions.ops(productionId, input)‎
מחילה על הפקה אצווה של **פעולות**. כל שינוי הוא פעולה, שמציינת את היעד שלה לפי מפתח יציב, כמו מזהה של שוט, slug של תפקיד או מזהה המשימה של תוצאה, ולעולם לא לפי מיקום.

```ts
ops(productionId: string, input: StudioOpsRequest): Promise<StudioOpsResponse>
ops(productionId: string, input: StudioOpsRequest & { dryRun: true }): Promise<StudioOpsDryRunResponse>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
ops: { type: 'unknown[]', required: true, description: "הפעולות, שמוחלות לפי הסדר. עד 100 פעולות לכל בקשה. רשימת הפעולות נמצאת ב-skill().operating." },
baseVersion: { type: 'number', description: "הגרסה שמולה הרכבתם את האצווה. כברירת מחדל הערך הוא למידע בלבד: האצווה מוחלת על הגרסה החדשה ביותר, והתשובה מציינת rebased." },
strict: { type: 'boolean', description: "דחייה במקום החלה מחדש על הגרסה העדכנית: קריאה עם baseVersion ישן יותר נכשלת אז עם 409 workflow_conflict." },
clientRequestId: { type: 'string', description: "האסימון שלכם לאצווה הזו, בין 8 ל-128 תווים, כדי שבניסיון חוזר האצווה לא תוחל פעמיים." },
dryRun: { type: 'true', description: "תצוגה מקדימה של מה שהאצווה הייתה עושה, בלי לכתוב כלום. כתבו את הליטרל true ישירות בקריאה עצמה." },
}}
/>

```ts
const result = await client.studio.productions.ops(productionId, {
ops: [/* operations from the operating guide */],
baseVersion: version,
clientRequestId: crypto.randomUUID(),
})
version = result.version // carry it forward as the next baseVersion
for (const r of result.receipts) console.log(r.summary)
```

- **הכול או כלום.** פעולה שגויה אחת גורמת לדחיית האצווה כולה עם [`StudioOpError`](https://nodaro.ai/docs/developers/sdk/errors#studio-batches), שה-`opIndex` שלה מציין את הפעולה, ושום דבר לא נכתב.
- **החלה מחדש על הגרסה העדכנית.** שני אנשים יכולים לערוך הפקה אחת באותו זמן. אצווה שהורכבה מול גרסה ישנה יותר עדיין מוחלת על הגרסה החדשה ביותר, והשדה `rebased` הוא `true`.
- **אישורי ביצוע.** ב-`receipts` יש שורה אחת בזמן עבר לכל פעולה, כמו "Deleted take 2 of Shot 1 (in the bin)" (טייק 2 של שוט 1 נמחק ועבר לפח). כשההשפעה של פעולה חורגת ממה שהיא מציינת, השדה `impact` שלה מפרט את ה-`keyframeIds` ואת ה-`shotIds` שצריך לרענן.
- **ניסוח בשפות אחרות.** אישור ביצוע יכול לכלול גם `detail`, והתשובה יכולה לכלול `warningDetails`, ושניהם מוגדרים מהטיפוס `StudioOpsText`, `{ template, vars }`. בשדות האלה נמצאות אותן שורות כנתונים, ללקוח שמנסח אותן בשפה של האדם שמולו. ב-`warningDetails` יש רשומה אחת לכל אזהרה, ו-`null` במקום שבו קיימת רק השורה באנגלית. בכל פעם שאין לכם ניסוח ל-`template` מסוים, הציגו את `summary` ואת `warnings`.
- **אמצו את התשובה.** החליפו את העותק שלכם ב-`production`, והמשיכו עם ה-`version` שחזר. אל תמזגו את התשובה לתוך העותק הישן שלכם.

**תצוגה מקדימה של אצווה.** עם `dryRun: true`, התשובה מתארת מה האצווה **הייתה** עושה, כדי שאדם יוכל לאשר קודם את העריכות של עוזר AI. בתשובה יש `dryRun`, `version`, `receipts` ו-`warnings`, ואין `production`. כל אישור ביצוע מוסיף `class`, הסיווג של הפעולה, שהוא `S` לפעולה בטוחה, `D` לפעולה שמוחקת, `P` לפעולה שמשנה את מי שיכול לגשת לעבודה ו-`$` לפעולה שמוציאה קרדיטים. הוא מוסיף גם `restorable`, שמופיע רק כשהפעולה העבירה משהו לפח. התייחסו אליו כ-`restorable ?? false`.

כתבו את `dryRun: true` כליטרל, בתוך האובייקט של הקריאה עצמה. כשהערך מועבר דרך משתנה, הטיפוס שלו מתרחב ל-`boolean`, והקריאה מקבלת את הטיפוסים של החלה, אף שבפועל היא עדיין מחזירה תצוגה מקדימה.

תצוגה מקדימה שולחת **שתי** בקשות: קודם אצווה ריקה, שמוודאת שהפריסה תומכת בתצוגות מקדימות, ואחר כך את האצווה שלכם. אחרת, פריסה שלא תומכת בתצוגות מקדימות הייתה מחילה את האצווה שלכם בלי אזהרה. שתי שגיאות אפשריות:

```ts

try {
const preview = await client.studio.productions.ops(productionId, { ops, baseVersion, dryRun: true })
for (const r of preview.receipts) console.log(r.class, r.summary, r.restorable ?? false)
} catch (err) {
if (err instanceof StudioPreviewUnavailable) {
// Nothing was sent. Say that no preview is available; do not apply the batch instead.
} else if (err instanceof StudioPreviewAppliedError) {
// The batch was applied. Adopt err.applied.production and err.applied.version.
// Do not send it again. When err.applied is undefined, read the production first.
} else {
throw err
}
}
```

### productions.reconcile(productionId)‎
מכניסה להפקה כל יצירה שהסתיימה מאז הבדיקה הקודמת שלכם, ומדווחת מה עדיין רץ. זו הקריאה היחידה שהופכת משימות שהסתיימו לתוצאות כשהאפליקציה לא פתוחה, והיא כותבת רק כשמשהו נכנס להפקה.

```ts
reconcile(productionId: string): Promise<{
landed: string[]
pending: string[]
failed: string[]
warnings: string[]
production: StudioProduction
version: number
}>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
}}
/>

```ts
const { landed, pending } = await client.studio.productions.reconcile(productionId)
```

השדה `landed` מפרט את המשימות שהמדיה שלהן כבר נמצאת בהפקה, השדה `pending` את המשימות שעדיין רצות, והשדה `failed` את המשימות שנכשלו או שבוטלו.

### productions.importPlan(productionId, plan, opts?)‎
מוסיפה את הסצנות של תוכנית להפקה קיימת.

```ts
importPlan(productionId: string, plan: Record<string, unknown>, opts?: { mode?: "append" }): Promise<{
production: StudioProduction
warnings?: Array<{ path: string; message: string; hint?: string }>
summary?: { shotsAdded: number; castEnrolled: number; castBound: number }
}>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
plan: { type: 'Record<string, unknown>', required: true, description: "התוכנית להוספה." },
mode: { type: '"append"', default: '"append"', description: "הוספת הסצנות אחרי הסצנות הקיימות." },
}}
/>

```ts
await client.studio.productions.importPlan(productionId, extraScenesPlan)
```

### productions.describe(productionId, input)‎
הופכת בריף לסצנות בעזרת הבמאי. המתודה מתחילה משימה וחוזרת מיד; הסצנות נכנסות להפקה דרך `reconcile()`. ההפקה חוזרת כשההרצה רשומה בה כטיוטה ממתינה.

```ts
describe(productionId: string, input: {
brief: string
llmModel: string
mode?: "append" | "replace"
label?: string
clientRequestId?: string
}): Promise<{ production: StudioProduction; jobId: string }>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
brief: { type: 'string', required: true, description: "על מה הסרט." },
llmModel: { type: 'string', required: true, description: "מודל השפה שכותב את הטיוטה של הסצנות." },
mode: { type: '"append" | "replace"', description: "הערך append מוסיף את הסצנות שבטיוטה. הערך replace כותב את הסרט מחדש." },
label: { type: 'string', description: "שם להרצה." },
clientRequestId: { type: 'string', description: "אסימון הניסיון החוזר שלכם." },
}}
/>

```ts
const { jobId } = await client.studio.productions.describe(productionId, {
brief: "A courier races across Rome in the rain to deliver a violin.",
llmModel,
})
```

### יצירת תמונות סטילס וקליפים
המתודה `generateStill()` יוצרת תמונת סטילס לשוט, המתודה `generateClip()` מנפישה אותו, והמתודה `generate()` עושה אחת מהשתיים לפי `kind`. הרצה שולחת את המשימות, רושמת בהפקה סימון המתנה וחוזרת: אף קריאה לא ממתינה דקות. השרת בונה את הבקשה מהתוכנית, מהמראות ומהייחוסים של השוט עצמו, ולכן סקריפט ולחיצה באפליקציה יוצרים את אותה מדיה.

```ts
generate(productionId: string, input: StudioGenerateRequest): Promise<StudioGenerateResult>
generateStill(productionId: string, shotId: string, opts?: StudioGenerateOptions): Promise<StudioGenerateResult>
generateClip(productionId: string, shotId: string, opts?: StudioGenerateOptions): Promise<StudioGenerateResult>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
shotId: { type: 'string', required: true, description: "השוט שיוצרים לו תמונת סטילס או שמנפישים אותו." },
kind: { type: '"still" | "clip"', description: "ב-generate()‎ בלבד: מה ליצור." },
count: { type: 'number', description: "בתמונות סטילס: כמה מועמדים. התוצאות מצטברות; הרצה חדשה אף פעם לא מחליפה תוצאות קודמות." },
mode: { type: '"start" | "start-end" | "references"', description: "בקליפים: אילו קלטים לשלוח. הערך start שולח רק את פריים הפתיחה, והערך start-end את שני הפריימים. כשמשמיטים אותו, הבחירה נקבעת לפי הקלטים השמורים של השוט." },
dryRun: { type: 'boolean', description: "החזרת אומדן מחיר, בלי לשלוח כלום." },
clientRequestId: { type: 'string', description: "אסימון הניסיון החוזר שלכם." },
overrides: { type: 'Record<string, unknown>', description: "שינויים להרצה הזו בלבד, כמו המודל, הפרומפט, יחס הגובה־רוחב או המזהים של ההכוונה הקולנועית. השוט עצמו לא משתנה." },
}}
/>

```ts

const quote = await client.studio.productions.generateStill(productionId, "shot-2", { count: 2, dryRun: true })
if (isStudioGenerateEstimate(quote)) console.log(quote.credits) // null means unpriced, not free

const run = await client.studio.productions.generateStill(productionId, "shot-2", {
count: 2,
clientRequestId: crypto.randomUUID(),
})
```

- **קודם אומדן מחיר.** הערך `dryRun: true` מתמחר את ההרצה ולא כותב כלום. צמצמו את הטיפוס של התשובה עם `isStudioGenerateEstimate()`.
- **ניסיון חוזר בטוח.** עם אותו `clientRequestId`, ניסיון חוזר מחזיר את המשימות שהקריאה הראשונה התחילה, עם הסימון `deduped: true`, ושום דבר לא נשלח ולא נגבים קרדיטים. לעולם אל תנסו שוב קריאה בתשלום בלעדיו. כל קריאה בתשלום בדף הזה מקבלת אותו, כולל `frame()` ו-`voice()`.
- **השיטה של הקליפ נבחרת בשבילכם.** בקליפ, נתיב הווידאו נקבע לפי הקלטים של השוט, וחוזר בשדה `lane`, שערכו `generate-video` או `text-to-video`.

### productions.frame(productionId, input)‎
שולפת פריים מהקליפ הפעיל של שוט, ומציבה אותו במקום שהפרמטר `target` מציין. המתודה ממתינה למשימה, שנמשכת כמה שניות, ומחזירה את ההפקה ששונתה ואת ה-`url` של התמונה.

```ts
frame(productionId: string, input: {
shotId: string
mode?: "first" | "last" | "timestamp"
timestamp?: number
target?: "new-shot" | "start-frame" | "end-frame" | "still"
clientRequestId?: string
}): Promise<StudioMediaResponse>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
shotId: { type: 'string', required: true, description: "השוט שהקליפ שלו ישמש." },
mode: { type: '"first" | "last" | "timestamp"', description: "איזה פריים לשלוף." },
timestamp: { type: 'number', description: "הזמן בשניות, כש-mode הוא timestamp." },
target: { type: '"new-shot" | "start-frame" | "end-frame" | "still"', default: '"new-shot"', description: "לאן הפריים נכנס: שוט חדש אחרי השוט הזה, פריים הפתיחה של השוט הזה, פריים הסיום שלו או תמונת סטילס נוספת שלו." },
clientRequestId: { type: 'string', description: "אסימון הניסיון החוזר שלכם." },
}}
/>

```ts
const { url } = await client.studio.productions.frame(productionId, { shotId: "shot-2", mode: "last" })
```

### productions.voice(productionId, input)‎
מקריאה את השורה של שוט ושומרת את ההקלטה בשוט. המתודה ממתינה למשימה.

```ts
voice(productionId: string, input: {
shotId: string
text: string
voiceId?: string
voiceType?: "premade" | "custom" | "library"
ttsProvider?: string
delivery?: Record<string, number>
clientRequestId?: string
}): Promise<StudioMediaResponse>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
shotId: { type: 'string', required: true, description: "השוט." },
text: { type: 'string', required: true, description: "השורה להקראה." },
voiceId: { type: 'string', description: "הקול." },
voiceType: { type: '"premade" | "custom" | "library"', description: "סוג הקול." },
ttsProvider: { type: 'string', description: "מודל הדיבור." },
delivery: { type: 'Record<string, number>', description: "הגדרות ההגשה, בגבולות שנתיב הדיבור מאפשר." },
clientRequestId: { type: 'string', description: "אסימון הניסיון החוזר שלכם." },
}}
/>

```ts
await client.studio.productions.voice(productionId, { shotId: "shot-3", text: "We're out of time." })
```

### productions.revoice(productionId, input)‎
מחליפה את הקולות בקליפ הפעיל של שוט. ההחלפה נמשכת כמה דקות, ולכן המתודה מחזירה `jobId`, והקליפ החדש נכנס להפקה דרך סימון ההמתנה שלו.

```ts
revoice(productionId: string, input: { shotId: string; plan: Record<string, unknown>; clientRequestId?: string }): Promise<{ production: StudioProduction; jobId: string }>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
shotId: { type: 'string', required: true, description: "השוט." },
plan: { type: 'Record<string, unknown>', required: true, description: "תוכנית החלפת הקולות, לפי סדר הדוברים, במבנה שנתיב החלפת הקולות מקבל." },
clientRequestId: { type: 'string', description: "אסימון הניסיון החוזר שלכם." },
}}
/>

```ts
const { jobId } = await client.studio.productions.revoice(productionId, {
shotId: "shot-3",
plan: recastPlan, // the speaker-ordered plan the voice recast route takes
})
```

### productions.music(productionId, input)‎
יוצרת את הפסקול של הסרט. הרצועה המוגמרת נכנסת להפקה דרך סימון ההמתנה שלה.

```ts
music(productionId: string, input: {
prompt: string
duration?: number
instrumental?: boolean
vocalGender?: string
model?: string
clientRequestId?: string
}): Promise<{ production: StudioProduction; jobId: string }>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
prompt: { type: 'string', required: true, description: "המוזיקה שאתם רוצים." },
duration: { type: 'number', description: "האורך בשניות." },
instrumental: { type: 'boolean', description: "מוזיקה בלי שירה." },
vocalGender: { type: 'string', description: "קול הזמר או הזמרת, במוזיקה עם שירה." },
model: { type: 'string', description: "מודל המוזיקה." },
clientRequestId: { type: 'string', description: "אסימון הניסיון החוזר שלכם." },
}}
/>

```ts
const { jobId } = await client.studio.productions.music(productionId, {
prompt: "Tense strings building to a chase",
instrumental: true,
})
```

### שיתוף והעתקה
המתודה `share()` פותחת את תצוגת השיתוף בקישור, והמתודה `unshare()` סוגרת אותה שוב. שיתוף הוא קריאה נפרדת ולא פעולה, כך שעריכה אף פעם לא משנה, כתופעת לוואי, את מי שיכול לראות את העבודה. המתודה `clone()` מעתיקה הפקה שבבעלותכם, או הפקה שאתם יכולים לראות, לפרויקט ה-Studio שלכם.

```ts
share(productionId: string): Promise<StudioProduction>
unshare(productionId: string): Promise<StudioProduction>
clone(productionId: string, input?: { name?: string }): Promise<StudioProduction>
```

<TypeTable
type={{
productionId: { type: 'string', required: true, description: "מזהה ההפקה." },
name: { type: 'string', description: "ב-clone בלבד: השם של העותק." },
}}
/>

```ts
await client.studio.productions.share(productionId)
const copy = await client.studio.productions.clone(productionId, { name: "Rome chase, take 2" })
```

העותק פרטי בהתחלה ולא בארכיון: מצב השיתוף ומצב הארכיון אף פעם לא עוברים אליו. ההעתקה נעשית דרך התצוגה **שלכם** של המקור, כך שהפח של מישהו אחר לא נכלל בעותק.

## client.studio: פריימים מתוכננים
המתודות האלה עוסקות בפריימים מתוכננים ובבדיקה שלהם. לפני שאתם מציגים בקרה, בדקו את `capabilities()`, וכשאתם פותחים מחדש הפקה, קראו פעם אחת ל-`reconcile()`, כי ייתכן שהתשובה לשליחה קודמת אבדה. שום דבר כאן לא מתחיל יצירה או מאשר מועמד, אלא אם אתם קוראים למתודה שעושה את זה.

| מתודה | מה היא עושה |
| --- | --- |
| [`capabilities()`](#studiocapabilities) | קריאת גרסאות הפורמט של התוכנית והפעולות שהפריסה הזו תומכת בהן |
| [`skill()`, `list()`, `validatePlan()`, `create()`](#studioskill-list-validateplan-and-create) | אותן קריאות ואותה פעולת יצירה כמו בשכבת ההפקות, בתוך המעטפת |
| [`get(id, options?)`](#studiogetid-options) | קריאת הפקה עם היכולות שלה |
| [`edit(id, input)`](#studioeditid-input) | החלת פעולות עם תנאי גרסה |
| [`saveEditorState(id, input)`](#studiosaveeditorstateid-input) | שמירה של שדות רגילים בעורך מול הגרסה שנטענה |
| [`generateKeyframe(id, input)`](#studiogeneratekeyframeid-input) | יצירת פריים מתוכנן, בלי לאשר אותו |
| [`generateShot(id, input)`](#studiogenerateshotid-input) | אומדן מחיר לתמונת סטילס או לקליפ, או שליחה של היצירה |
| [`acceptKeyframe(id, review, concurrency?)`](#studioacceptkeyframeid-review-concurrency) | אישור מועמד שנבדק |
| [`reconcile(id)`](#studioreconcileid) | רישום משימות שהסתיימו, בלי לאשר כלום |
| [`setShared(id, input)`](#studiosetsharedid-input) | שיתוף או ביטול שיתוף, בכפוף לגרסה שבדקתם |
| [`clone(id, input?)`](#studiocloneid-input) | העתקה של הפקה שמורה |
| [`importBundle(input)`](#studioimportbundleinput) | ייבוא של הפקה ניידת |
| [`appendBundle(id, input)`](#studioappendbundleid-input) | הוספה של חבילה להפקה |

### studio.capabilities()‎
מחזירה את גרסאות הפורמט של התוכנית, ואילו פעולות על פריימים מתוכננים הפריסה הזו תומכת בהן.

```ts
capabilities(): Promise<{ data: StudioProductionCapabilities }>
```

```ts
const { data: caps } = await client.studio.capabilities()
if (caps.operations.generateKeyframes) showGenerateFrameButton()
```

ב-`operations` יש דגל אחד לכל פעולה, כמו `readKeyframes`, `editKeyframes`, `generateKeyframes`, `acceptKeyframes`, `rejectKeyframes`, `editSequencePlans`, `generateLinkedClips`, `retakeLinkedClips`, `saveEditorState`, `revisionedSharing`, `editableSharedCopies`, `cloneLinkedProductions`, `importPlannedBundles`, `importLinkedBundles`, `appendPlannedBundles` ו-`appendLinkedBundles`. הערכים של `automaticAcceptance` ושל `unattendedGeneration` הם תמיד `false`.

### studio.skill(), list(), validatePlan()‎ ו-create()‎
אותן קריאות כמו `productions.skill()`, `list()`, `validatePlan()` ו-`create()`, אבל התשובה חוזרת בתוך המעטפת `{ data }`. ב-`list()`, השורות נמצאות ב-`response.data.data`.

```ts
skill(): Promise<{ data: Record<string, unknown> }>
list(options?: { limit?: number; cursor?: string; includeArchived?: boolean }): Promise<{ data: {
data: Array<{ id: string; name: string; version: number; updatedAt: string; thumbnailUrl: string | null; shared: boolean; archived: boolean; shotCount: number }>
nextCursor?: string
} }>
validatePlan(plan: Record<string, unknown>): Promise<{ data: { valid: boolean; errors: object[]; warnings: object[]; summary?: object } }>
create(input: { name?: string; plan?: Record<string, unknown> }): Promise<{ data: StudioProductionReply }>
```

<TypeTable
type={{
options: { type: '{ limit?, cursor?, includeArchived? }', description: "ב-list()‎: חלוקה לעמודים והכללה של שורות בארכיון." },
plan: { type: 'Record<string, unknown>', description: "ב-validatePlan()‎ וב-create()‎: התוכנית." },
name: { type: 'string', description: "ב-create()‎: השם של ההפקה." },
}}
/>

```ts
const { data: page } = await client.studio.list({ limit: 20 })
for (const row of page.data) console.log(row.name, row.shotCount)
```

### studio.get(id, options?)‎
מחזירה הפקה עם היכולות שלה. המתודה אף פעם לא מכניסה משימות להפקה.

```ts
get(id: string, options?: { detail?: "summary" | "full"; shotId?: string }): Promise<{ data: StudioProductionReply }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה ההפקה." },
detail: { type: '"summary" | "full"', description: "הערך full מוסיף כל תוצאה עם ההקשר שלה." },
shotId: { type: 'string', description: "קריאה של שוט אחד בלבד." },
}}
/>

```ts
const { data: { production } } = await client.studio.get(productionId, { detail: "full" })
const frame = production.keyframes?.[0] // { id, label, revision, previewUrl, acceptedUrl, pending, ... }
```

### studio.edit(id, input)‎
מחילה פעולות עם תנאי גרסה (`POST .../:id/ops`), בתוך המעטפת. ב-`remove_shot`, ב-`restore_trashed` וב-`purge_trashed`, השתמשו ב-`baseVersion` עם `strict`, ונתקו מקטע רצף משויך לפני שאתם מסירים את הסצנה שלו.

```ts
edit(id: string, input: { ops: Array<{ op: string; [field: string]: unknown }>; baseVersion?: number; strict?: boolean; clientRequestId?: string }): Promise<{ data: StudioProductionReply & { version: number; rebased: boolean; receipts: object[] } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה ההפקה." },
ops: { type: 'Array<{ op: string; ... }>', required: true, description: "הפעולות." },
baseVersion: { type: 'number', description: "הגרסה שטענתם." },
strict: { type: 'boolean', description: "דחייה במקום החלה מחדש על גרסה חדשה יותר." },
clientRequestId: { type: 'string', description: "אסימון הניסיון החוזר שלכם." },
}}
/>

```ts
await client.studio.edit(productionId, {
ops: [{ op: "reject_keyframe_result", keyframeId, expectedRevision, resultKey, expectedAcceptedResultKey, reason: "Face drifted" }],
baseVersion: version,
strict: true,
})
```

כמה פעולות על פריימים מתוכננים שנשלחות דרך `edit()`:

- **`reject_keyframe_result`** רושמת את הסטטוס **נדרש תיקון** בלי ליצור כלום. היא דורשת את `operations.rejectKeyframes`.
- **`update_sequence_plan`** עורכת את הרשימה הסדורה של המקטעים ברצף, כל אחד בצורה `{ shotId, startKeyframeId, endKeyframeId }`, ושומרת על מזהי הסצנות. היא דורשת את `operations.editSequencePlans`.
- **`detach_sequence_segment`** הופכת מקטע אחד לעצמאי, כש-`mode` מוגדר ל-`clear` או ל-`keep-accepted`. היא דורשת את `operations.editSequencePlans`.
- **`purge_trashed`** מוחקת סופית את הפריטים בפח שאתם מציגים, והפעולה `clear_trash` מרוקנת את כל הפחים, כולל הפריימים המתוכננים שבהם.

### studio.saveEditorState(id, input)‎
שומרת שדות רגילים של העורך מול הגרסה שטענתם. בדקו קודם את `operations.saveEditorState`. השמירה תמיד במצב strict: כשיש התנגשות, היא נכשלת עם 409, ולכן שמרו את הטיוטה המקומית וטענו מחדש לפני שאתם פותרים את ההתנגשות.

```ts
saveEditorState(id: string, input: { expectedVersion: number; graph: object; clientRequestId?: string })
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה ההפקה." },
expectedVersion: { type: 'number', required: true, description: "הגרסה שטענתם." },
graph: { type: 'object', required: true, description: "מצב העורך לשמירה." },
clientRequestId: { type: 'string', description: "אסימון הניסיון החוזר שלכם." },
}}
/>

```ts
await client.studio.saveEditorState(productionId, { expectedVersion: version, graph })
```

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

### studio.generateKeyframe(id, input)‎
יוצרת פריים מתוכנן בלי לאשר אותו. אין אומדן מחיר ליצירת פריימים.

```ts
generateKeyframe(id: string, input: { keyframeId: string; expectedRevision: number; clientRequestId?: string; overrides?: Record<string, unknown> }): Promise<{ data: { jobIds: string[]; deduped?: true; lane?: string } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה ההפקה." },
keyframeId: { type: 'string', required: true, description: "הפריים המתוכנן." },
expectedRevision: { type: 'number', required: true, description: "הגרסה של הפריים שקראתם." },
clientRequestId: { type: 'string', description: "אסימון הניסיון החוזר שלכם. שלחו את אותו ערך כשאתם מנסים שוב." },
overrides: { type: 'Record<string, unknown>', description: "שינויים להרצה הזו בלבד." },
}}
/>

```ts
const { data: caps } = await client.studio.capabilities()
const { data: { production } } = await client.studio.get(productionId, { detail: "full" })
const frame = production.keyframes?.[0]

if (frame && caps.operations.generateKeyframes) {
const { data: generation } = await client.studio.generateKeyframe(productionId, {
keyframeId: frame.id,
expectedRevision: frame.revision,
clientRequestId: crypto.randomUUID(),
})
// follow generation.jobIds with client.jobs, then call reconcile()
}
```

היצירה לא מאשרת מועמד ולא יוצרת פורטרט לדמות. אלמנט בליהוק שיש לו רק תיאור לא צריך פורטרט.

### studio.generateShot(id, input)‎
נותנת אומדן מחיר לתמונת סטילס או לקליפ של שוט, או שולחת את היצירה.

```ts
generateShot(id: string, input: StudioShotGenerationInput): Promise<{ data: StudioGenerationReply }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה ההפקה." },
kind: { type: '"still" | "clip"', required: true, description: "מה ליצור." },
shotId: { type: 'string', required: true, description: "השוט." },
count: { type: 'number', description: "כמה מועמדים." },
mode: { type: '"start" | "start-end" | "references"', description: "בקליפים: אילו קלטים לשלוח." },
dryRun: { type: 'boolean', description: "החזרת אומדן מחיר, בלי לשלוח כלום." },
expectedInputHash: { type: 'string', description: "בקליפים מקושרים: ה-inputHash של אומדן המחיר שבדקתם." },
retakeResultKey: { type: 'string', description: "בקליפים מקושרים: הטייק לצילום חוזר, עם הבקשה המקורית שלו." },
clientRequestId: { type: 'string', description: "אסימון הניסיון החוזר שלכם." },
overrides: { type: 'Record<string, unknown>', description: "שינויים להרצה הזו בלבד." },
}}
/>

```ts
const { data: quote } = await client.studio.generateShot(productionId, { kind: "clip", shotId, dryRun: true })
if ("inputHash" in quote) {
await client.studio.generateShot(productionId, {
kind: "clip",
shotId,
expectedInputHash: quote.inputHash,
clientRequestId: crypto.randomUUID(),
})
}
```

**קליפים מקושרים.** אומדן מחיר לקליפ בין פריימים מתוכננים כולל את `inputHash`, את ה-`endpointPins` המאושרים, את הערכים המנורמלים של משך הקליפ, הרזולוציה, יחס הגובה־רוחב והגדרות האודיו, ואת ה-`creditIdentifier` ששימש לתמחור. העבירו את ה-`inputHash` שבדקתם בתור `expectedInputHash`. אם ההגדרות או הפריימים המאושרים השתנו מאז האומדן, הקריאה נכשלת עם `409 sequence_quote_changed` לפני שנשלח משהו; בקשו אומדן מחיר חדש. הקרדיטים שבאומדן הם הערכה בלבד: כשהיצירה מתחילה, משוריין המחיר העדכני.

**צילומים חוזרים.** כשהדגל `operations.retakeLinkedClips` הוא `true`, העבירו את `retakeResultKey` עם `kind: "clip"` ועם `shotId`, וקבלו אומדן מחיר עם `dryRun: true`. שלחו את הבקשה עם ה-`expectedInputHash` שבדקתם ועם `clientRequestId` חדש, והשמיטו את `mode`, את `overrides` ואת `count`. צילום חוזר שולח שוב את הבקשה המקורית ומשתמש בתמונות השמורות של הפריימים, גם אחרי שהתוכנית או האישור השתנו. הטייקים הקודמים נשארים בהיסטוריה. טייק בלי בקשה מקורית שאפשר לאמת או בלי תמונות שמורות נדחה, וצילום חוזר לא יוצר סרטון זהה בייט לבייט.

### studio.acceptKeyframe(id, review, concurrency?)‎
מאשרת מועמד שנבדק לפריים מתוכנן. זה שלב נפרד ומפורש: היצירה אף פעם לא קוראת למתודה הזו.

```ts
acceptKeyframe(id: string, review: StudioKeyframeAcceptanceInput, concurrency?: { baseVersion?: number; strict?: boolean; clientRequestId?: string })
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה ההפקה." },
keyframeId: { type: 'string', required: true, description: "הפריים המתוכנן." },
expectedRevision: { type: 'number', required: true, description: "הגרסה של הפריים שבדקתם." },
resultKey: { type: 'string', required: true, description: "המועמד לאישור." },
expectedAcceptedResultKey: { type: 'string | null', required: true, description: "המועמד שאושר קודם, או null." },
requirementChecks: { type: 'Array<{ requirementId: string; outcome: "pass" | "waived" }>', required: true, description: "התוצאה של כל בדיקת דרישה." },
waivedReason: { type: 'string', description: "נדרש כשמוותרים על בדיקה." },
concurrency: { type: '{ baseVersion?, strict?, clientRequestId? }', description: "תנאי הגרסה לכתיבה." },
}}
/>

```ts
await client.studio.acceptKeyframe(productionId, {
keyframeId: frame.id,
expectedRevision: frame.revision,
resultKey,
expectedAcceptedResultKey: frame.acceptedResultKey,
requirementChecks, // one { requirementId, outcome } per requirement of the frame
})
```

כשיש התנגשות, המתודה זורקת את השגיאה הרגילה. ה-SDK אף פעם לא בוחר מיוזמתו תוצאה אחרת, ולא מנסה שוב מיוזמתו מול גרסה חדשה יותר.

### studio.reconcile(id)‎
רושמת משימות שהסתיימו, בלי לאשר אף מועמד ובלי להתחיל יצירה. המתודה בודקת גם משימות של סצנות שבפח: קליפ שהסתיים נשאר בגרף השמור של אותה סצנה, ומשחזרים אותו עם `restore_trashed`.

```ts
reconcile(id: string): Promise<{ data: StudioProductionReply & { landed: string[]; pending: string[]; failed: string[]; version: number } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה ההפקה." },
}}
/>

```ts
const { data } = await client.studio.reconcile(productionId)
console.log(data.landed, data.pending)
```

### studio.setShared(id, input)‎
משתפת הפקה או מבטלת את השיתוף שלה, בכפוף לגרסה שבדקתם. בדקו את `operations.revisionedSharing` והעבירו `expectedVersion`: אז, אם הייתה עריכה במקביל, הקריאה נכשלת עם `409 workflow_conflict`, וה-SDK לא מנסה שוב. רק מי שמורשה לשנות את הנראות יכול להשתמש במתודה.

```ts
setShared(id: string, input: { shared: boolean; allowEditableCopy?: boolean; expectedVersion?: number }): Promise<{ data: StudioProductionReply }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה ההפקה." },
shared: { type: 'boolean', required: true, description: "הערך true פותח את קישור השיתוף, והערך false סוגר אותו." },
allowEditableCopy: { type: 'boolean', description: "מאפשר לצופים מחוברים שנכנסים דרך הקישור להעתיק את ההפקה כך שאפשר יהיה לערוך אותה. דורש את operations.editableSharedCopies." },
expectedVersion: { type: 'number', description: "הגרסה שבדקתם." },
}}
/>

```ts
await client.studio.setShared(productionId, { shared: true, allowEditableCopy: true, expectedVersion: version })
```

עם `allowEditableCopy`, הבעלים או מנהל של סביבת העבודה מאפשרים לצופים שנכנסים דרך הקישור להעתיק את התוכנית השמורה, את הפרומפטים, את התיאורים של האלמנטים בליהוק, את קלטי הייחוס השמורים ואת היסטוריית הטייקים. הפח והערות הבדיקה הפרטיות אף פעם לא נכללים. העותקים פרטיים בהתחלה, ואף פריים בהם לא מאושר. כיבוי ההעתקה, או ביטול השיתוף, חוסם עותקים חדשים; עותקים שכבר נוצרו נשארים עצמאיים.

### studio.clone(id, input?)‎
מעתיקה הפקה שמורה. לפני שאתם מעתיקים הפקה עם פריימים מקושרים, בדקו את `operations.cloneLinkedProductions`, והעבירו את ה-`expectedVersion` שטענתם: אם המקור השתנה, הקריאה נכשלת עם 409.

```ts
clone(id: string, input?: { name?: string; projectId?: string; expectedVersion?: number }): Promise<{ data: StudioProductionReply }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "ההפקה להעתקה." },
name: { type: 'string', description: "השם של העותק." },
projectId: { type: 'string', description: "פרויקט שלכם שאליו נכנס העותק." },
expectedVersion: { type: 'number', description: "הגרסה של המקור שטענתם." },
}}
/>

```ts
const { data: { production: copy } } = await client.studio.clone(productionId, {
name: "Rome chase copy",
expectedVersion: version,
})
```

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

### studio.importBundle(input)‎
מייבאת הפקה ניידת כהפקה חדשה ופרטית, עם מזהים חדשים לסצנות, לפריימים ולרצפים (`POST .../import-bundle`). למתכונים ולתוכניות בלי מדיה, בדקו את `operations.importPlannedBundles`, ולחבילות עם מדיה שמורה של פריימים, בדקו את `importLinkedBundles`.

```ts
importBundle(input: { bundle: Record<string, unknown>; projectId?: string }): Promise<{ data: StudioProductionReply }>
```

<TypeTable
type={{
bundle: { type: 'Record<string, unknown>', required: true, description: "ההפקה הניידת." },
projectId: { type: 'string', description: "פרויקט שלכם שאליו מייבאים." },
}}
/>

```ts
const { data: { production } } = await client.studio.importBundle({ bundle })
```

חבילה מקושרת מציינת את הפקת המקור שלה; השרת בודק שהיא בבעלותכם, ומאמת כל תמונה שמורה לפני שהוא מעתיק משהו. כשאין גישה, או כשנתוני המקור מזויפים, הייבוא נדחה לפני שההפקה החדשה נוצרת. אף אחד משני סוגי הייבוא לא מעביר אישורים או משימות שרצות, ואף אחד מהם לא יוצר מדיה.

### studio.appendBundle(id, input)‎
מוסיפה חבילה שלמה להפקה שאפשר לערוך, עם מזהים חדשים ובדיקה מדויקת של הגרסה (`POST .../:id/import-bundle`). אסימון OAuth צריך `workflows:write`, ואתם צריכים הרשאת עריכה להפקה. בדקו קודם את `appendPlannedBundles` או את `appendLinkedBundles`.

```ts
appendBundle(id: string, input: { bundle: Record<string, unknown>; expectedVersion: number; afterShotId?: string; applyFilm?: boolean }): Promise<{
data: { production: StudioProductionRecord; importedShotIds: string[]; importedKeyframeIds: string[] }
}>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "ההפקה שאליה מוסיפים." },
bundle: { type: 'Record<string, unknown>', required: true, description: "החבילה להוספה." },
expectedVersion: { type: 'number', required: true, description: "הגרסה של ההפקה שטענתם." },
afterShotId: { type: 'string', description: "הוספה אחרי השוט הזה. השמיטו אותו כדי להוסיף בסוף." },
applyFilm: { type: 'boolean', description: "אימוץ מראה הסרט של החבילה. המוזיקה, החיתוכים והבריף של הסרט נשארים כמו שהם." },
}}
/>

```ts
const { data } = await client.studio.appendBundle(productionId, { bundle, expectedVersion: version })
console.log(data.importedShotIds)
```

הסצנות, הפריימים, המשימות, השיתוף ושאר ההגדרות הקיימים נשארים; התפקידים שיובאו ממוזגים לליהוק. צריך לאשר שוב את הפריימים שיובאו. קריאה עם `afterShotId` לא מוכר או עם `expectedVersion` לא עדכני נכשלת.

## client.shots
רשומות השוטים שמאחורי קישורי השיתוף `/s/:id`, לשיתוף ולרימיקס. שוט שומר את המצב של כלי הבנייה: הבחירות בבוררים, הפרומפטים, מודלי היעד, הנכסים שאוזכרו עם `@` וכתובות התוצאות, תחת מזהה של 12 תווים שאי אפשר לנחש, שהוא גם מפתח השיתוף. שוטים הם **פרטיים** כברירת מחדל; שיתוף הוא שינוי נראות שאתם עושים בעצמכם.

```ts
create(input?: CreateShotInput): Promise<{ id: string }>
get(id: string): Promise<{ shot: Shot }>
update(id: string, input: UpdateShotInput): Promise<{ shot: Shot }>
delete(id: string): Promise<void>
```

<TypeTable
type={{
id: { type: 'string', description: "ב-get, ב-update וב-delete: מזהה השוט." },
mode: { type: '"single" | "multi-shot" | "frame-to-motion" | "storyboard"', description: "המצב של כלי הבנייה." },
selectionState: { type: 'Record<string, unknown>', description: "הבחירות בבוררים." },
freeText: { type: 'string', description: "טקסט חופשי לפרומפט." },
negativePrompt: { type: 'string', description: "ממה להימנע." },
assembledPrompt: { type: 'string', description: "הפרומפט הסופי." },
perModelPrompts: { type: 'Record<string, string>', description: "פרומפט לכל מודל." },
models: { type: 'string[]', description: "מודלי היעד." },
entityRefs: { type: 'Array<{ entitySlug, variantSlug?, role?, kind? }>', description: "הדמויות, המיקומים, האובייקטים והיצורים שאוזכרו." },
resultUrls: { type: 'string[]', description: "כתובות התוצאות. הן חייבות להיות כתובות URL ציבוריות פשוטות של http או https: כתובות חתומות נדחות, כך שאסימון אף פעם לא דולף לרשומת שיתוף." },
visibility: { type: '"private" | "public"', default: '"private"', description: "מי יכול לקרוא את השוט." },
}}
/>

```ts
const { id } = await client.shots.create({ mode: "single", freeText: "A lighthouse in a storm", models: ["nano-banana-2"] })
await client.shots.update(id, { visibility: "public" }) // anyone with the id can now read it
const { shot } = await client.shots.get(id)
```

כל מי שהמזהה של שוט ציבורי בידיו יכול לקרוא אותו. רק הבעלים של שוט פרטי יכולים לקרוא אותו, וקריאה של כל אחד אחר נכשלת עם `NotFoundError`. רק הבעלים יכולים לעדכן או למחוק שוט.

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

### מה זה הפקת Studio?

הפקת Studio היא תהליך שבהגדרות שלו נמצאים השוטים של סרט: לכל שוט יש תמונת סטילס של הפריים, קליפ מונפש אופציונלי, והתוכנית, המראות, הליהוק והקול שיצרו אותם. אפליקציית ה-Studio וה-SDK קוראים וכותבים את אותה הפקה.

### למה למשאב client.studio יש שתי קבוצות של מתודות?

השכבה client.studio.productions עובדת על מסמך ההפקה: פעולות, תמונות סטילס, קליפים, קול ומוזיקה, והיא מחזירה את המטען עצמו. השכבה client.studio עוסקת בפריימים מתוכננים ובבדיקה שלהם, והיא מחזירה את מעטפת ה-data של ה-API. שתי השכבות עובדות על אותן הפקות.

### איך מנסים שוב יצירה בהפקת Studio בלי לשלם פעמיים?

העבירו בפרמטר clientRequestId מזהה שאתם יוצרים בעצמכם, ושלחו את אותו מזהה גם בניסיון החוזר. השרת מחזיר את המשימות שהקריאה הראשונה התחילה, מסמן את התשובה ב-deduped, ולא נגבים שוב קרדיטים.

### איך בודקים קודם את המחיר של תמונת סטילס או של קליפ?

קראו למתודה generateStill או למתודה generateClip, כש-dryRun מוגדר ל-true. התשובה היא אומדן מחיר עם הקרדיטים, ושום דבר לא נשלח. צמצמו את הטיפוס של התשובה עם הפונקציה isStudioGenerateEstimate.
