미디어 및 업로드
Nodaro SDK로 TypeScript에서 파일을 업로드하고, 미디어 라이브러리를 나열하며, 소셜 동영상 다운로드, 클립 자르기, 자막 입히기, 이미지와 동영상 합성을 처리합니다.
client.uploads는 내 파일을 Nodaro에 저장하고, client.library는 이미 가진 미디어를 나열하고, client.gallery는 공개 갤러리와 내 즐겨찾기를 둘러보며, client.media는 미디어를 준비하고 편집합니다. 소셜 동영상을 다운로드하고, 클립을 자르고, 자막을 입히고, 이미지와 동영상을 합성합니다. 대부분의 client.media 메서드는 작업을 시작하고 그 jobId를 반환하며, 이 작업은 client.jobs.getStatus()로 폴링합니다. 이 메서드들은 업로드 REST API와 음성 및 미디어 REST API의 엔드포인트를 호출합니다.
메서드
client.uploads
uploads.upload(file)
파일 하나를 업로드하고(POST /v1/upload, 멀티파트) 공개 URL과 저장 정보를 반환합니다. SDK는 파일을 폼 데이터로 보내며, 멀티파트 경계는 런타임이 설정하도록 맡깁니다.
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,
})| 필드 | 타입 | 설명 |
|---|---|---|
url | string | 저장된 파일의 공개 URL입니다. imageUrl, videoUrl, audioUrl로 전달하세요. |
assetId | string | null | 저장된 항목의 ID이며, 익명 업로드이면 null입니다. |
thumbnailUrl | string | null | 이미지와 동영상의 썸네일이며, 없으면 null입니다. |
category | string | image, video, audio 중 하나입니다. |
filename | string | 표시되는 파일 이름입니다. |
mimeType | string | 서버가 확정한 미디어 타입입니다. 브라우저가 선언한 타입과 다를 수 있습니다. 예를 들어 application/octet-stream으로 보낸 .mp4 파일은 video/mp4가 됩니다. |
sizeBytes | number | 저장된 크기입니다. |
r2Key | string | 파일의 저장 키입니다. |
저장 공간이 가득 차면 StorageExceededError를 발생시킵니다. 이미지 업로드, 동영상 업로드, 오디오 업로드 노드도 같은 저장 공간을 씁니다.
client.library
library.list(params?)
저장된 내 미디어를 커서와 함께 나열합니다(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는 작업 ID입니다. favoritesOnly는 client.me()에서 얻은 내 userId와 함께 쓸 때만 작동합니다. 토큰이 없으면 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()
즐겨찾기한 작업 ID를 최신순으로 반환합니다(GET /v1/gallery/favorites). OAuth 앱에는 assets:read 또는 jobs:read 스코프가 필요합니다.
favorites(): Promise<string[]>const ids = await client.gallery.favorites()client.media: 가져오기 및 준비
media.downloadVideo(input)
YouTube, TikTok, Instagram, X, Facebook의 동영상을 내 저장 공간에 다운로드합니다(POST /v1/download-video). 작업 ID가 아닌 downloadId를 반환합니다. downloadVideoProgress()로 진행 상황을 따라가세요. 완료된 파일은 내 라이브러리에 들어갑니다.
downloadVideo(input: {
url: string
maxHeight?: number
sectionStartSec?: number
sectionEndSec?: number
requireAudio?: boolean
}): Promise<{ downloadId: string }>속성
타입
const { downloadId } = await client.media.downloadVideo({
url: "https://youtu.be/dQw4w9WgXcQ",
maxHeight: 720,
})media.downloadVideoProgress(downloadId, opts?)
다운로드의 실시간 진행 상황을 비동기 이터레이터로 스트리밍합니다(GET /v1/download-video/progress/:id, 서버 전송 이벤트). 다운로드가 완료되거나 실패할 때까지 약 500ms마다 이벤트를 하나씩 내보낸 다음 끝납니다.
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
}): 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.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
chunkDuration: number
minPartDuration?: number
audioFormat?: "mp3" | "wav" | "aac"
}): Promise<{ jobId: string }>속성
타입
실행 비용은 구간 수와 관계없이 22크레딧입니다. 완료된 작업의 출력에는 순서대로 담긴 구간인 videoUrls와 audioUrls, chunkCount, 그리고 각 구간의 정확한 길이인 partDurationsSec가 있습니다. 두 입력을 모두 분할했다면 audioPartDurationsSec에 오디오 구간의 길이가 나열됩니다. 파일 하나는 최대 3시간까지이며, 구간은 최대 200개까지 만들 수 있습니다.
const { jobId } = await client.media.splitMedia({ videoUrl, chunkDuration: 60, minPartDuration: 30 })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가 가장 우선이고, 그다음은 subtitle 스타일의 text, 그다음은 음성 텍스트 변환입니다. 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 사이의 정수로 반올림해 유지합니다. 가변 프레임 레이트 소스나 프레임 한도를 넘는 긴 클립은 30fps로 렌더링됩니다.
음성 변환, 수정, 자막 입히기
client.audio.transcribe() 작업의 words는 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과 03,600초 범위의 100, start, 그리고 선택 사항인 end를 받습니다. end가 없으면 동영상이 끝날 때까지 남습니다. preset은 card, corner-badge, full-frame 중 하나입니다. 상자 필드인 x와 y는 -100width와 height는 1100이며, 모두 출력 프레임에 대한 퍼센트입니다. 명시적인 상자 필드는 프리셋보다 우선합니다. 둘 다 없는 레이어는 오른쪽 아래 또는 1이고, corner로 지정한 위치의 코너 배지가 됩니다. opacity는 0animate는 기본적으로 켜져 있으며, 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을 씁니다. - 실제 텍스트:
text객체와 함께kind: "text"를 씁니다. 내용, 글꼴, 굵기, 색, 정렬, 외곽선, 배경 상자, 그리고 기본 이미지 높이에 대한 퍼센트 크기를 설정합니다. - QR 코드:
qr: { text }와 함께kind: "qr"을 씁니다. - 단색 도형:
shape: { shape, color }와 함께kind: "shape"를 씁니다.
위치는 기본 이미지에 대한 퍼센트입니다. 그래서 같은 호출이 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, 그리고 각각 { id, label, width, height, url } 형식인 variants가 있습니다. SVG 레이어는 목표 크기에서 선명하게 그려집니다. qr.fromInput: true인 QR 레이어는 qrText에서 내용을 가져오며, qrText가 없는 실행은 그 필드 이름을 밝힌 400으로 거부됩니다.
media.videoMask(input)
동영상 마스크 노드처럼 일반 텍스트로 설명한 대상을 동영상 전체에서 추적해, 대상이 있는 곳은 흰색인 흑백 마스크 동영상을 반환합니다(POST /v1/video-mask).
videoMask(input: {
videoUrl: string
prompt: string
threshold?: number
provider?: "sam-3.1" | "sam-3"
}): Promise<{ jobId: string }>속성
타입
가격은 클립 길이에 따라 달라지며, SAM 3.1에서는 55~3,108크레딧입니다. 30fps보다 빠른 클립은 그 비율만큼 더 길게 계산됩니다. 완료된 작업의 출력에는 videoUrl(마스크), width, height, frames, coverage가 있습니다.
media.maskComposite(input)
마스크 합성 노드처럼 마스크 동영상을 통해 전경 동영상을 배경 동영상 위에 배치합니다(POST /v1/mask-composite). 마스크의 흰색 부분에 전경이 보입니다.
maskComposite(input: {
foregroundUrl: string
backgroundUrl: string
maskUrl: string
invertMask?: boolean
feather?: number
grow?: number
backgroundFit?: "cover" | "contain" | "stretch"
audioSource?: "foreground" | "background" | "mix" | "none"
}): Promise<{ jobId: string }>속성
타입
실행 비용은 결과의 시작된 5초마다 11크레딧입니다.
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). 모델은 레이어를 얼굴, 주요 피사체, 복잡한 질감에서 떨어뜨려 놓습니다. 폴링할 작업 없이 바로 응답하며, 이미지 설명 호출 1회로 청구됩니다. 합성은 하지 않으므로 배치는 직접 적용하세요.
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에는 imageOverlay의 퍼센트 단위로 된 anchor, x, y, width와 함께, 사용자에게 보여 줄 수 있는 한 문장짜리 reason이 있습니다. jobId는 청구 기록입니다.
자주 묻는 질문
Nodaro SDK로 파일을 업로드하려면 어떻게 하나요?
File을 넣어 client.uploads.upload(file)을 호출하세요. 이미지, 동영상, 오디오 URL을 받는 모든 노드에 전달할 수 있는 공개 url을 반환합니다.
코드에서 동영상에 자막을 추가하려면 어떻게 하나요?
videoUrl과 style을 넣어 client.media.addCaptions를 호출하세요. 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)를 순회하면 다운로드가 완료되거나 실패할 때까지 단계와 퍼센트를 받을 수 있습니다.
관련 문서
보이스와 오디오
작업과 실행
업로드
보이스 및 미디어
자막 추가
최종 업데이트