דילוג לתוכן
תיעוד Nodaro
תיעודמדריך הרכיביםמודליםתבניותסוכני AI (MCP)מפתחיםאירוח עצמימחקרהשוואות
SDK ל-TypeScript

מדיה והעלאות

העלאת קבצים, ספריית המדיה, ייבוא סרטונים מקישורים, קיצוץ קליפים, צריבת כתוביות ושילוב שכבות של תמונות וסרטונים עם ה-SDK של Nodaro.

client.uploads שומר את הקבצים שלכם ב-Nodaro, client.library מחזיר את המדיה שכבר יש לכם, והמשאב client.gallery מאפשר לעיין בגלריה הציבורית ובמועדפים שלכם. המשאב client.media מכין ועורך מדיה: הוא מוריד סרטונים מקישורים, מקצץ קליפים, צורב כתוביות ומשלב שכבות של תמונות וסרטונים. רוב המתודות של client.media מתחילות משימה ומחזירות את ה-jobId שלה, ואת הסטטוס של המשימה בודקים מדי כמה שניות עם client.jobs.getStatus(). המתודות קוראות לנקודות הקצה שבדפי ה-API העלאות וקול ומדיה.

מתודות

מתודהמה היא עושה
uploads.upload(file)העלאת קובץ אחד וקבלת הכתובת הציבורית שלו
uploads.uploadJson(json)שמירת מסמך JSON וקבלת הכתובת שלו
uploads.importUrl(url, options?)העתקת תמונה מכתובת אינטרנט לאחסון שלכם
library.list(params?)רשימת המדיה השמורה שלכם וחיפוש בה
gallery.browse(params?)עיון בגלריה הציבורית
gallery.toggleFavorite(jobId)סימון יצירה כמועדפת, או הסרת הסימון
gallery.favorites()רשימת המזהים של המשימות שסימנתם כמועדפות
contentRights.declaration()קריאת הצהרת זכויות התוכן
contentRights.confirm(input)אישור ההצהרה לקישור אחד לפני הייבוא שלו
contentRights.authorization()קריאת ההרשאה הקבועה לייבוא דרך מפתחות וסוכנים
contentRights.authorize(input)מתן הרשאה לייבוא דרך מפתחות וסוכנים, מתוך הסשן של בעל החשבון עצמו
contentRights.revokeAuthorization()ביטול ההרשאה הזו
media.downloadVideo(input)הורדת הסרטון שמאחורי קישור ושמירה שלו באחסון שלכם
media.downloadVideoProgress(downloadId, opts?)קבלת ההתקדמות של הורדה בזמן אמת
media.videoMetadata(input)קריאת האורך והגודל של סרטון מרשת חברתית בלי להוריד אותו
media.saveToStorage(input)העתקת קובץ מדיה מכתובת URL לאחסון שלכם
media.process(input)קיצוץ או חיתוך של קובץ שמור, בחינם
media.discard(input)מחיקת קובץ שיצרתם זה עתה ושכבר אין לכם צורך בו
media.trimVideo(input)קיצוץ סרטון לטווח מסוים
media.trimAudio(input)קיצוץ אודיו, או חילוץ שלו מסרטון
media.splitMedia(input)חיתוך סרטון או קובץ אודיו לחלקים שהאורך שלהם בין מינימום למקסימום, או חיתוך סרטון לחלק אחד לכל שוט
media.addCaptions(input)צריבת כתוביות בסרטון
media.stillToVideo(input)הפיכת תמונה ורצועת אודיו לסרטון
media.slideshow(input)הפיכת תמונות, ואודיו אופציונלי, למצגת
media.videoOverlay(input)הצבת שכבות תמונה מתוזמנות מעל סרטון
media.imageCollage(input)סידור של 2–30 תמונות בקולאז׳ אחד
media.imageOverlay(input)הצבת שכבות של תמונה, טקסט, קוד QR וצורה על תמונה
media.videoMask(input)מעקב אחרי נושא שמתואר בטקסט וקבלת סרטון מסכה
media.maskComposite(input)הצבת סרטון אחד מעל סרטון אחר דרך סרטון מסכה, או פיקסול או טשטוש של האזור הממוסך
media.suggestOverlayPlacement(input)בקשה ממודל ראייה להציע איפה למקם שכבה

client.uploads

uploads.upload(file)‎

מעלה קובץ אחד (POST /v1/upload, בפורמט multipart) ומחזירה את הכתובת הציבורית שלו ואת פרטי האחסון. ה-SDK שולח את הקובץ כ-form data, ומשאיר לסביבת הריצה לקבוע את ה-boundary של ה-multipart.

upload(file: File): Promise<UploadResult>

מאפיין

סוג

const result = await client.uploads.upload(file)

const clip = await client.nodes.runAndWait("generate-video", {
  prompt: "The camera slowly pushes in",
  imageUrl: result.url,
})
שדהסוגתיאור
urlstringהכתובת הציבורית של הקובץ השמור. העבירו אותה בתור imageUrl, videoUrl או audioUrl.
assetIdstring | nullהמזהה של הפריט השמור, או null בהעלאה אנונימית.
thumbnailUrlstring | nullתמונה ממוזערת לתמונות ולווידאו, או null.
categorystringimage, video או audio.
filenamestringשם התצוגה של הקובץ.
mimeTypestringסוג המדיה שהשרת קבע. הוא יכול להיות שונה מהסוג שהדפדפן הצהיר עליו, למשל video/mp4 לקובץ .mp4 שנשלח כ-application/octet-stream.
sizeBytesnumberהגודל של הקובץ השמור.
r2Keystringהמפתח של הקובץ באחסון.

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

uploads.uploadJson(json)‎

שומרת מסמך JSON של עד 25 MB (POST /v1/upload-json) ומחזירה את { url }, הכתובת הציבורית שלו. אפשר לשמור כך, למשל, את מצב העיצוב של עורך תמונות: מעבירים את הכתובת שלו ל-jobs.fromUpload בתור editStateUrl, כדי שאפשר יהיה לפתוח את העריכה שוב.

uploadJson(json: Record<string, unknown> | readonly unknown[]): Promise<UploadJsonResult>

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

uploads.importUrl(url, options?)‎

מעתיקה תמונה מכתובת אינטרנט לאחסון שלכם ומחזירה אותה (POST /v1/media/import-url, עם גוף הבקשה { url, confirmationId? }). העותק השמור נחשב להעלאה שלכם, והוא מוגש כך שאלמנט <canvas> בדפדפן יכול לקרוא את הפיקסלים שלו, למשל כדי לחתוך אותו. תמונה באתר אחר לא מאפשרת את זה.

importUrl(url: string, options?: { confirmationId?: string }): Promise<ImportUrlResult>

מאפיין

סוג

הפלטפורמה שומרת את הקובץ רק אם אפשר לפענח אותו כתמונה: JPEG, PNG, WebP או GIF. תמונת HEIC, HEIF או AVIF מומרת ל-JPEG או ל-PNG.

try {
  const picture = await client.uploads.importUrl("https://example.org/photo.jpg")
  console.log(picture.url, picture.width, picture.height)
} catch (err) {
  if (err instanceof StorageExceededError && err.reason === "file_too_large") {
    // one picture over 20 MB, not a full storage
  }
}
שדהסוגתיאור
urlstringהכתובת הציבורית של התמונה השמורה.
thumbnailUrlstring | nullתמונה ממוזערת, או null כשאי אפשר היה ליצור אותה.
assetIdstring | nullהמזהה של הפריט השמור, או null כשלא נכתב פריט.
mimeTypestringסוג המדיה הסופי. מקור HEIC, HEIF או AVIF נשמר כ-JPEG או כ-PNG.
sizeBytesnumberהגודל של הקובץ השמור.
filenamestringהחלק האחרון בנתיב של הכתובת, או imported-<id>.<ext>.
widthnumberהרוחב בפיקסלים, או 0 כשאי אפשר היה לקרוא אותו.
heightnumberהגובה בפיקסלים, או 0 כשאי אפשר היה לקרוא אותו.
סטטוסשגיאהמתי
400NodaroError עם הקוד validation_errorהכתובת היא לא http או https, או שהיא כתובת IP פרטית או שמורה. הקובץ הוא לא תמונה שאפשר לפענח, או שהפורמט שלו לא מתקבל, כגון SVG או TIFF.
422NodaroError עם הקוד fetch_failedאי אפשר היה להגיע לכתובת, או שהיא החזירה שגיאה. גם שם מארח שמפנה לכתובת פרטית נכלל במקרה הזה.
403ImportRefusedError, שהיא גם ForbiddenErrorייבוא קישורים חסום בחשבון שלכם, או שהחשבון סגור, לפי מדיניות המפרים החוזרים של הפריסה (imports_blocked); הפריסה לא מאפשרת ייבוא מהאתר (import_source_disabled); או שאישור זכויות התוכן חסר כשהוא נדרש, שייך לקישור אחר או לחשבון אחר, לא עדכני, שתוקפו פג או שכבר נוצל (קוד rights_confirmation_*).
413StorageExceededError עם reason בערך file_too_largeהתמונה גדולה מ-20 MB.
413StorageExceededError עם reason בערך storage_limit_exceededנפח האחסון שלכם מלא. ב-details נמצאים usedBytes, quotaBytes, remainingBytes ו-tier.

רכיב העלאת תמונה משתמש באותו ייבוא כשמדביקים קישור לתמונה. כדי למחוק תמונה מיובאת שכבר אין לכם צורך בה, קראו ל-media.discard().

client.library

library.list(params?)‎

מחזירה את המדיה השמורה שלכם בעמודים, עם cursor (GET /v1/library). כברירת מחדל היא מחזירה את הפריטים ששמרתם בספרייה שלכם, ובנוסף פריטים ששותפו איתכם, כמו שחלון בחירת המדיה בעורך מציג אותם.

list(params?: ListLibraryParams): Promise<{ data: LibraryAsset[]; nextCursor: string | null; totalCount?: number }>

מאפיין

סוג

const { data: videos, nextCursor } = await client.library.list({ type: "video", limit: 20 })
for (const asset of videos) console.log(asset.filename, asset.url)
const { data, nextCursor } = await client.library.list({ type: "image", q: "poster" })

כל LibraryAsset כולל את id, type, filename, mimeType, sizeBytes, url, thumbnailUrl, metadata, isLibraryItem, uploadSource, source, sourceDetail, jobId ו-createdAt. השדה jobId הוא המשימה שיצרה את הקובץ, או null בהעלאה. שרתים מגרסאות ישנות יותר משמיטים אותו. השדה totalCount מופיע רק בעמוד הראשון.

client.gallery

הגלריה הציבורית של יצירות שהושלמו, והמועדפים שלכם. מועדף הוא סימון של יצירה: יצירה שלכם, או יצירה ציבורית של כל אחד אחר. במשימות שלכם הסימון מופיע בשדה Job.favorited, והמתודה client.jobs.list({ favorited: true }) מחזירה את המשימות המועדפות. בפריסה שמסתירה את הגלריה, הנתיבים האלה מחזירים 404.

gallery.browse(params?)‎

מחזירה עמוד אחד מהגלריה הציבורית, מהחדש לישן (GET /v1/gallery). לא צריך אסימון.

browse(params?: BrowseGalleryParams): Promise<GalleryPage>

מאפיין

סוג

const { data: items, nextCursor } = await client.gallery.browse({ type: "image", limit: 20 })

העמוד הוא { data: GalleryItem[], nextCursor, totalCount? }, והשדה totalCount מופיע רק בעמוד הראשון. כל פריט הוא { id, type, jobName, outputUrl, thumbnailUrl, createdAt, prompt, model, preview? }, והשדה id הוא מזהה המשימה. הפרמטר favoritesOnly עובד רק עם ה-userId שלכם, מ-client.me(). עם favoritesOnly ובלי אסימון, המתודה זורקת UnauthorizedError. עם userId של אדם אחר, או בלי userId, היא זורקת ForbiddenError.

gallery.toggleFavorite(jobId)‎

מסמנת את המשימה כמועדפת, או מסירה את הסימון כשהמשימה כבר מועדפת (POST /v1/gallery/favorite). השדה favorited מציין את המצב של המשימה אחרי הקריאה.

toggleFavorite(jobId: string): Promise<{ favorited: boolean }>
const { favorited } = await client.gallery.toggleFavorite(jobId)

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

gallery.favorites()‎

מחזירה את המזהים של המשימות שסימנתם כמועדפות, מהחדש לישן (GET /v1/gallery/favorites). אפליקציות OAuth צריכות את ההרשאה assets:read או jobs:read.

favorites(): Promise<string[]>
const ids = await client.gallery.favorites()

client.contentRights

המשאב client.contentRights קורא את ההצהרה שמי שמייבא קישור מאשר לפני הייבוא, ורושם את האישור שמצורף אחר כך לייבוא (/v1/content-rights). הכללים מפורטים בדף ה-API זכויות תוכן.

contentRights.declaration()‎

declaration(): Promise<{ version: number; text: string }>

מחזירה את ההצהרה הנוכחית (GET /v1/content-rights/declaration). הציגו את text כמו שהוא, עם תיבת סימון שמתחילה לא מסומנת. אישור מציין את ה-version. הקריאה לא דורשת פרטי גישה.

contentRights.confirm(input)‎

confirm(input: { url: string; declarationVersion: number }): Promise<{
  id: string
  declarationVersion: number
  expiresAt: string
}>

רושמת שהחשבון המחובר אישר את ההצהרה לקישור אחד (POST /v1/content-rights/confirm). אפשר לאשר כל קישור http(s) שייבוא מקבל: דף של סרטון, קובץ, תמונה או דף ששירות הדיבוב קורא. העבירו את ה-id שחזר בתור confirmationId ל-media.downloadVideo(), ל-uploads.importUrl() או לייבוא שמושך את הקישור, כגון voices.dub() ו-audio.transcribe(). אישור אחד פותח הורדה אחת של הקישור הזה, מאותו חשבון, תחת ההצהרה הנוכחית ובתוך שעה. תשובת 409 rights_confirmation_stale פירושה שההצהרה השתנתה אחרי שקיבלתם אותה: טענו אותה שוב ובקשו את האישור שוב.

const { version, text } = await client.contentRights.declaration()
// Show `text` with a checkbox. Once it is ticked:
const { id } = await client.contentRights.confirm({ url, declarationVersion: version })
const { downloadId } = await client.media.downloadVideo({ url, maxHeight: 1080, confirmationId: id })

הגדרה של הפריסה קובעת אם הורדה בלי אישור נדחית. כשההגדרה מופעלת, הורדה כזו נדחית עם 403 rights_confirmation_required. תמיד שלחו אישור.

contentRights.authorization()‎

authorization(): Promise<{ id: string; declarationVersion: number; createdAt: string } | null>

מחזירה את ההרשאה הקבועה של החשבון לייבוא דרך מפתחות ה-API, האפליקציות המחוברות וסוכני ה-AI שלו (GET /v1/content-rights/authorization). כשאין הרשאה כזו, המתודה מחזירה null: ההרשאה לא ניתנה מעולם, בוטלה או ניתנה להצהרה ישנה יותר.

contentRights.authorize(input)‎

authorize(input: { declarationVersion: number }): Promise<{
  id: string
  declarationVersion: number
  createdAt: string
}>

רושמת שבעל החשבון נתן הרשאה לייבוא דרך המפתחות, האפליקציות והסוכנים שלו, לגרסת ההצהרה שהוא ראה (POST /v1/content-rights/authorization). קראו לה מתוך הסשן המחובר של בעל החשבון עצמו. קריאה דרך מפתח, אפליקציה או סוכן נדחית עם 403 forbidden. תשובת 409 rights_confirmation_stale פירושה שההצהרה השתנתה: טענו אותה שוב ובקשו את ההרשאה שוב.

contentRights.revokeAuthorization()‎

revokeAuthorization(): Promise<{ revoked: boolean }>

מבטלת את ההרשאה הקבועה (DELETE /v1/content-rights/authorization). הרשומה נשמרת. השדה revoked הוא false כשלא היה מה לבטל. קראו לה מתוך הסשן של בעל החשבון עצמו.

client.media: ייבוא והכנה

media.downloadVideo(input)‎

מורידה לאחסון שלכם את הסרטון שמאחורי קישור: הדף של הסרטון או קישור ישיר לקובץ וידאו (POST /v1/download-video). המתודה מחזירה downloadId, ולא מזהה משימה. את ההתקדמות מקבלים עם downloadVideoProgress(). הקובץ המוגמר נשמר בספרייה שלכם.

downloadVideo(input: {
  url: string
  maxHeight?: number
  sectionStartSec?: number
  sectionEndSec?: number
  exactSection?: boolean
  requireAudio?: boolean
  confirmationId?: string
}): Promise<{ downloadId: string }>

מאפיין

סוג

const { downloadId } = await client.media.downloadVideo({
  url: "https://youtu.be/dQw4w9WgXcQ",
  maxHeight: 720,
})

media.downloadVideoProgress(downloadId, opts?)‎

מזרימה את ההתקדמות של הורדה בזמן אמת, כאיטרטור אסינכרוני שמקבל אירועי שרת (SSE) מ-GET /v1/download-video/progress/:id. היא מחזירה אירוע בערך כל 500 אלפיות שנייה, עד שההורדה מסתיימת או נכשלת, ואז האיטרטור מסתיים.

downloadVideoProgress(downloadId: string, opts?: { signal?: AbortSignal }): AsyncGenerator<DownloadVideoProgress>

מאפיין

סוג

for await (const event of client.media.downloadVideoProgress(downloadId)) {
  console.log(`${event.phase} ${event.percent}%`)
  if (event.phase === "completed") console.log("Stored at", event.videoUrl)
  if (event.phase === "failed") console.error(event.error)
}

כל אירוע הוא { phase, percent, videoUrl?, thumbnailUrl?, error? }. התחילו לעבור על האירועים זמן קצר אחרי ש-downloadVideo() מחזירה תשובה, כי רשומת ההתקדמות פגה זמן קצר אחרי שההורדה מסתיימת. ה-timeoutMs של הלקוח לא חל כאן, כי הורדות גדולות נמשכות כמה דקות.

media.videoMetadata(input)‎

מחזירה את האורך, הממדים והכותרת של סרטון מרשת חברתית, וגם אם הוא שידור חי, בלי להוריד אותו (POST /v1/video-metadata). התשובה מגיעה ישירות, בלי משימה. השתמשו בה כדי להחליט אם להוריד רק חלק מהסרטון.

videoMetadata(input: { url: string }): Promise<VideoMetadata>

מאפיין

סוג

const meta = await client.media.videoMetadata({ url: "https://youtu.be/dQw4w9WgXcQ" })

השדות לא מובטחים: פלטפורמה מסוימת עשויה לא לדווח על כולם.

media.saveToStorage(input)‎

מעתיקה קובץ מדיה מכתובת URL לאחסון שלכם ב-Nodaro (POST /v1/save-to-storage). השרת מושך את הקובץ, ולכן שום דבר לא עובר דרך הלקוח שלכם.

saveToStorage(input: { mediaUrl: string; filename?: string; mediaType?: "image" | "video" | "audio" }): Promise<{ jobId: string }>

מאפיין

סוג

const { jobId } = await client.media.saveToStorage({ mediaUrl: "https://example.com/intro.mp4", mediaType: "video" })

רכיב שמירה לאחסון עושה את אותו הדבר בתוך תהליך.

media.process(input)‎

מקצצת או חותכת קובץ שכבר נמצא באחסון שלכם (POST /v1/media/process). התשובה מגיעה ישירות, והקריאה לא עולה כלום. השתמשו בה כדי להכין מקור לפני שלב בתשלום.

process(input: {
  sourceUrl: string
  type: "video" | "audio"
  trim?: { startTime: number; endTime: number }
  crop?: { x: number; y: number; width: number; height: number }
  format?: "mp4" | "webm" | "mp3" | "wav" | "m4a" | "aac"
  deleteSource?: boolean
}, options?: { timeoutMs?: number; signal?: AbortSignal }): Promise<{ data: MediaProcessResult }>

מאפיין

סוג

const { data } = await client.media.process({
  sourceUrl: stored.url,
  type: "video",
  trim: { startTime: 12, endTime: 42 },
})
console.log(data.url, data.sizeBytes)

התוצאה כוללת את url, thumbnailUrl, assetId, sizeBytes, mimeType ו-metadata.

media.discard(input)‎

מוחקת קובץ שיצרתם זה עתה ושכבר אין לכם צורך בו (POST /v1/media/discard). למשל, הורדה מקישור שהחלפתם לפני שהשתמשתם בה, קטע מקוצץ שהוחלף בקטע חדש יותר ותמונה שייבאתם ואז ביטלתם.

discard(input: { url: string }): Promise<{ data: { discarded: boolean } }>

מאפיין

סוג

המתודה discard() לא מבצעת מחיקה כללית. הקובץ נמחק רק כשכל התנאים האלה מתקיימים:

  • הקובץ הוא שלכם.
  • הקובץ נוצר ב-downloadVideo(), ב-process() או בייבוא מכתובת URL, כגון uploads.importUrl().
  • הקובץ נוצר לפני פחות מ-24 שעות.
  • אף משימה שלכם לא משתמשת בקובץ, לא כפלט ולא כקלט, כגון ה-videoUrl של הרצה.

במקרה כזה התשובה היא { data: { discarded: true } }. אחרת שום דבר לא משתנה, והקריאה נכשלת עם שגיאת 409 שהקוד שלה הוא not_discardable. הערך discarded: false פירושו שבכתובת הזו לא היה קובץ שלכם: הקובץ כבר נמחק, או שהוא לא שלכם.

await client.media.discard({ url: oldCut.url })

media.trimVideo(input)‎

מקצצת סרטון לטווח מסוים (POST /v1/trim-video), כמו רכיב קיצוץ וידאו. אפשר לציין את הטווח בכל יחידה שנוחה לכם.

trimVideo(input: {
  videoUrl: string
  startTime?: number
  endTime?: number
  trimStartFrames?: number
  trimEndFrames?: number
  trimStartSeconds?: number
  trimEndSeconds?: number
  keepFirstSeconds?: number
  keepLastSeconds?: number
}): Promise<{ jobId: string }>

מאפיין

סוג

const { jobId } = await client.media.trimVideo({ videoUrl, keepFirstSeconds: 15 })

media.trimAudio(input)‎

מקצצת אודיו לטווח מסוים, או מחלצת אותו מסרטון (POST /v1/trim-audio), כמו רכיב קיצוץ אודיו.

trimAudio(input: {
  videoUrl?: string
  audioUrl?: string
  audioFormat?: "mp3" | "wav" | "aac"
  startTime?: number
  endTime?: number
}): Promise<{ jobId: string }>

מאפיין

סוג

const { jobId } = await client.media.trimAudio({ videoUrl, startTime: 5, endTime: 35 })

media.splitMedia(input)‎

חותכת סרטון, קובץ אודיו או את שניהם לחלקים רצופים (POST /v1/split-media), כמו רכיב פיצול למקטעים. כל קלט מפוצל בנפרד.

splitMedia(input:
  | {
      videoUrl?: string
      audioUrl?: string
      mode?: "duration"
      chunkDuration: number
      minPartDuration?: number
      audioFormat?: "mp3" | "wav" | "aac"
    }
  | { videoUrl: string; mode: "shots" }
): Promise<{ jobId: string }>

מאפיין

סוג

הרצה עולה 22 קרדיטים, לא משנה כמה חלקים נוצרים. בפלט של המשימה המוגמרת נמצאים videoUrls ו-audioUrls (החלקים לפי הסדר), chunkCount ו-partDurationsSec (האורך המדויק של כל חלק). כששני הקלטים פוצלו, השדה audioPartDurationsSec מפרט את האורכים של חלקי האודיו. קובץ יכול להיות באורך של עד 3 שעות, ולהתפצל ל-200 חלקים לכל היותר.

const { jobId } = await client.media.splitMedia({ videoUrl, chunkDuration: 60, minPartDuration: 30 })

עם mode: "shots", הסרטון נחתך במקום זאת לחלק אחד לכל שוט, בנקודות החיתוך שנמדדות מהקובץ. המצב הזה לא מקבל אורך חלק, ומקבל רק סרטון: שליחה של audioUrl נפרד נדחית עם 400. כל חלק שומר על האודיו של הסרטון עצמו. סרטונים יכולים להיות באורך של עד 30 דקות, עם 200 שוטים לכל היותר, באותו מחיר. הפלט כולל אז גם את shotCount ואת shots, שכל פריט בו הוא { index, startSec, endSec, durationSec }.

const { jobId } = await client.media.splitMedia({ videoUrl: "https://example.com/ad.mp4", mode: "shots" })

client.media: כתוביות

media.addCaptions(input)‎

צורבת כתוביות בסרטון (POST /v1/add-captions), כמו רכיב הוספת כתוביות. אפשר לשלוח את המילים ב-text, לשלוח captions מתוזמנות לפי מילה, או לתת ל-Nodaro לתמלל את הדיבור, וזו ברירת המחדל.

addCaptions(input: AddCaptionsInput): Promise<{ jobId: string }>

מאפיין

סוג

const { jobId } = await client.media.addCaptions({
  videoUrl: "https://example.com/talk.mp4",
  style: "word-highlight",
  maxWordsPerLine: 2,
})

מאיפה המילים מגיעות. כשבקריאה יש כמה מקורות, captions המתוזמנות לפי מילה גוברות, אחריהן text בסגנון subtitle, ורק אחר כך התמלול. בסגנון subtitle, השדה text הוא הכתובית: הוא נצרב כגוש סטטי אחד לכל אורך הסרטון, ותמלול אף פעם לא מחליף אותו. כתבו בו \n כדי לכפות מעבר שורה. בסגנון מונפש, השדה text הוא רק חלופה. הוא משמש כשהתמלול לא מוצא כלום או כש-autoTranscribe הוא false, ואז המילים שלו מתחלקות באופן שווה לאורך הסרטון.

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

אפשרויות עיצוב בסגנון subtitle. אפשרויות העיצוב (look, fontFamily, fontWeight, strokeColor, strokeWidth, uppercase, positionY ו-maxWordsPerLine) עובדות גם בסגנון subtitle. על כתובית עם אחת מהן משלמים את המחיר של סגנון מונפש, ועל כתובית של טקסט פשוט משלמים את המחיר הנמוך יותר. האפשרויות highlightColor ו-animate קיימות רק בסגנונות מונפשים, ובסגנון subtitle הן נדחות עם 400. גם על כתובית שנוצרה מתמלול אוטומטי משלמים את המחיר של סגנון מונפש.

שורות. הסגנונות word-highlight, karaoke ו-bouncy מציגים שורה אחת בכל פעם. שורה מסתיימת בסוף משפט, בהפסקה של 0.5 שנייה או יותר, כשהיא ממלאת כ-85% מרוחב הפריים או כשהיא מגיעה ל-maxWordsPerLine מילים. הסגנון word-pop מציג מילה אחת בכל פעם, ולכן ל-maxWordsPerLine אין בו השפעה. עמוד של tiktok-words אף פעם לא חוצה סוף משפט או הפסקה. גם השורות וגם העמודים נשארים על המסך 1.5 שניות לכל היותר אחרי המילה האחרונה שנאמרה. השדות startMs ו-endMs של מילה מתזמנים את האפקט שלה, לא את משך הזמן שהיא נשארת על המסך.

מנועי תמלול. סגנון מונפש צריך תזמון לכל מילה, ולכן הערך של transcribeProvider חייב להיות incredibly-fast-whisper, ברירת המחדל כאן, או elevenlabs-stt. למנוע whisper אין תזמון לכל מילה. הוא נדחה עם 400 validation_error רק כשהתמלול הוא המקור האפשרי היחיד של המילים. בסגנון subtitle כל מנוע עובד, כי כתובית צריכה רק תזמון ברמת המשפט.

קצב פריימים. רינדור מעוצב שומר על קצב הפריימים של סרטון המקור, מעוגל למספר שלם בין 15 ל-60. מקור עם קצב פריימים משתנה, או קליפ ארוך מדי למגבלת הפריימים, מרונדר ב-30 פריימים לשנייה.

תמלול, תיקון ואז צריבה

לשדה words של משימת client.audio.transcribe() יש בדיוק המבנה שהשדה captions מקבל. תקנו מילה, ואז צרבו את הכתוביות בלי תמלול נוסף:

import type { TranscribeJobOutput } from "@nodaro/sdk"

const { jobId } = await client.audio.transcribe({
  audioUrl: "https://example.com/talk.mp3",
  provider: "elevenlabs-stt", // always returns word timings
})
// ...poll until the job completes, then:
const { data: job } = await client.jobs.get(jobId)
const { words = [] } = job.output_data as TranscribeJobOutput

const corrected = words.map((w, i) => (i === 7 ? { ...w, text: "Nodaro" } : w))
await client.media.addCaptions({
  videoUrl: "https://example.com/talk.mp4",
  captions: corrected,
  autoTranscribe: false,
  style: "word-highlight",
})

client.media: רינדור ושילוב שכבות

media.stillToVideo(input)‎

הופכת תמונת סטילס אחת ורצועת אודיו אחת לקובץ MP4 (POST /v1/still-to-video), כמו רכיב תמונה נייחת לווידאו. הסרטון מרונדר בשרת בלי מודל AI, והקריאה לא עולה קרדיטים. הסרטון ארוך בדיוק כמו האודיו, ואין שדה לאורך.

stillToVideo(input: {
  imageUrl: string
  audioUrl: string
  motion?: "none" | "zoom-in" | "zoom-out" | "pan-left" | "pan-right" | "ken-burns"
  intensity?: number
  resolution?: "720p" | "1080p" | "4K"
  aspectRatio?: "16:9" | "9:16" | "1:1" | "4:3"
  fps?: 24 | 30
  fit?: "cover" | "contain"
  padColor?: string
}): Promise<{ jobId: string }>

מאפיין

סוג

const { jobId } = await client.media.stillToVideo({ imageUrl: coverUrl, audioUrl: podcastUrl, motion: "ken-burns" })

media.slideshow(input)‎

הופכת 2–100 תמונות ורצועת אודיו אופציונלית למצגת בקובץ MP4 (POST /v1/slideshow), כמו רכיב מצגת. הקריאה לא עולה קרדיטים. לתמונה אחת, קראו ל-stillToVideo().

slideshow(input: {
  imageUrls: string[]
  audioUrl?: string
  imageDurations?: Array<number | null>
  perImageDuration?: number
  transition?: string
  transitionDuration?: number
  motion?: "none" | "zoom-in" | "zoom-out" | "ken-burns" | "alternate"
  intensity?: number
  resolution?: "720p" | "1080p" | "4K"
  aspectRatio?: "16:9" | "9:16" | "1:1" | "4:3"
  fps?: 24 | 30
  fit?: "cover" | "contain"
  padColor?: string
}): Promise<{ jobId: string }>

מאפיין

סוג

const { jobId } = await client.media.slideshow({ imageUrls: frames, audioUrl: musicUrl, motion: "alternate" })

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

media.videoOverlay(input)‎

מציבה 1–20 שכבות תמונה מתוזמנות מעל סרטון במעבר אחד (POST /v1/video-overlay), כמו רכיב שכבה על וידאו. הרצה עולה 22 קרדיטים, לא משנה כמה שכבות יש ומה האורך. האודיו של הסרטון עצמו נשמר כמו שהוא.

videoOverlay(input: VideoOverlayRequest): Promise<{ jobId: string }>

מאפיין

סוג

כל שכבה מקבלת imageUrl ו-start, בשניות מ-0 עד 3,600, ו-end אופציונלי. בלי end, השכבה נשארת עד סוף הסרטון. הערך של preset הוא card, corner-badge או full-frame. שדות התיבה x ו-y נעים מ--100 עד 100, והשדות width ו-height מ-1 עד 100, כולם באחוזים מפריים הפלט. שדה תיבה שמוגדר במפורש גובר על preset. שכבה בלי אף אחד מהם היא תג פינה, למטה מימין או בפינה שמצוינת ב-corner. השדה opacity נע מ-0 עד 1, השדה animate מופעל כברירת מחדל, והשדה zIndex נע מ-0 עד 100.

const { jobId } = await client.media.videoOverlay({
  videoUrl,
  layers: [
    { imageUrl: logoUrl, start: 0, preset: "corner-badge", corner: "top-right" },
    { imageUrl: offerCardUrl, start: 8, end: 14, preset: "card" },
  ],
})

בפלט של המשימה המוגמרת נמצאים videoUrl, thumbnailUrl, width, height, durationSec ו-warnings. כל אזהרה היא { layer?, slot?, code, detail }, עם קוד כגון clipped, skipped, animated_first_frame או audio_reencoded.

media.imageCollage(input)‎

מסדרת 2–30 תמונות בקולאז׳ אחד גדול, ב-2K או ב-4K (POST /v1/image-collage), כמו רכיב קולאז׳ תמונות.

imageCollage(input: {
  imageUrls: string[]
  imageSizes?: Array<0 | 1 | 2 | 3>
  numbered?: boolean
  imageLabels?: Array<string | null>
  badgePosition?: "top-left" | "top-right"
  layout?: "smart" | "grid"
  resolution?: "2K" | "4K"
  aspectRatio?: string
  gap?: number
  backgroundColor?: string
}): Promise<{ jobId: string }>

מאפיין

סוג

const { jobId } = await client.media.imageCollage({
  imageUrls: shotUrls,
  numbered: true,
  imageLabels: ["Wide", "Medium", "Close-up"],
})

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

media.imageOverlay(input)‎

מציבה 1–12 שכבות על תמונת בסיס, בדיוק ברמת הפיקסל (POST /v1/image-overlay), כמו רכיב שכבת תמונה. המתודה משלבת שכבות בלי מודל AI. הקריאה עולה 11 קרדיטים, וכל גודל נוסף של פלטפורמה ב-variants מוסיף למחיר.

imageOverlay(input: {
  imageUrl: string
  layers: Array<{
    kind?: "image" | "text" | "qr" | "shape"
    imageUrl?: string
    anchor?: "top-left" | "top" | "top-right" | "left" | "center" | "right" | "bottom-left" | "bottom" | "bottom-right"
    x?: number
    y?: number
    width?: number
    height?: number
    opacity?: number
    rotation?: number
    blend?: "over" | "multiply" | "screen"
    fit?: "contain" | "cover" | "stretch"
    shadow?: { blur: number; offsetX: number; offsetY: number; color: string; opacity: number }
    roundedCorners?: number
    zIndex?: number
    text?: OverlayTextStyle
    qr?: OverlayQrStyle
    shape?: OverlayShapeStyle
    effects?: OverlayImageEffects
  }>
  canvas?: { width: number; height: number; backgroundColor?: string }
  baseFit?: "contain" | "cover"
  outputFormat?: "png" | "jpg" | "webp"
  variants?: string[]
  maskMode?: "none" | "layers" | "around" | "outside"
  maskSpread?: number
  qrText?: string
}): Promise<{ jobId: string }>

מאפיין

סוג

שכבות. לכל שכבה יש אחד מארבעה סוגים:

  • תמונה: הערך kind: "image", ברירת המחדל, עם imageUrl.
  • טקסט אמיתי: הערך kind: "text" עם אובייקט text. האובייקט קובע את התוכן, הגופן, העובי, הצבע, היישור, קו המתאר ותיבת הרקע, וגם גודל באחוזים מגובה הבסיס.
  • קוד QR: הערך kind: "qr" עם qr: { text }.
  • צורה שטוחה: הערך kind: "shape" עם shape: { shape, color }.

המיקומים הם באחוזים מתמונת הבסיס, ולכן אותה קריאה עובדת על תצוגה מקדימה ב-1K ועל רינדור ב-4K:

  • השדה anchor הוא אחד מתשעה מיקומים, center כברירת מחדל.
  • השדות x ו-y מזיזים את השכבה מהעוגן, באחוזים מהרוחב ומהגובה של הבסיס. בעוגן ימני או תחתון, ערך שלילי מזיז אותה פנימה.
  • השדה width הוא באחוזים מרוחב הבסיס, 25 כברירת מחדל. הגובה נקבע לפי יחס הגובה־רוחב של השכבה, אלא אם מגדירים height, ואז השדה fit קובע איך השכבה ממלאת את התיבה.
  • השדה opacity נע מ-0 עד 1, השדה rotation הוא במעלות סביב מרכז השכבה והשדה blend הוא over (ברירת המחדל), multiply או screen.
  • השדה shadow מוסיף צל רך, השדה roundedCorners מעגל את הפינות בפיקסלים והשדה zIndex קובע את סדר השכבות.
const { jobId } = await client.media.imageOverlay({
  imageUrl: productShotUrl,
  layers: [{ imageUrl: logoUrl, anchor: "bottom-right", x: -4, y: -6, width: 12, opacity: 0.95 }],
  variants: ["youtube-thumbnail"],
})

בפלט של המשימה המוגמרת נמצאים imageUrl, ה-width וה-height שלה, maskUrl ו-variants, שכל פריט בו הוא { id, label, width, height, url }. שכבות SVG מצוירות בחדות בגודל היעד. שכבת QR עם qr.fromInput: true לוקחת את התוכן שלה מ-qrText, והרצה בלי qrText נדחית עם שגיאת 400 שמציינת אותו.

media.videoMask(input)‎

עוקבת אחרי נושא שמתואר בטקסט פשוט לאורך סרטון, ומחזירה סרטון מסכה בשחור־לבן, שבו הנושא לבן (POST /v1/video-mask), כמו רכיב מסכת וידאו.

videoMask(input: {
  videoUrl: string
  prompt: string
  threshold?: number
  provider?: "sam-3.1" | "sam-3"
  holdLastMask?: boolean
  minSubjectHeightPct?: number
  maxSubjectHeightPct?: number
  zone?: { leftPct?: number; rightPct?: number; topPct?: number; bottomPct?: number } | null
  perShot?: boolean
  redetectEverySec?: number
}): Promise<{ jobId: string }>

מאפיין

סוג

המחיר נקבע לפי אורך הקליפ: ב-SAM 3.1 הוא נע מ-55 עד 3,108 קרדיטים. קליפ בקצב של יותר מ-30 פריימים לשנייה נספר כארוך יותר, באופן יחסי. בפלט של המשימה המוגמרת נמצאים videoUrl (המסכה), width, height, frames, chunks, shots, coverage, kept ו-recipe. השדה chunks סופר את הבקשות שנשלחו למודל. השדה shots הוא 1, אלא אם נמצאו חיתוכים במצב perShot. השדה recipe הוא הגרסה של שיטת המיסוך, והוא משתנה בכל פעם שאותו קליפ עם אותן הגדרות היה נותן מסכה אחרת. השדה kept הוא false רק כשמסנני הגודל והאזור לא השאירו כלום באף פריים. בדקו את kept ולא את coverage, שמעוגל ל-4 ספרות אחרי הנקודה.

media.maskComposite(input)‎

מציבה סרטון חזית מעל סרטון רקע דרך סרטון מסכה (POST /v1/mask-composite), כמו רכיב שילוב לפי מסכה. לבן במסכה מציג את החזית. עם fill: "pixelate" או "blur", האזור הממוסך מציג את הרקע עצמו, מפוקסל או מטושטש, ולא צריך חזית.

maskComposite(input: {
  foregroundUrl?: string
  backgroundUrl: string
  maskUrl: string
  invertMask?: boolean
  feather?: number
  grow?: number
  opacity?: number
  fill?: "video" | "pixelate" | "blur"
  pixelSizePct?: number
  blurStrengthPct?: number
  backgroundFit?: "cover" | "contain" | "stretch"
  audioSource?: "foreground" | "background" | "mix" | "none"
}): Promise<{ jobId: string }>

מאפיין

סוג

הרצה עולה 11 קרדיטים לכל 5 שניות של התוצאה, או חלק מהן.

const { jobId: maskJobId } = await client.media.videoMask({ videoUrl: talkUrl, prompt: "the person" })
// Poll the mask job. Its output videoUrl is the mask.
const { jobId } = await client.media.maskComposite({ foregroundUrl: talkUrl, backgroundUrl: beachUrl, maskUrl })

media.suggestOverlayPlacement(input)‎

שואלת מודל ראייה איפה למקם שכבה אחת על תמונת בסיס (POST /v1/image-overlay/suggest-placement). המודל מרחיק את השכבה מפנים, מהנושא המרכזי ומטקסטורות עמוסות. התשובה מגיעה ישירות, בלי משימה שצריך לבדוק את הסטטוס שלה, והקריאה מתומחרת כמו קריאה אחת של רכיב תיאור תמונה. שום שכבה לא משולבת בתמונה: את המיקום מחילים בעצמכם.

suggestOverlayPlacement(input: {
  imageUrl: string
  layerAspect?: number
  intent?: string
  safeArea?: { x: number; y: number; w: number; h: number }
  llmModel?: string
}): Promise<{ jobId: string; placement: OverlayPlacement }>

מאפיין

סוג

const { placement } = await client.media.suggestOverlayPlacement({
  imageUrl: baseUrl,
  intent: "a logo",
  layerAspect: 2.5,
})
const { reason, ...box } = placement // anchor, x, y and width, in imageOverlay's units
await client.media.imageOverlay({ imageUrl: baseUrl, layers: [{ imageUrl: logoUrl, ...box }] })

ב-placement נמצאים anchor, x, y ו-width, ביחידות האחוזים של imageOverlay, ובנוסף reason, משפט אחד שאפשר להציג למשתמש. השדה jobId הוא רשומת החיוב.

שאלות נפוצות

איך מעלים קובץ עם ה-SDK של Nodaro?

קראו ל-client.uploads.upload(file)‎ עם אובייקט File. המתודה מחזירה כתובת ציבורית, ואפשר להעביר אותה לכל רכיב שמקבל כתובת של תמונה, של סרטון או של אודיו.

איך מוסיפים כתוביות לסרטון מקוד?

קראו ל-client.media.addCaptions עם videoUrl ועם style. כשאין text ואין captions, Nodaro מתמלל את הדיבור. הקריאה מחזירה jobId, ובמשימה המוגמרת נמצא הסרטון עם הכתוביות.

אילו מתודות מדיה לא עולות קרדיטים?

המתודות stillToVideo ו-slideshow מרנדרות בלי מודל AI ולא עולות קרדיטים, וגם המתודה media.process לא עולה כלום. המתודה videoOverlay עולה 22 קרדיטים להרצה, והמתודה imageOverlay עולה 11 קרדיטים, וכל גודל נוסף של פלטפורמה מוסיף למחיר שלה.

איך מסמנים יצירה כמועדפת מקוד?

קראו ל-client.gallery.toggleFavorite(jobId)‎. המתודה מסמנת את המשימה כמועדפת, או מסירה את הסימון כשהמשימה כבר מועדפת. את המועדפים שלכם מקבלים עם client.gallery.favorites()‎ או עם client.jobs.list({ favorited: true })‎.

איך בודקים את ההתקדמות של הורדת סרטון?

המתודה client.media.downloadVideo מחזירה downloadId. עברו בלולאה על client.media.downloadVideoProgress(downloadId)‎ כדי לקבל את השלב ואת האחוז, עד שההורדה מסתיימת או נכשלת.

עודכן לאחרונה

בדף הזה