편집
TypeScript로 팟캐스트와 긴 동영상을 편집합니다. 호출 한 번으로 에피소드를 다듬고, 무음을 감지하고, 녹음을 동기화하고, 컷을 계획하고, 편집 결정 목록을 렌더링합니다.
client.edit에는 팟캐스트와 긴 동영상을 위한 편집 도구가 들어 있습니다. 호출 한 번으로 에피소드 전체를 다듬고, 무음을 찾고, 여러 녹음이 서로 얼마나 어긋나 있는지 측정합니다. 또한 스크립트로 컷을 계획하고, 편집 결정 목록(EDL)을 완성된 동영상 파일이나 오디오 파일로 렌더링합니다. 대부분의 메서드는 작업을 시작하고 그 ID를 반환하며, 이 작업은 client.jobs.getStatus()로 폴링합니다. estimateTightenEpisode()는 실행 가격을 견적하고, remapTranscript()는 요청 없이 로컬에서 실행됩니다.
메서드
| 메서드 | 엔드포인트 | 기능 |
|---|---|---|
edit.tightenEpisode(input, options?) | POST /v1/tighten-episode | 호출 한 번으로 녹음 하나를 완성된 에피소드로 다듬기 |
edit.estimateTightenEpisode(input) | POST /v1/tighten-episode/estimate | 에피소드 다듬기 실행 가격 견적. 아무것도 만들거나 청구하지 않음 |
edit.continueUseCase(input, options?) | POST /v1/tighten-episode/continue | 에피소드 다듬기 실행을 새 작업으로 이어 가기 |
edit.silenceDetect(input) | POST /v1/silence-detect | 오디오 소스나 동영상 소스의 무음 구간 찾기 |
edit.audioSync(input) | POST /v1/audio-sync | 녹음 2~6개 사이의 시간 오프셋 측정 |
edit.editPlan(input) | POST /v1/edit-plan | 스크립트로 간결한 컷, 짧은 클립, 챕터, 트레일러 계획 |
edit.applyEdl(input) | POST /v1/apply-edl | EDL을 동영상 파일이나 오디오 파일로 렌더링 |
edit.remapTranscript(edl, transcript) | 없음, 로컬 | 스크립트의 타이밍을 편집된 타임라인으로 옮기기 |
코드로 팟캐스트 편집하기
에피소드 전체라면 tightenEpisode()가 아래의 모든 단계를 호출 한 번으로 실행합니다. 각 단계를 직접 제어해야 할 때는 단계를 하나씩 쓰세요.
녹음을 텍스트로 변환
단어 타이밍을 반환하는 엔진으로 client.audio.transcribe()를 실행하세요. 그 output_data.json이 계획에 필요한 스크립트입니다.
무음 찾기
마스터 녹음에서 silenceDetect()를 실행하세요. 그 output_data.json에 무음 구간이 들어 있습니다.
컷 계획
스크립트, 소스, 무음 구간을 넣어 editPlan()을 tighten 모드로 실행하세요. 계획은 unwrapEditPlanOutput()으로 읽으세요.
렌더링
계획을 applyEdl()에 전달하세요. 완료된 작업에 편집된 동영상이나 오디오가 들어 있습니다.
import { unwrapEditPlanOutput } from "@nodaro/sdk"
async function outputOf(jobId: string): Promise<any> {
for (;;) {
const { data } = await client.jobs.getStatus(jobId)
if (data.status === "completed") return data.output_data
if (data.status === "failed" || data.status === "cancelled") throw new Error(data.error_message ?? data.status)
await new Promise((resolve) => setTimeout(resolve, 3_000))
}
}
// 1. Transcribe with word timings
const tr = await client.audio.transcribe({ audioUrl: masterUrl, provider: "elevenlabs-stt" })
const transcript = (await outputOf(tr.jobId)).json
// 2. Find the silence
const sd = await client.edit.silenceDetect({ audioUrl: masterUrl, thresholdDb: -35 })
const silence = (await outputOf(sd.jobId)).json
// 3. Plan a tighter cut
const plan = await client.edit.editPlan({
mode: "tighten",
planTier: "standard",
transcript,
sources: [{ id: "ep", url: masterUrl, kind: "video", role: "master-audio" }],
silence,
})
const edl = unwrapEditPlanOutput(await outputOf(plan.jobId))
// 4. Render the plan
const render = await client.edit.applyEdl({ edl, output: "video", quality: "final" })
const { videoUrl } = await outputOf(render.jobId)같은 단계를 노드로도 실행할 수 있습니다. 음성 텍스트 변환, 무음 감지, 편집 계획, EDL 적용입니다.
client.edit
edit.tightenEpisode(input, options?)와 estimateTightenEpisode(input)
에피소드 다듬기 노드처럼 호출 한 번으로 팟캐스트 녹음을 다듬습니다. 실행은 녹음을 텍스트로 변환하고, 다듬은 컷을 계획해 렌더링합니다. 설정에 따라 에피소드에 자막을 넣고, 프레이밍하고, 그 아래에 배경 음악을 깝니다. mediaUrl만 필수이며, 생략한 필드는 노드의 기본값을 씁니다.
tightenEpisode(input: TightenEpisodeInput, options?: { idempotencyKey?: string }): Promise<TightenEpisodeJobResult>
estimateTightenEpisode(input: TightenEpisodeInput): Promise<{ data: TightenEpisodeEstimate }>속성
타입
const quote = await client.edit.estimateTightenEpisode({ mediaUrl: episodeUrl })
console.log(quote.data.totalCredits)
const { jobId } = await client.edit.tightenEpisode(
{ mediaUrl: episodeUrl, framing: { aspect: "9:16", layout: "auto" } },
{ idempotencyKey: crypto.randomUUID() },
)tightenEpisode()는 { jobId, executionId?, deduped? }를 반환합니다. 같은 요청을 같은 idempotencyKey로 다시 시도하면, 응답은 이미 시작된 실행이며 deduped: true가 붙습니다. jobId를 폴링하세요. 완료된 작업의 output_data에는 에피소드가 videoUrl로, 오디오 에피소드라면 audioUrl로 담깁니다. 또한 json에 계획의 EDL이, quality에 검토용 미리보기라면 "proxy", 그 밖에는 "final"이 담깁니다.
estimateTightenEpisode()는 { totalCredits, stepsCredits, feeCredits, durationSec }를 반환합니다. 실행의 각 단계를 자체 요금으로 계산하고 실행당 고정 수수료를 더한 값입니다. 편집 결과를 알기 전에 녹음 전체 길이로 렌더링 가격을 매기므로 견적은 높게 나오는 편입니다. 청구는 실제로 렌더링된 분을 따릅니다. 녹음 길이를 읽을 수 없으면 durationSec은 null입니다. 크레딧이 없는 에디션에서는 모든 크레딧 금액이 0입니다.
두 메서드 모두 무언가를 만들거나 청구하기 전에 거부합니다. 400에는 validation_error, media_unreadable, media_has_no_audio, media_too_long, music_has_no_audio가 담깁니다. 403에는 node_not_available이 담깁니다. 503에는 nodaro_connection_required 또는 use_case_unavailable이 담깁니다. 잔액이 견적을 감당하지 못하면 tightenEpisode()는 InsufficientCreditsError도 발생시킵니다. 셀프 호스팅 설치에서는 Nodaro Cloud에서 실행되는 단계에 Nodaro Cloud 연결이 필요합니다.
edit.continueUseCase(input, options?)
에피소드 다듬기 실행을 새 작업으로 이어 가며, tightenEpisode()처럼 { jobId, executionId?, deduped? }를 반환합니다.
continueUseCase(input: UseCaseContinueInput, options?: { idempotencyKey?: string }): Promise<TightenEpisodeJobResult>속성
타입
const preview = await client.edit.tightenEpisode({ mediaUrl: episodeUrl, reviewBeforeRender: true })
// ...after a person reviews the preview:
const final = await client.edit.continueUseCase({ jobId: preview.jobId, mode: "render-final" })완료된 최종 렌더링이 있어도 편집을 바꾼 같은 미리보기의 최종 렌더링을 다시 할 수 있습니다. 그러면 최종 단계가 다시 실행되고 다시 청구되며, 여전히 수수료는 없습니다. 완전히 같은 편집을 다시 보내면 거부됩니다. 거부는 실행이 생기기 전에 발생합니다. 다른 작업을 가리키는 거부는 ContinuationRefusedError를 발생시킵니다. 이 오류는 상태 409의 NodaroError이며, jobId가 그 작업입니다. 코드는 세 가지입니다. already_running은 이어 가기가 진행 중이거나 완료되었다는 뜻이므로 그 작업을 따라가세요. continuation_superseded는 이후의 이어 가기도 실패했다는 뜻이므로 대신 그 작업을 이어 가세요. render_final_already_rendered는 그 작업의 결과를 쓰라는 뜻입니다. 그 밖의 거부에는 not_found, continuation_not_found, continuation_not_failed, continuation_not_completed가 있습니다. continuation_settling은 잠시 후 다시 시도하라는 뜻이고, render_final_not_preview는 그 작업이 검토용 미리보기가 아니라는 뜻입니다. 결제자가 감당할 수 없는 실행은 InsufficientCreditsError를 발생시킵니다.
edit.silenceDetect(input)
오디오 소스나 동영상 소스의 무음 구간을 찾습니다. AI 모델 없이 서버에서 실행됩니다.
silenceDetect(input: SilenceDetectInput): Promise<{ jobId: string }>속성
타입
const { jobId } = await client.edit.silenceDetect({ audioUrl: masterUrl, minSilenceMs: 900 })완료된 작업의 output_data.json은 SilenceRanges 객체 { version, ranges: [{ startMs, endMs }], durationMs }입니다. 이 객체 전체를 silence로 editPlan()에 전달하세요.
edit.audioSync(input)
오디오 동기화 노드처럼, 한 대화를 담은 녹음 2~6개의 시계가 서로 얼마나 어긋나 있는지 소리로 측정합니다. AI 모델 없이 서버에서 실행됩니다. 첫 번째 소스를 제외하고 소스 하나당 11크레딧이 듭니다. 소스 2개는 11크레딧, 4개는 33크레딧, 6개는 55크레딧입니다.
audioSync(input: AudioSyncInput): Promise<{ jobId: string }>속성
타입
const { jobId } = await client.edit.audioSync({
sources: [
{ id: "mic", url: micUrl },
{ id: "camA", url: camAUrl },
],
reference: "mic",
})완료된 작업의 output_data.json은 AudioSyncResult입니다.
{
version: number
reference: string // the source every offset is measured against
offsets: Array<{
sourceId: string
offsetMs: number // referenceMs = sourceMs + offsetMs
confidence: number // 0 to 1; below 0.5 a note asks you to check by ear
driftMsPerHour: number | null // measured, never corrected; null when the overlap was too short
}>
notes: string[] // low confidence, drift above 33 ms over the overlap, no shared sound
}마스터 녹음을 reference로 지정하면, 각 offsetMs가 그대로 EDL에서 해당 소스의 offsetMs가 됩니다. 잘못된 요청은 크레딧이 예약되기 전에, code가 validation_error인 NodaroError로 거부됩니다. 소스가 2개보다 적거나 6개보다 많은 경우, id가 중복된 경우, reference가 id 중 하나가 아닌 경우가 여기에 해당합니다.
edit.editPlan(input)
편집 계획 노드처럼, 타이밍이 있는 스크립트로 편집을 계획합니다. 네 종류의 계획 중 하나를 작성합니다. 녹음 전체의 간결한 컷, 짧은 클립 묶음, 챕터, 또는 트레일러입니다.
editPlan(input: EditPlanInput): Promise<{ jobId: string }>속성
타입
import { unwrapEditPlanOutput } from "@nodaro/sdk"
const { jobId } = await client.edit.editPlan({
mode: "clips",
planTier: "standard",
transcript,
sources: [{ id: "ep", url: masterUrl, kind: "video", role: "master-audio" }],
silence,
count: 5,
targetAspect: "9:16",
})
const { data } = await client.jobs.getStatus(jobId) // poll until completed
const clips = unwrapEditPlanOutput(data.output_data) // one EDL per clip완료된 작업의 출력은 unwrapEditPlanOutput()으로 읽으세요. tighten 모드와 trailer 모드에서는 Edl을, clips 모드에서는 Edl 배열을, chapters 모드에서는 ChapterSet을 반환합니다.
셀프 호스팅 설치에서 이 메서드를 쓰려면 Nodaro Cloud 연결이 필요합니다. 연결이 없으면 503 nodaro_connection_required로 실패합니다.
edit.applyEdl(input)
EDL 적용 노드처럼, 편집 결정 목록을 동영상 파일이나 오디오 파일로 렌더링합니다.
applyEdl(input: ApplyEdlInput): Promise<{ jobId: string }>속성
타입
const { jobId } = await client.edit.applyEdl({ edl, output: "video", quality: "proxy", crossfadeMs: 80 })완료된 작업의 output_data에는 컷이 담깁니다. videoUrl과 thumbnailUrl, 또는 audioUrl입니다. 스크립트를 보냈다면 옮긴 스크립트가 json에, 그리고 quality("proxy" 또는 "final")가 담기며, clipKey와 planBasis를 보냈다면 그 값도 담깁니다. proxy 렌더는 미리 보기이며 공개 갤러리에 나타나지 않습니다. 미리 보기와 최종 렌더링과 품질별 크레딧을 참고하세요.
EDL은 크레딧이 예약되기 전에 검사됩니다. 알 수 없는 소스, 동영상 편집에서 화면이 없는 세그먼트, 렌더링 한 번에 출력 180분 초과는 code가 invalid_edl인 NodaroError로 실패합니다. 길이는 크로스페이드를 적용한 뒤에 측정합니다. message가 문제를 알려 줍니다. 예를 들어 편집이 200분 분량으로 렌더링되므로 최대 180분짜리 부분으로 나눠야 한다고 알려 줍니다.
edit.remapTranscript(edl, transcript)
스크립트를 편집의 타임라인으로 요청 없이 로컬에서 옮깁니다. 잘라 낸 구간 안의 단어는 빼고, 컷에 걸친 단어는 잘라 내고, 모든 타이밍을 편집된 출력에 맞게 옮깁니다. applyEdl()에 transcript를 전달하면 서버에서 같은 작업을 합니다.
remapTranscript(edl: Edl, transcript: Transcript): Transcript속성
타입
const editedTranscript = client.edit.remapTranscript(edl, transcript)
// caption the edited video without another transcription렌더링이나 두 번째 텍스트 변환 없이 편집본에 자막을 달 때 사용하세요. 새 타이밍만 필요할 때는 큰 스크립트에서도 이쪽이 더 빠릅니다.
자주 묻는 질문
Nodaro SDK로 팟캐스트의 쉬는 구간을 잘라 내려면 어떻게 하나요?
녹음을 텍스트로 변환하고 client.edit.silenceDetect를 실행한 다음, 스크립트와 무음 구간을 넣어 client.edit.editPlan을 tighten 모드로 실행하세요. 반환된 계획은 client.edit.applyEdl로 렌더링하세요.
팟캐스트 에피소드를 호출 한 번으로 다듬을 수 있나요?
네. client.edit.tightenEpisode는 최대 3시간 길이의 동영상 또는 오디오 녹음 URL 하나를 받아 작업을 반환합니다. 실행은 녹음을 텍스트로 변환하고, 다듬은 에피소드를 계획해 렌더링하며, 선택에 따라 자막, 음악, 프레이밍을 더합니다.
EDL이란 무엇인가요?
편집 결정 목록입니다. 편집에 쓰는 소스와 남길 세그먼트를 순서대로, 트랜지션과 함께 담은 목록입니다. client.edit.editPlan이 EDL을 작성하고, client.edit.applyEdl이 이를 동영상 파일이나 오디오 파일로 렌더링합니다.
EDL 렌더링 한 번의 길이는 최대 얼마인가요?
렌더링 한 번에 출력은 최대 180분입니다. 이보다 긴 편집은 크레딧이 예약되기 전에 invalid_edl로 거부됩니다. 여러 부분으로 나누세요.
client.edit.audioSync의 비용은 얼마인가요?
첫 번째 소스를 제외하고 소스 하나당 11크레딧입니다. 녹음 2개는 11크레딧, 4개는 33크레딧, 6개는 55크레딧입니다.
관련 문서
보이스와 오디오
미디어 및 업로드
에피소드 다듬기
편집 계획
EDL 적용
최종 업데이트