הפקות Studio
בניית הפקות Studio מתוכנית עם ה-SDK ל-TypeScript, עריכה בפעולות, יצירת תמונות סטילס וקליפים, בדיקת פריימים מתוכננים, שיתוף והעתקה.
זמין ב-Nodaro Cloud
הפקת Studio היא תהליך של Nodaro שבהגדרות שלו נמצאים השוטים של סרט. לכל שוט יש תמונת סטילס של הפריים, קליפ מונפש אופציונלי, והתוכנית, המראות, שיוכי הליהוק והקול שיצרו אותם. המשאב client.studio קורא וכותב הפקות, כך שסקריפט, עוזר AI ואפליקציית ה-Studio עובדים על אותה הפקה. המשאב client.shots שומר את רשומות השוטים המשותפות שמאחורי קישורי שיתוף. המתודות קוראות לנקודות הקצה שבדף ה-API הפקות Studio. המדריך לכתיבת הפקות נמצא בדף הפקות Studio דרך MCP.
הפקות 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() | קריאת המדריך לכתיבה, הקטלוג, סכמת התוכנית ומדריך הפעולות |
validatePlan(plan) | בדיקת תוכנית, בחינם |
list(opts?) | רשימת ההפקות שלכם |
get(productionId, opts?) | קריאת הפקה |
exportPlan(productionId, opts?) | תכנון ותמחור של שלבי הייצוא |
create(input?) | יצירת הפקה, מתוכנית או בלעדיה |
ops(productionId, input) | החלת אצווה של פעולות, או תצוגה מקדימה שלה |
reconcile(productionId) | הכנסת יצירות שהסתיימו להפקה |
importPlan(productionId, plan, opts?) | הוספת הסצנות של תוכנית להפקה |
describe(productionId, input) | הפיכת בריף לסצנות |
generate(), generateStill(), generateClip() | יצירת תמונת סטילס לשוט, או הנפשה שלו |
frame(productionId, input) | שליפת פריים מהקליפ של שוט |
voice(productionId, input) | הקראת השורה של שוט |
revoice(productionId, input) | החלפת הקולות בקליפ של שוט |
music(productionId, input) | יצירת הפסקול של הסרט |
share(), unshare(), clone() | פתיחה או סגירה של קישור השיתוף, או העתקה של ההפקה |
client.studio.productions
productions.skill()
מחזירה את המדריך לכתיבה, את הקטלוג המלא, את ה-JSON Schema של התוכנית ואת מדריך הפעולות, כפי שהם נוצרים מהגרסה שהשרת מריץ. המתודה לא עולה כלום.
skill(): Promise<{ skill: string; catalog: string; schema: Record<string, unknown>; operating: string; generatedFrom: object }>const { skill, schema, operating } = await client.studio.productions.skill()השדה operating מפרט את הפעולות שהמתודה ops() מקבלת. קבלו אותו בזמן ריצה, במקום לקבע את רשימת הפעולות בקוד.
productions.validatePlan(plan)
בודקת תוכנית לפני שהיא הופכת להפקה. המתודה לא עולה כלום, לא שומרת כלום ומתאימה את השמות שבליהוק לספרייה שלכם. תקנו את השגיאות שב-errors ובדקו שוב, עד שהשדה valid הוא true, ואז קראו ל-create({ plan }).
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 }
}>מאפיין
סוג
const check = await client.studio.productions.validatePlan(plan)
if (!check.valid) console.log(check.errors)השדה summary.bound סופר את האלמנטים בליהוק שהותאמו לדמות בספרייה שלכם.
productions.list(opts?)
מחזירה את ההפקות שלכם, מהחדש לישן.
list(opts?: { limit?: number; cursor?: string; includeArchived?: boolean }): Promise<{ data: StudioProduction[]; nextCursor?: string }>מאפיין
סוג
const { data: productions } = await client.studio.productions.list({ limit: 20 })productions.get(productionId, opts?)
מחזירה הפקה. המתודה רק מחזירה נתונים, והיא אף פעם לא מכניסה להפקה משימה שהסתיימה, ולכן כשאתם מחכים למשימה, קראו קודם ל-reconcile().
get(productionId: string, opts?: { detail?: "summary" | "full"; shotId?: string }): Promise<StudioProduction>מאפיין
סוג
const production = await client.studio.productions.get(productionId, { detail: "full" })productions.exportPlan(productionId, opts?)
מחזירה לפי הסדר את השלבים שמרכיבים את הסרט, עם המחירים שלהם. המתודה לא מריצה כלום ולא מוציאה קרדיטים: הריצו את השלבים בעצמכם, עם המתודות הרגילות להרצת רכיבים.
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[]
}>מאפיין
סוג
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?)
יוצרת הפקה, ויכולה גם להכניס אליה תוכנית באותה קריאה.
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 }
}>מאפיין
סוג
const { production, summary } = await client.studio.productions.create({ name: "Rome chase", plan })productions.ops(productionId, input)
מחילה על הפקה אצווה של פעולות. כל שינוי הוא פעולה, שמציינת את היעד שלה לפי מפתח יציב, כמו מזהה של שוט, slug של תפקיד או מזהה המשימה של תוצאה, ולעולם לא לפי מיקום.
ops(productionId: string, input: StudioOpsRequest): Promise<StudioOpsResponse>
ops(productionId: string, input: StudioOpsRequest & { dryRun: true }): Promise<StudioOpsDryRunResponse>מאפיין
סוג
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, שה-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, והקריאה מקבלת את הטיפוסים של החלה, אף שבפועל היא עדיין מחזירה תצוגה מקדימה.
תצוגה מקדימה שולחת שתי בקשות: קודם אצווה ריקה, שמוודאת שהפריסה תומכת בתצוגות מקדימות, ואחר כך את האצווה שלכם. אחרת, פריסה שלא תומכת בתצוגות מקדימות הייתה מחילה את האצווה שלכם בלי אזהרה. שתי שגיאות אפשריות:
import { StudioPreviewAppliedError, StudioPreviewUnavailable } from "@nodaro/sdk"
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)
מכניסה להפקה כל יצירה שהסתיימה מאז הבדיקה הקודמת שלכם, ומדווחת מה עדיין רץ. זו הקריאה היחידה שהופכת משימות שהסתיימו לתוצאות כשהאפליקציה לא פתוחה, והיא כותבת רק כשמשהו נכנס להפקה.
reconcile(productionId: string): Promise<{
landed: string[]
pending: string[]
failed: string[]
warnings: string[]
production: StudioProduction
version: number
}>מאפיין
סוג
const { landed, pending } = await client.studio.productions.reconcile(productionId)השדה landed מפרט את המשימות שהמדיה שלהן כבר נמצאת בהפקה, השדה pending את המשימות שעדיין רצות, והשדה failed את המשימות שנכשלו או שבוטלו.
productions.importPlan(productionId, plan, opts?)
מוסיפה את הסצנות של תוכנית להפקה קיימת.
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 }
}>מאפיין
סוג
await client.studio.productions.importPlan(productionId, extraScenesPlan)productions.describe(productionId, input)
הופכת בריף לסצנות בעזרת הבמאי. המתודה מתחילה משימה וחוזרת מיד; הסצנות נכנסות להפקה דרך reconcile(). ההפקה חוזרת כשההרצה רשומה בה כטיוטה ממתינה.
describe(productionId: string, input: {
brief: string
llmModel: string
mode?: "append" | "replace"
label?: string
clientRequestId?: string
}): Promise<{ production: StudioProduction; jobId: string }>מאפיין
סוג
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. הרצה שולחת את המשימות, רושמת בהפקה סימון המתנה וחוזרת: אף קריאה לא ממתינה דקות. השרת בונה את הבקשה מהתוכנית, מהמראות ומהייחוסים של השוט עצמו, ולכן סקריפט ולחיצה באפליקציה יוצרים את אותה מדיה.
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>מאפיין
סוג
import { isStudioGenerateEstimate } from "@nodaro/sdk"
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 של התמונה.
frame(productionId: string, input: {
shotId: string
mode?: "first" | "last" | "timestamp"
timestamp?: number
target?: "new-shot" | "start-frame" | "end-frame" | "still"
clientRequestId?: string
}): Promise<StudioMediaResponse>מאפיין
סוג
const { url } = await client.studio.productions.frame(productionId, { shotId: "shot-2", mode: "last" })productions.voice(productionId, input)
מקריאה את השורה של שוט ושומרת את ההקלטה בשוט. המתודה ממתינה למשימה.
voice(productionId: string, input: {
shotId: string
text: string
voiceId?: string
voiceType?: "premade" | "custom" | "library"
ttsProvider?: string
delivery?: Record<string, number>
clientRequestId?: string
}): Promise<StudioMediaResponse>מאפיין
סוג
await client.studio.productions.voice(productionId, { shotId: "shot-3", text: "We're out of time." })productions.revoice(productionId, input)
מחליפה את הקולות בקליפ הפעיל של שוט. ההחלפה נמשכת כמה דקות, ולכן המתודה מחזירה jobId, והקליפ החדש נכנס להפקה דרך סימון ההמתנה שלו.
revoice(productionId: string, input: { shotId: string; plan: Record<string, unknown>; clientRequestId?: string }): Promise<{ production: StudioProduction; jobId: string }>מאפיין
סוג
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)
יוצרת את הפסקול של הסרט. הרצועה המוגמרת נכנסת להפקה דרך סימון ההמתנה שלה.
music(productionId: string, input: {
prompt: string
duration?: number
instrumental?: boolean
vocalGender?: string
model?: string
clientRequestId?: string
}): Promise<{ production: StudioProduction; jobId: string }>מאפיין
סוג
const { jobId } = await client.studio.productions.music(productionId, {
prompt: "Tense strings building to a chase",
instrumental: true,
})שיתוף והעתקה
המתודה share() פותחת את תצוגת השיתוף בקישור, והמתודה unshare() סוגרת אותה שוב. שיתוף הוא קריאה נפרדת ולא פעולה, כך שעריכה אף פעם לא משנה, כתופעת לוואי, את מי שיכול לראות את העבודה. המתודה clone() מעתיקה הפקה שבבעלותכם, או הפקה שאתם יכולים לראות, לפרויקט ה-Studio שלכם.
share(productionId: string): Promise<StudioProduction>
unshare(productionId: string): Promise<StudioProduction>
clone(productionId: string, input?: { name?: string }): Promise<StudioProduction>מאפיין
סוג
await client.studio.productions.share(productionId)
const copy = await client.studio.productions.clone(productionId, { name: "Rome chase, take 2" })העותק פרטי בהתחלה ולא בארכיון: מצב השיתוף ומצב הארכיון אף פעם לא עוברים אליו. ההעתקה נעשית דרך התצוגה שלכם של המקור, כך שהפח של מישהו אחר לא נכלל בעותק.
client.studio: פריימים מתוכננים
המתודות האלה עוסקות בפריימים מתוכננים ובבדיקה שלהם. לפני שאתם מציגים בקרה, בדקו את capabilities(), וכשאתם פותחים מחדש הפקה, קראו פעם אחת ל-reconcile(), כי ייתכן שהתשובה לשליחה קודמת אבדה. שום דבר כאן לא מתחיל יצירה או מאשר מועמד, אלא אם אתם קוראים למתודה שעושה את זה.
| מתודה | מה היא עושה |
|---|---|
capabilities() | קריאת גרסאות הפורמט של התוכנית והפעולות שהפריסה הזו תומכת בהן |
skill(), list(), validatePlan(), create() | אותן קריאות ואותה פעולת יצירה כמו בשכבת ההפקות, בתוך המעטפת |
get(id, options?) | קריאת הפקה עם היכולות שלה |
edit(id, input) | החלת פעולות עם תנאי גרסה |
saveEditorState(id, input) | שמירה של שדות רגילים בעורך מול הגרסה שנטענה |
generateKeyframe(id, input) | יצירת פריים מתוכנן, בלי לאשר אותו |
generateShot(id, input) | אומדן מחיר לתמונת סטילס או לקליפ, או שליחה של היצירה |
acceptKeyframe(id, review, concurrency?) | אישור מועמד שנבדק |
reconcile(id) | רישום משימות שהסתיימו, בלי לאשר כלום |
setShared(id, input) | שיתוף או ביטול שיתוף, בכפוף לגרסה שבדקתם |
clone(id, input?) | העתקה של הפקה שמורה |
importBundle(input) | ייבוא של הפקה ניידת |
appendBundle(id, input) | הוספה של חבילה להפקה |
studio.capabilities()
מחזירה את גרסאות הפורמט של התוכנית, ואילו פעולות על פריימים מתוכננים הפריסה הזו תומכת בהן.
capabilities(): Promise<{ data: StudioProductionCapabilities }>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.
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 }>מאפיין
סוג
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?)
מחזירה הפקה עם היכולות שלה. המתודה אף פעם לא מכניסה משימות להפקה.
get(id: string, options?: { detail?: "summary" | "full"; shotId?: string }): Promise<{ data: StudioProductionReply }>מאפיין
סוג
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, ונתקו מקטע רצף משויך לפני שאתם מסירים את הסצנה שלו.
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[] } }>מאפיין
סוג
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, ולכן שמרו את הטיוטה המקומית וטענו מחדש לפני שאתם פותרים את ההתנגשות.
saveEditorState(id: string, input: { expectedVersion: number; graph: object; clientRequestId?: string })מאפיין
סוג
await client.studio.saveEditorState(productionId, { expectedVersion: version, graph })השמירה לא יכולה לשנות תוכניות של פריימים, אישורים, שיוכים של פריימי קצה, היסטוריית משימות, פריטים מוגנים בפח או שיתוף. לכל אחד מהם יש פעולות משלו.
studio.generateKeyframe(id, input)
יוצרת פריים מתוכנן בלי לאשר אותו. אין אומדן מחיר ליצירת פריימים.
generateKeyframe(id: string, input: { keyframeId: string; expectedRevision: number; clientRequestId?: string; overrides?: Record<string, unknown> }): Promise<{ data: { jobIds: string[]; deduped?: true; lane?: string } }>מאפיין
סוג
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)
נותנת אומדן מחיר לתמונת סטילס או לקליפ של שוט, או שולחת את היצירה.
generateShot(id: string, input: StudioShotGenerationInput): Promise<{ data: StudioGenerationReply }>מאפיין
סוג
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?)
מאשרת מועמד שנבדק לפריים מתוכנן. זה שלב נפרד ומפורש: היצירה אף פעם לא קוראת למתודה הזו.
acceptKeyframe(id: string, review: StudioKeyframeAcceptanceInput, concurrency?: { baseVersion?: number; strict?: boolean; clientRequestId?: string })מאפיין
סוג
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.
reconcile(id: string): Promise<{ data: StudioProductionReply & { landed: string[]; pending: string[]; failed: string[]; version: number } }>מאפיין
סוג
const { data } = await client.studio.reconcile(productionId)
console.log(data.landed, data.pending)studio.setShared(id, input)
משתפת הפקה או מבטלת את השיתוף שלה, בכפוף לגרסה שבדקתם. בדקו את operations.revisionedSharing והעבירו expectedVersion: אז, אם הייתה עריכה במקביל, הקריאה נכשלת עם 409 workflow_conflict, וה-SDK לא מנסה שוב. רק מי שמורשה לשנות את הנראות יכול להשתמש במתודה.
setShared(id: string, input: { shared: boolean; allowEditableCopy?: boolean; expectedVersion?: number }): Promise<{ data: StudioProductionReply }>מאפיין
סוג
await client.studio.setShared(productionId, { shared: true, allowEditableCopy: true, expectedVersion: version })עם allowEditableCopy, הבעלים או מנהל של סביבת העבודה מאפשרים לצופים שנכנסים דרך הקישור להעתיק את התוכנית השמורה, את הפרומפטים, את התיאורים של האלמנטים בליהוק, את קלטי הייחוס השמורים ואת היסטוריית הטייקים. הפח והערות הבדיקה הפרטיות אף פעם לא נכללים. העותקים פרטיים בהתחלה, ואף פריים בהם לא מאושר. כיבוי ההעתקה, או ביטול השיתוף, חוסם עותקים חדשים; עותקים שכבר נוצרו נשארים עצמאיים.
studio.clone(id, input?)
מעתיקה הפקה שמורה. לפני שאתם מעתיקים הפקה עם פריימים מקושרים, בדקו את operations.cloneLinkedProductions, והעבירו את ה-expectedVersion שטענתם: אם המקור השתנה, הקריאה נכשלת עם 409.
clone(id: string, input?: { name?: string; projectId?: string; expectedVersion?: number }): Promise<{ data: StudioProductionReply }>מאפיין
סוג
const { data: { production: copy } } = await client.studio.clone(productionId, {
name: "Rome chase copy",
expectedVersion: version,
})העותק פרטי בהתחלה. הוא שומר את הקלטים של הפריימים, שנספרים במכסת האחסון שלכם, מקבל מזהים חדשים לפריימים, לסצנות ולרצפים, ולא כולל משימות שרצות או פריימים מאושרים. בדקו ואשרו את הפריימים שלו לפני שאתם יוצרים מדיה שתלויה בהם. ההעתקה לא שולחת שום יצירה.
studio.importBundle(input)
מייבאת הפקה ניידת כהפקה חדשה ופרטית, עם מזהים חדשים לסצנות, לפריימים ולרצפים (POST .../import-bundle). למתכונים ולתוכניות בלי מדיה, בדקו את operations.importPlannedBundles, ולחבילות עם מדיה שמורה של פריימים, בדקו את importLinkedBundles.
importBundle(input: { bundle: Record<string, unknown>; projectId?: string }): Promise<{ data: StudioProductionReply }>מאפיין
סוג
const { data: { production } } = await client.studio.importBundle({ bundle })חבילה מקושרת מציינת את הפקת המקור שלה; השרת בודק שהיא בבעלותכם, ומאמת כל תמונה שמורה לפני שהוא מעתיק משהו. כשאין גישה, או כשנתוני המקור מזויפים, הייבוא נדחה לפני שההפקה החדשה נוצרת. אף אחד משני סוגי הייבוא לא מעביר אישורים או משימות שרצות, ואף אחד מהם לא יוצר מדיה.
studio.appendBundle(id, input)
מוסיפה חבילה שלמה להפקה שאפשר לערוך, עם מזהים חדשים ובדיקה מדויקת של הגרסה (POST .../:id/import-bundle). אסימון OAuth צריך workflows:write, ואתם צריכים הרשאת עריכה להפקה. בדקו קודם את appendPlannedBundles או את appendLinkedBundles.
appendBundle(id: string, input: { bundle: Record<string, unknown>; expectedVersion: number; afterShotId?: string; applyFilm?: boolean }): Promise<{
data: { production: StudioProductionRecord; importedShotIds: string[]; importedKeyframeIds: string[] }
}>מאפיין
סוג
const { data } = await client.studio.appendBundle(productionId, { bundle, expectedVersion: version })
console.log(data.importedShotIds)הסצנות, הפריימים, המשימות, השיתוף ושאר ההגדרות הקיימים נשארים; התפקידים שיובאו ממוזגים לליהוק. צריך לאשר שוב את הפריימים שיובאו. קריאה עם afterShotId לא מוכר או עם expectedVersion לא עדכני נכשלת.
client.shots
רשומות השוטים שמאחורי קישורי השיתוף /s/:id, לשיתוף ולרימיקס. שוט שומר את המצב של כלי הבנייה: הבחירות בבוררים, הפרומפטים, מודלי היעד, הנכסים שאוזכרו עם @ וכתובות התוצאות, תחת מזהה של 12 תווים שאי אפשר לנחש, שהוא גם מפתח השיתוף. שוטים הם פרטיים כברירת מחדל; שיתוף הוא שינוי נראות שאתם עושים בעצמכם.
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>מאפיין
סוג
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.
דפים קשורים
הפקות Studio
הפקות Studio
שגיאות
Recast ב-SDK
משימות והרצות
עודכן לאחרונה