# 보이스 및 미디어

> 보이스, 보이스 변경, 다중 화자 음색 변환, 더빙, 동영상 가져오기, 자막, 자르기, 오디오 도구, SNS 게시를 모두 Nodaro의 REST 엔드포인트로 사용하는 방법입니다.

Source: https://nodaro.ai/ko/docs/developers/api/voice-and-media

**보이스 및 미디어 엔드포인트**는 Nodaro의 보이스와 미디어 도구를 일반 REST로 제공합니다. 보이스 카탈로그, 보이스 변경과 다중 화자 음색 변환, 더빙, 보이스 디자인, 동영상 가져오기, 자막, 자르기, 오디오 도구, 음성 텍스트 변환, SNS 게시를 다룹니다. 대부분은 작업입니다. `POST`가 바로 `{ jobId }`를 반환하면, 상태가 `completed`가 될 때까지 `GET /v1/jobs/:id/status`를 폴링하고 `output_data`에서 결과를 읽으세요.

경로는 아래에 표시한 예외를 제외하고 모든 에디션에서 동작하며, Bearer 토큰을 받습니다. [인증](https://nodaro.ai/docs/developers/api/authentication)을 참고하고, 여러 작업을 한 번에 폴링하는 방법은 [작업](https://nodaro.ai/docs/developers/api/jobs#poll-many-jobs-at-once)을 참고하세요. 로컬 파일을 보내려면 먼저 업로드하세요. [업로드](https://nodaro.ai/docs/developers/api/uploads)를 참고하세요.

## 보이스
| 메서드 | 경로 | 기능 |
| --- | --- | --- |
| `GET` | `/v1/voices` | 기본 제공 보이스 카탈로그입니다. 이름, `voice_id`, 성별, 억양, 나이가 있습니다. |
| `GET` | `/v1/voices/library` | `search`, `gender`, `language`, `page`, `page_size` 등으로 공유 보이스 라이브러리를 검색합니다. |
| `GET` | `/v1/voice-clones` | 클로닝이 종료되기 전에 만든 보이스 클론입니다. |
| `PATCH` | `/v1/voice-clones/:id` | 그 클론 중 하나의 이름을 바꾸거나 편집합니다. |
| `DELETE` | `/v1/voice-clones/:id` | 그 클론 중 하나를 삭제합니다. |
| `POST` | `/v1/voice-clones`, `/v1/voice-clones/from-url` | 종료되었습니다. 둘 다 `410 voice_cloning_retired`로 응답합니다. |

보이스를 받는 경로에는 카탈로그 보이스의 `voice_id`, `Rachel` 같은 기본 제공 보이스의 이름, 또는 클론의 `elevenlabsVoiceId`를 전달하세요. 종료 전에 만든 클론은 어디서나 계속 동작합니다.

보이스 라이브러리 결과에는 `recommendedProvider`가 있을 수 있습니다. 그 보이스가 검증된 텍스트 음성 변환 모델입니다. 앱에 모델 선택기가 없다면 이 값을 [텍스트 음성 변환](https://nodaro.ai/docs/nodes/audio/text-to-speech)에 `provider`로 보내세요. 그러면 보이스가 미리 듣기와 같은 소리를 냅니다. `verifiedProviders`는 그 보이스가 검증된 모든 모델을 나열합니다. 앱에 선택기가 있다면 사용자가 고른 모델이 이 목록에 없을 때만 선택을 덮어쓰세요. 응답의 `hasMore`로 “더 불러오기”를 구현합니다.

```ts

const client = createClient({
baseUrl: 'https://app.nodaro.ai',
auth: new StaticTokenAuth(process.env.NODARO_API_KEY!),
})

const { voices, hasMore } = await client.voices.searchLibrary({ search: 'deep', language: 'en' })
const voice = voices[0]
await client.nodes.run('text-to-speech', {
text: 'Hello!',
voice: voice.voice_id,
voiceType: 'library',
...(voice.recommendedProvider ? { provider: voice.recommendedProvider } : {}),
})
```

## 보이스 디자인 또는 설명한 보이스로 말하기
| 메서드 | 경로 | 본문 | 결과 |
| --- | --- | --- | --- |
| `POST` | `/v1/voice-design` | `{ text, voiceDescription, model?, loudness?, guidanceScale?, seed?, quality?, shouldEnhance? }` | 미리 듣기와 재사용할 수 있는 보이스 ID입니다. |
| `POST` | `/v1/voice-remix` | `{ text, voiceDescription }` | 클로닝 없이 설명한 보이스로 만든 음성입니다. |

[보이스 디자인](https://nodaro.ai/docs/nodes/audio/voice-design)에서 `text`는 100~1,000자의 미리 듣기 문장이고, `loudness`는 -1~1, `guidanceScale`은 0~100입니다. [보이스 리믹스](https://nodaro.ai/docs/nodes/audio/voice-remix)에서 `text`는 1~5,000자입니다. 둘 다 작업을 반환합니다.

## 녹음의 보이스 바꾸기
`POST /v1/voice-changer`는 오디오 트랙이나 말하는 동영상 전체의 보이스를 대상 보이스 하나로 바꿉니다. `audioUrl`과 `videoUrl` 중 정확히 하나만 보내세요. 둘 다 보내면 동영상이 우선합니다. 동영상이면 Nodaro가 오디오를 꺼내 보이스를 바꾼 뒤 원래 화면에 다시 입히며, 작업의 `output_data`에는 `videoUrl`과 `audioUrl`이 모두 들어 있습니다.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/voice-changer \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "videoUrl": "https://cdn.nodaro.ai/uploads/talking.mp4", "voiceId": "Aria", "removeBackgroundNoise": false }'
```

**TypeScript SDK**

```ts
const { jobId } = await client.voices.change({
videoUrl: 'https://cdn.nodaro.ai/uploads/talking.mp4',
voiceId: 'Aria',
})
```

**CLI**

```bash
nodaro voice changer --voice Aria --video https://cdn.nodaro.ai/uploads/talking.mp4 --watch
```

| 필드 | 기능 |
| --- | --- |
| `voiceId` | 필수. 대상 보이스입니다. |
| `audioUrl` 또는 `videoUrl` | 둘 중 하나는 필수. 바꿀 녹음입니다. |
| `model` | 음성 대 음성 변환 모델입니다. |
| `stability`, `similarityBoost` | 0~1. 전달이 얼마나 안정적인지, 대상 보이스에 얼마나 가까운지입니다. |
| `style` | 0~1, 기본값 0. 전달을 과장하며, 대신 속도와 안정성이 조금 떨어집니다. |
| `useSpeakerBoost` | 대상 보이스와의 유사도를 높이며, 조금 더 느려집니다. |
| `seed` | 출력을 재현할 수 있게 하는 정수입니다. |
| `removeBackgroundNoise` | `true`이면 깨끗한 보이스만 반환하고, `false`이면 새 보이스 아래에 음악과 효과음을 유지합니다. |

가격과 팁은 [보이스 체인저](https://nodaro.ai/docs/nodes/audio/voice-changer)를 참고하세요.

## 여러 화자의 음색 변환
[보이스 체인저 Pro](https://nodaro.ai/docs/nodes/audio/voice-changer-pro)는 녹음에서 화자를 하나씩 감지하고, 말과 타이밍은 그대로 둔 채 화자마다 다른 보이스를 입힙니다. Nodaro Cloud에서 실행됩니다. Nodaro Cloud에 연결된 셀프 호스팅 설치는 그 연결을 통해 한 번에 끝나는 음색 변환을 실행할 수 있지만, 분석과 내보내기 단계에는 Nodaro Cloud 자체가 필요합니다. [Nodaro Cloud에 연결](https://nodaro.ai/docs/self-hosting/cloud-connect)을 참고하세요.

| 메서드 | 경로 | 기능 |
| --- | --- | --- |
| `POST` | `/v1/voice-changer-pro` | 매핑된 모든 화자의 음색을 변환해 완성된 동영상이나 개별 스템으로 반환합니다. |
| `POST` | `/v1/voice-changer-pro/analyze` | 음색 변환 없이 화자를 감지합니다. Nodaro Cloud 전용입니다. |
| `POST` | `/v1/voice-changer-pro/export` | 스템을 직접 믹스한 결과로 완성된 동영상을 렌더링합니다. Nodaro Cloud 전용입니다. |
| `POST` | `/v1/voice-changer-pro/translate` | 감지한 대본을 다른 언어로 번역해, 그 언어로 동영상을 다시 말하게 합니다. Nodaro Cloud 전용입니다. |

### 화자에 보이스 매핑
`orderedVoices`는 감지 순서대로 화자를 매핑합니다. 화자 0은 항목 0을, 화자 1은 항목 1을 받는 식이며, 항목은 1~8개입니다. 목록 끝을 넘는 화자는 원래 보이스를 유지합니다. 각 항목은 다음 중 하나입니다.

- **보이스 ID**, 예: `"Rachel"`.
- **`null`**, 유지 슬롯입니다. 그 화자는 원래 보이스를 유지하고, 뒤의 화자는 계속 음색이 변환됩니다. 유지 슬롯은 비용이 들지 않지만, 항목 중 하나 이상은 보이스여야 합니다.
- **화자별 설정을 담은 객체**: `{ voiceId, engine?, stability?, similarityBoost?, style?, useSpeakerBoost?, seed?, volumeMode?, volume?, languageCode? }`. `engine`은 `sts`(기본값, 음성 대 음성), `v3` 또는 `v4`입니다. `v3`는 대본을 바탕으로 ElevenLabs v3로 대사를 다시 연기하며, `stability`로 0, 0.5, 1만 받습니다. `v4`는 같은 일을 ElevenLabs v4로 하며, `stability`로 0~1 사이의 어떤 값이든 받고 `similarityBoost`를 적용합니다. `languageCode`는 번역한 대사를 말할 때 쓰며, 아래 설명처럼 `v4`에서만 받습니다. `volumeMode`는 `match`(기본값), `normalize`, `manual` 중 하나이며, `manual`에서는 `volume`을 0~200%로 지정합니다.

음색을 변환하기 전에 Nodaro는 항상 보이스를 음악, 효과음과 분리합니다. 본문의 나머지 필드가 결과를 정합니다.

| 필드 | 기능 |
| --- | --- |
| `audioUrl` 또는 `videoUrl` | 둘 중 하나는 필수. 동영상이면 음색이 변환된 오디오를 원래 화면에 다시 입힙니다. |
| `preserveBackground` | 새 보이스 아래에 음악과 효과음을 다시 섞습니다. 기본값은 `true`입니다. |
| `separationQuality` | `fast`(기본값, 보이스를 더 많이 보존) 또는 `best`(더 정밀한 분리)입니다. |
| `removeBackgroundNoise` | 결과의 노이즈도 제거합니다. |
| `musicVolumeMode`, `musicVolume` | 유지한 배경의 레벨입니다. `match`(기본값), `normalize`, 또는 0~200%를 지정하는 `manual`입니다. |
| `voiceFx` | 보이스에만 거는 리버브나 에코입니다: `{ preset, wetDryMix?, delayMs?, decay? }`. `room`, `hall`, `church` 같은 리버브 프리셋은 `wetDryMix`를, `echo`와 `custom`은 `delayMs`와 `decay`를 사용합니다. |
| `output` | `video`(기본값, 완성된 결과) 또는 `stems`(직접 믹스할 수 있도록 효과와 레벨 조정이 없는 트랙)입니다. |
| `analysis` | 이전 분석 작업의 `output_data`입니다. 음색 변환은 화자를 다시 감지하지 않고 그 화자와 스템을 재사용합니다. |

음색 변환은 매핑된 화자마다 요금이 부과되며, 분석과 내보내기는 고정 가격입니다. 가격은 [보이스 체인저 Pro](https://nodaro.ai/docs/nodes/audio/voice-changer-pro)를 참고하세요.

**다른 언어로 다시 말하기.** `POST /v1/voice-changer-pro/translate`는 분석의 화자들을 구간별로 하나의 대상 언어로 번역합니다. 각 대사에는 길이 예산이 있어서, 다시 말한 대사가 원래 자리에 맞습니다. 본문은 `{ speakers, targetLanguage, sourceLanguage?, tier? }`이며, `speakers`는 분석에서 가져오고 `tier`는 `economy`, `standard`, `premium` 중 하나입니다. 작업의 `output_data.speakers`는 모든 화자, 구간, 타이밍을 유지하며, 번역한 `text`, `sourceText`에 담긴 원문, 그리고 원래 언어로 남은 대사의 ID인 `untranslated`를 담습니다. 텍스트를 검토한 다음, 이 화자들을 음색 변환의 `analysis.speakers`로 보내고 다시 말할 각 보이스에 `engine: "v4"`와 대상 언어로 설정한 `languageCode`를 넣으세요. `sts`나 `v3`에 `languageCode`를 보내거나 모델이 제공하지 않는 언어를 보내면 `400`으로 응답합니다. 번역 가격은 텍스트 길이에 따라 정해지며, 최소 가격과 시작된 1,000자당 최대 가격은 등급에 따라 다릅니다. Nodaro Cloud가 아닌 곳에서는 네 개의 `voice-changer-pro` 라우트가 `404`로 응답합니다.

### 대화형 흐름: 분석, 스템으로 음색 변환, 내보내기
한 번에 끝나는 음색 변환은 호출 한 번으로 완성된 동영상을 렌더링합니다. 대화형 흐름에서는 음색 변환 비용을 내기 전에 화자를 확인하고, 렌더링하기 전에 결과를 믹스할 수 있습니다.

### 화자 감지
`audioUrl` 또는 `videoUrl`과, 선택적으로 `separationQuality`와 `suggestTitle`을 담아 `POST /v1/voice-changer-pro/analyze`를 호출하세요. 완료된 작업의 `output_data`에는 분리된 `vocalsUrl`과 `backgroundUrl`, 그리고 감지된 `languageCode`와 `languageProbability`가 들어 있습니다. `speakers` 목록은 각 화자의 `id`, `segments`, `firstStartSec`, `wordCount`, `snippet`을 알려 줍니다. 요청했다면 `suggestedTitle`도 있습니다. 박수처럼 사람이 아닌 “화자”가 있는지 살펴보세요.

### 스템으로 음색 변환
`output: "stems"`와 분석 `output_data` 전체를 `analysis`로 담아 `POST /v1/voice-changer-pro`를 호출하세요. 음색 변환이 감지 단계를 건너뛰므로, 감지 비용을 다시 내지 않고 다른 보이스로 다시 변환할 수 있습니다.

### 믹스와 내보내기
각 스템의 레벨은 내 인터페이스에서 직접 정하세요. 비용이 들지 않습니다. 그런 다음 원본 `videoUrl`과 최대 16개의 `tracks`를 담아 `POST /v1/voice-changer-pro/export`를 호출하세요. 각 트랙은 `{ url, gain, muted, kind? }`입니다. `gain`은 0~200, `kind`는 `voice` 또는 `background`이며, 음소거되지 않은 트랙이 하나 이상 있어야 합니다. 선택 사항인 `voiceFx`는 보이스 트랙에만 적용됩니다. 동영상 스트림은 다시 인코딩하지 않고 그대로 복사하므로, 내보낸 결과가 미리 듣기와 일치합니다. 완료된 작업의 `output_data.videoUrl`이 결과입니다.

**curl**

```bash
AUTH="Authorization: Bearer $NODARO_API_KEY"
VIDEO="https://cdn.nodaro.ai/uploads/panel.mp4"

# 1. Detect the speakers
JOB=$(curl -s -X POST https://app.nodaro.ai/v1/voice-changer-pro/analyze -H "$AUTH" \
  -H 'Content-Type: application/json' -d "{\"videoUrl\":\"$VIDEO\",\"suggestTitle\":true}" | jq -r .jobId)
# ...poll until completed, then keep the analysis
ANALYSIS=$(curl -s "https://app.nodaro.ai/v1/jobs/$JOB/status" -H "$AUTH" | jq .data.output_data)

# 2. Recast to stems: speaker 2 keeps their own voice
JOB=$(curl -s -X POST https://app.nodaro.ai/v1/voice-changer-pro -H "$AUTH" -H 'Content-Type: application/json' \
  -d "{\"videoUrl\":\"$VIDEO\",\"orderedVoices\":[\"Rachel\",null,\"Aria\"],\"output\":\"stems\",\"analysis\":$ANALYSIS}" | jq -r .jobId)

# 3. Export your mix
curl -s -X POST https://app.nodaro.ai/v1/voice-changer-pro/export -H "$AUTH" -H 'Content-Type: application/json' -d "{
\"videoUrl\": \"$VIDEO\",
\"tracks\": [
{ \"url\": \"<voice stem 0>\", \"gain\": 100, \"muted\": false },
{ \"url\": \"<voice stem 1>\", \"gain\": 90, \"muted\": false },
{ \"url\": \"<background stem>\", \"gain\": 70, \"muted\": false, \"kind\": \"background\" }
],
\"voiceFx\": { \"preset\": \"hall\", \"wetDryMix\": 25 }
}"
```

**TypeScript SDK**

```ts
const { jobId: analyzeJob } = await client.voices.analyze({ videoUrl, suggestTitle: true })
const analysis = (await client.jobs.get(analyzeJob)).output_data // once completed

const { jobId: recastJob } = await client.voices.recast({
videoUrl,
orderedVoices: ['Rachel', null, 'Aria'],
output: 'stems',
analysis,
})
const stems = (await client.jobs.get(recastJob)).output_data // once completed

await client.voices.exportMix({
videoUrl,
tracks: [
{ url: stems.tracks[0].url, gain: 100, muted: false },
{ url: stems.tracks[1].url, gain: 90, muted: false },
{ url: stems.backgroundUrl, gain: 70, muted: false, kind: 'background' },
],
voiceFx: { preset: 'hall', wetDryMix: 25 },
})
```

**CLI**

```bash
nodaro voice analyze --video https://cdn.nodaro.ai/uploads/panel.mp4 --watch --json > analyze.json
jq .output_data analyze.json > analysis.json

nodaro voice recast --video https://cdn.nodaro.ai/uploads/panel.mp4 --voices Rachel,keep,Aria \
  --analysis-file analysis.json --output stems --watch

nodaro voice export --source https://cdn.nodaro.ai/uploads/panel.mp4 --tracks-file mix.json \
  --voice-fx hall --voice-fx-mix 25 --watch
```

CLI에서 `--voices`의 `keep`이라는 단어는 유지 슬롯입니다.

## 다른 언어로 더빙
`POST /v1/dubbing`은 오디오 트랙이나 동영상 전체를 번역하고 다시 녹음하며, 각 화자의 보이스를 유지합니다. 원본은 정확히 하나만 보내세요. `audioUrl`, `videoUrl`, 또는 `sourceUrl`(YouTube나 TikTok 페이지처럼 더빙 모델이 직접 가져오는 공개 링크)입니다. 동영상은 더빙된 동영상으로 돌아옵니다. `output_data.videoUrl`과 함께, 더빙된 트랙만 따로 `output_data.audioUrl`에 들어 있습니다.

| 필드 | 기능 |
| --- | --- |
| `targetLanguage` | 필수. ISO 코드입니다. 예: `es`, `pt-BR`. |
| `sourceLanguage` | 말하는 언어입니다. 생략하면 감지합니다. |
| `numSpeakers` | `0`(기본값)이면 화자 수를 감지합니다. 화자 수를 알면 1~20으로 지정해 분리 품질을 높일 수 있습니다. |
| `startTime`, `endTime` | 원본의 이 구간만 초 단위로 더빙합니다. |
| `disableVoiceCloning`, `dropBackgroundAudio` | 각 화자의 원래 보이스를 재현하지 않거나, 음악과 효과음을 뺍니다. |
| `highestResolution` | 동영상에서 원본 해상도를 유지합니다. |
| `useProfanityFilter`, `targetAccent`, `watermark` | 비속어 필터, 대상 언어의 억양, 동영상에 넣는 더빙 모델의 워터마크입니다. |

더빙은 더빙하는 구간의 분 단위로 가격이 매겨지며, 최소 1분입니다. 구간은 최대 30분이며, 더 길면 `413`으로 응답하므로 `startTime`과 `endTime`으로 구간을 정해 더빙하세요. [더빙](https://nodaro.ai/docs/nodes/audio/dubbing)을 참고하세요.

## 링크에서 동영상 가져오기
`POST /v1/download-video`는 SNS 동영상(YouTube, TikTok, Instagram, X, Facebook)이나 동영상 파일 직접 링크를 내 저장 공간으로 가져옵니다. 작업 ID가 아니라 `{ downloadId }`를 반환합니다. 완성된 파일은 내 라이브러리에 저장됩니다.

| 필드 | 기능 |
| --- | --- |
| `url` | 필수. 가져올 페이지나 파일입니다. |
| `maxHeight` | 해상도 상한입니다. 예: `720`. 생략하면 가능한 최고 해상도입니다. |
| `sectionStartSec`, `sectionEndSec` | 해당 범위만 초 단위로 가져옵니다. 컷이 키프레임에 맞춰지기 때문에 앞뒤로 몇 초씩 더 포함됩니다. 둘 다 보내거나 둘 다 생략하고, 정확하게 자르려면 결과를 자르세요. |
| `exactSection` | `true`로 보내면 여유 없이 요청한 범위에서 정확히 자릅니다. |
| `requireAudio` | 소리가 없는 결과는 보통 품질이 떨어진 원본이라는 뜻이므로 기본적으로 실패합니다. 무음 클립을 받으려면 `false`를 보내세요. |

다운로드는 서버 전송 이벤트 스트림인 `GET /v1/download-video/progress/:downloadId`로 추적하세요. 약 500ms마다 `{ phase, percent, videoUrl?, thumbnailUrl?, error? }`를 보내며, 저장된 `videoUrl`과 함께 `completed`에서, 또는 `error`와 함께 `failed`에서 끝납니다. 진행률은 다운로드가 끝난 뒤 잠시만 보관되므로 가져오기가 시작되면 바로 수신을 시작하세요. 계정마다 다운로드는 동시에 최대 4개까지 실행되며, 다섯 번째는 `429 too_many_downloads`로 응답합니다.

**curl**

```bash
DL=$(curl -s -X POST https://app.nodaro.ai/v1/download-video \
  -H "Authorization: Bearer $NODARO_API_KEY" -H 'Content-Type: application/json' \
  -d '{ "url": "https://www.youtube.com/watch?v=VIDEO_ID", "maxHeight": 720 }' | jq -r .downloadId)

curl -sN https://app.nodaro.ai/v1/download-video/progress/$DL \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

**TypeScript SDK**

```ts
const { downloadId } = await client.media.downloadVideo({
url: 'https://www.youtube.com/watch?v=VIDEO_ID',
maxHeight: 720,
})
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)
}
```

**CLI**

```bash
nodaro media download "https://www.youtube.com/watch?v=VIDEO_ID" --max-height 720 --watch
nodaro media metadata "https://www.youtube.com/watch?v=VIDEO_ID"
```

관련 경로 두 개는 바로 응답합니다.

- `{ url }`로 `POST /v1/video-metadata`를 호출하면 동영상을 다운로드하지 않고 길이, 크기, 제목, 라이브 여부를 읽습니다. 일부 구간만 가져올지 결정할 때 사용하세요.
- `{ mediaUrl, filename?, mediaType? }`로 `POST /v1/save-to-storage`를 호출하면 외부 미디어 URL을 서버에서 내 저장 공간으로 복사합니다. 작업을 반환합니다.

## 동영상 편집
| 메서드 | 경로 | 기능 | 가격 |
| --- | --- | --- | --- |
| `POST` | `/v1/trim-video` | 동영상을 지정한 범위로 자릅니다. | [동영상 자르기](https://nodaro.ai/docs/nodes/video/trim-video) |
| `POST` | `/v1/add-captions` | 동영상에 자막을 입힙니다. | [자막 추가](https://nodaro.ai/docs/nodes/video/add-captions) |
| `POST` | `/v1/still-to-video` | 이미지 하나와 오디오 트랙 하나로 MP4를 만듭니다. | 0크레딧 |
| `POST` | `/v1/slideshow` | 이미지 2~100개와 선택 사항인 오디오 트랙으로 MP4를 만듭니다. | 0크레딧 |
| `POST` | `/v1/video-overlay` | 시간을 지정한 이미지 레이어 1~20개를 동영상 위에 배치합니다. | 22크레딧 |
| `POST` | `/v1/media/process` | 저장된 파일을 자르거나 크롭하고 바로 응답합니다. | 무료 |
| `POST` | `/v1/edit-plan` | 녹음의 대본으로 편집을 계획합니다. 군더더기를 줄이거나, 클립을 찾거나, 챕터를 표시하거나, 트레일러를 만듭니다. 결과는 미디어가 아니라 편집 결정 목록입니다. | [편집 계획](https://nodaro.ai/docs/nodes/video/edit-plan) |
| `POST` | `/v1/camera-switch` | 편집의 각 컷을 말하는 사람의 카메라로 바꿉니다. 소리는 바뀌지 않습니다. | [카메라 전환](https://nodaro.ai/docs/nodes/video/camera-switch) |
| `POST` | `/v1/apply-edl` | 편집 결정 목록을 동영상이나 오디오 파일 하나로 렌더링합니다. `final` 또는 `proxy` 미리 보기로 렌더링합니다. | [EDL 적용](https://nodaro.ai/docs/nodes/video/apply-edl) |
| `POST` | `/v1/tighten-episode` | 팟캐스트나 인터뷰를 한 번의 호출로 다듬습니다. 녹음을 받아쓰고, 무음과 군더더기 말을 없애고, 에피소드를 렌더링하며, 원하면 자막과 배경 음악도 추가합니다. | [에피소드 다듬기](https://nodaro.ai/docs/nodes/video/tighten-episode) |
| `POST` | `/v1/speaker-view` | 편집을 화자가 화면에 나오도록 레이아웃에 배치해 렌더링하며, 화자가 바뀔 때마다 전환합니다. 출력 1분 단위로 가격이 매겨집니다. | [화자 뷰](https://nodaro.ai/docs/nodes/video/speaker-view#credits) |
| `POST` | `/v1/split-media` | 동영상, 오디오 파일 또는 둘 다를 최소 길이와 최대 길이 사이의 구간으로 자릅니다. | 실행당 22크레딧 |
| `POST` | `/v1/video-mask` | 텍스트로 설명한 대상을 추적해 흑백 마스크 동영상을 반환합니다. 클립 길이에 따라 가격이 매겨집니다. | [동영상 마스크](https://nodaro.ai/docs/nodes/video/video-mask) |
| `POST` | `/v1/mask-composite` | 마스크 동영상을 통해 전경 동영상을 배경 동영상 위에 배치합니다. | 시작된 5초마다 11크레딧 |
| `POST` | `/v1/video-depth` | 동영상을 가까운 곳은 밝고 먼 곳은 어두운 깊이 동영상으로 바꿉니다. 클립 길이에 따라 가격이 매겨집니다. | [동영상 깊이](https://nodaro.ai/docs/nodes/video/video-depth) |

편집 계획, 카메라 전환, 에피소드 다듬기의 계획 단계는 Nodaro Cloud에서 실행됩니다. 셀프 호스팅 설치는 [Nodaro Cloud 연결](https://nodaro.ai/docs/self-hosting/cloud-connect)을 통해 실행하며, 연결이 없으면 `503 nodaro_connection_required`로 응답합니다.

`POST /v1/split-media`는 `videoUrl`, `audioUrl` 또는 둘 다와 함께 `chunkDuration`(구간의 최대 길이, 1초 이상), `minPartDuration`(기본값 4, `chunkDuration`보다 클 수 없음), `audioFormat`(`mp3`, `wav`, `aac`)을 받습니다. 완료된 작업의 `output_data`에는 `videoUrls`, `audioUrls`, `chunkCount`, `partDurationsSec`가 있습니다. 구간을 자르는 방식은 [구간 분할](https://nodaro.ai/docs/nodes/video/split-into-chunks)을 참고하세요.

`GET /v1/edit-plan/capabilities`는 이 서버가 계획할 수 있는 편집 계획 모드를 `{ modes, source, perMinute }`로 나열합니다. `tighten`, `clips`, `chapters`는 항상 나열되고, `trailer`는 서버가 트레일러를 계획할 수 있을 때만 나열됩니다. `perMinute`는 편집 계획이 녹음의 시작된 1분마다 청구되면 `true`입니다. 서버가 나열하지 않은 모드는 `POST /v1/edit-plan`에서 아무것도 청구되기 전에 `400 mode_not_available`로 응답합니다.

### 에피소드 다듬기
`POST /v1/tighten-episode`는 녹음 하나에 [에피소드 다듬기](https://nodaro.ai/docs/nodes/video/tighten-episode)를 실행합니다. 본문은 `{ mediaUrls: [<recording URL>], musicUrl?, output?, framing?, captions?, captionStyle?, reviewBeforeRender?, planTier?, instructions?, styleGuide?, musicDuckAmount? }`입니다. `framing`은 `{ aspect, layout }` 또는 `null`입니다. 라우트는 `{ jobId, executionId }`와 함께 `202`로 응답합니다.

- **작업을 추적하세요.** `GET /v1/jobs/:id/status`를 사용합니다. 작업의 `output_data`에는 에피소드(`videoUrl`, 오디오 에피소드라면 `audioUrl`), 편집(`json`), 대본(`transcript`), 그리고 `quality`가 담깁니다. `quality`는 검토 실행의 미리 보기라면 `proxy`, 그 밖에는 `final`입니다. 실패한 실행은 완료된 부분을 유지하고, 실패한 단계를 `failedStep`에 담습니다.
- **안전하게 재시도하세요.** `Idempotency-Key` 헤더가 있으면 같은 요청을 재시도했을 때 같은 `jobId`와 `"deduped": true`로 응답하고, 아무것도 시작하지 않습니다.
- **먼저 가격을 확인하세요.** `POST /v1/tighten-episode/estimate`는 같은 본문을 받아 아무것도 시작하지 않고 `{ data: { totalCredits, stepsCredits, feeCredits, durationSec } }`로 응답합니다. 견적은 렌더링을 녹음 전체 길이로 계산하므로, 실제 실행은 대개 더 적게 듭니다.
- **실행을 이어 가세요.** `{ jobId, mode }`와 함께 `POST /v1/tighten-episode/continue`를 보내면 내 실행 하나를 이어 갑니다. `"mode": "from-failed"`는 실패한 실행이 완료하지 못한 부분만 실행합니다. `"mode": "render-final"`은 계획을 내가 편집한 내용을 담은 선택 사항 `editedEdl`과 함께, 검토 실행의 최종본을 렌더링합니다.

미리 보기가 아니었던 실행의 최종 렌더링은 `render_final_not_preview`로 응답하고, 이미 렌더링된 편집의 최종 렌더링은 `render_final_already_rendered`로 응답합니다. 실패하지 않은 실행을 이어 가려고 하면 `409 continuation_not_failed`로 응답합니다.

워크플로 안에서 **렌더링 전에 검토**가 켜진 **에피소드 다듬기** 노드는 편집기나 앱 자체 페이지에서 사람이 검토해야 합니다. API, SDK, MCP의 `run_workflow`, 트리거로 시작한 워크플로 실행에서는 그 앞의 노드가 실행되고 청구된 다음, 이 노드가 `preview_review_required`로 실패하며 이 노드에는 아무것도 청구되지 않습니다. 이런 실행에서는 **렌더링 전에 검토**를 끄세요. **목록**이 입력되는 검토 켜짐 노드는 실행이 하나라도 시작되기 전에 `use_case_review_list_driven`으로 거부됩니다. SDK는 `client.edit.tightenEpisode`, `client.edit.estimateTightenEpisode`, `client.edit.continueUseCase`로, CLI는 `nodaro edit tighten-episode`와 `nodaro edit continue-use-case`로 같은 라우트를 호출합니다. MCP에서는 `tighten_episode`와 `continue_use_case`를 사용하세요.

### 동영상 자르기
`POST /v1/trim-video`는 `videoUrl`과, 원하는 단위로 된 범위를 받습니다. 초 단위의 `startTime`과 `endTime`, `trimStartFrames`와 `trimEndFrames`, `trimStartSeconds`와 `trimEndSeconds`, 또는 `keepFirstSeconds`나 `keepLastSeconds`입니다.

### 자막 입히기
`POST /v1/add-captions`는 `videoUrl`을 받고, 다음 순서로 처음 찾은 소스에서 단어를 가져옵니다. `captions`(단어별 타이밍 항목), `transcript`, `subtitle` 스타일의 `text`, 마지막으로 음성 자동 변환입니다.

| 필드 | 기능 |
| --- | --- |
| `style` | `subtitle`(고정 블록) 또는 키네틱 스타일인 `word-highlight`, `karaoke`, `tiktok-words`, `word-pop`, `bouncy` 중 하나입니다. |
| `text` | `subtitle`에서는 자막 자체이며, 동영상 전체에 고정 블록 하나로 입혀집니다. 키네틱 스타일에서는 음성 변환 결과가 없을 때만 쓰는 대체 텍스트입니다. |
| `captions` | 단어별 타이밍 항목 `{ text, startMs, endMs }`이며, 키네틱 스타일에서는 단어마다 하나씩입니다. 음성 텍스트 변환의 `words`를 그대로 넣을 수 있습니다. |
| `auto_transcribe`, `transcribe_provider` | 다른 소스에서 단어를 얻지 못하면 음성을 텍스트로 변환합니다. 키네틱 스타일에는 단어 타이밍이 필요합니다: `incredibly-fast-whisper`(기본값) 또는 `elevenlabs-stt`. |
| `look` | `outline`(키네틱 스타일의 기본값) 또는 `clean`(`subtitle`의 기본값)입니다. |
| `maxWordsPerLine` | 프레임 너비 제한과 별도로 줄당 1~20단어입니다. 1이나 2로 하면 리듬감 있게 읽힙니다. |
| `highlightColor`, `animate` | 키네틱 스타일 전용입니다. `subtitle`에서는 `400`으로 응답합니다. |
| `position`, `positionY`, `fontSize`, `fontFamily`, `fontWeight`, `color`, `backgroundColor`, `strokeColor`, `strokeWidth`, `uppercase` | 텍스트의 모양입니다. |
| `segments` | 동영상의 구간마다 스타일, 룩, 위치를 따로 지정합니다. |

스타일을 지정하지 않은 일반 텍스트 `subtitle`은 저렴한 렌더링입니다. 키네틱 스타일, 스타일 필드, 타이밍이 있는 자막, 음성 자동 변환, `segments` 중 하나라도 쓰면 더 높은 가격으로 렌더링되며 원본 프레임 레이트를 유지합니다. 모든 옵션과 두 가격은 [자막 추가](https://nodaro.ai/docs/nodes/video/add-captions)를 참고하세요.

### 정지 이미지를 동영상으로
- <strong>`POST /v1/still-to-video`</strong>는 `{ imageUrl, audioUrl, motion?, intensity?, resolution?, aspectRatio?, fps?, fit?, padColor? }`를 받습니다. 동영상은 오디오 길이만큼 이어지며, 길이 필드는 없습니다. `motion`은 `none`(기본값), `zoom-in`, `zoom-out`, `pan-left`, `pan-right`, `ken-burns` 중 하나이며, `intensity`는 1~10입니다. [정지 이미지로 동영상 만들기](https://nodaro.ai/docs/nodes/video/still-to-video)를 참고하세요.
- <strong>`POST /v1/slideshow`</strong>는 `imageUrls`(2~100개), 선택 사항인 `audioUrl`, `imageDurations`(이미지당 초, 자동이면 `null`) 또는 `perImageDuration`, `transition`, 그리고 같은 모양 필드를 받습니다. 오디오가 있으면 슬라이드쇼는 오디오 길이만큼 이어집니다. 고정한 길이의 합이 맞지 않으면 비율에 맞춰 조정되며, 작업 출력에 그 사실이 표시됩니다. 오디오가 없으면 이미지 수에 `perImageDuration`을 곱한 길이만큼 소리 없이 이어집니다. [슬라이드쇼](https://nodaro.ai/docs/nodes/video/slideshow)를 참고하세요.

둘 다 AI 모델 없이 서버에서 렌더링되며 0크레딧입니다. `resolution`은 `720p`, `1080p`, `4K` 중 하나, `fps`는 `24` 또는 `30`이며, `fit: "contain"`은 자르는 대신 `padColor`로 레터박스를 넣습니다.

### 동영상에 이미지 오버레이
`POST /v1/video-overlay`는 렌더링 한 번으로 이미지 레이어 1~20개를 동영상 위에 배치하고, 원래 오디오는 건드리지 않습니다. 각 레이어는 `{ imageUrl, start, end?, preset?, corner?, anchor?, x?, y?, width?, height?, fit?, opacity?, animate?, zIndex? }`입니다. `start`와 `end`는 초 단위이며, `end`가 없으면 레이어는 동영상이 끝날 때까지 이어집니다. `preset`은 `card`, `corner-badge`, `full-frame` 중 하나이며, 위치나 크기를 명시하면 프리셋보다 우선합니다. 둘 다 없는 레이어는 코너 배지가 되며, `corner`로 따로 정하지 않으면 오른쪽 아래에 놓입니다.

`outputAspect`(`16:9`, `9:16`, `1:1`, `4:5`)는 `baseFit`, `backgroundColor`와 함께 프레임을 바꿉니다. 작업은 `{ videoUrl, thumbnailUrl, width, height, durationSec, warnings }`를 반환합니다. 레이어 수와 관계없이 실행당 22크레딧이 들며, 사용자마다 분당 30개의 요청을 보낼 수 있습니다. [동영상 오버레이](https://nodaro.ai/docs/nodes/video/video-overlay)를 참고하세요.

### 저장된 파일을 바로 자르기 또는 크롭
`POST /v1/media/process`는 저장한 파일을 자르거나 크롭하고, 같은 요청 안에서 무료로 응답합니다. 본문은 `{ sourceUrl, type, trim?, crop?, format?, deleteSource? }`입니다. `type`은 `video` 또는 `audio`, `trim`은 `{ startTime, endTime }`, `crop`은 `{ x, y, width, height }`이고, `format`은 `mp4`, `webm`, `mp3`, `wav`, `m4a`, `aac` 중 하나입니다. `{ data: { url, thumbnailUrl, assetId, sizeBytes, mimeType, metadata } }`를 반환합니다. `deleteSource: true`는 원본이 내 것이고 다른 곳에서 쓰지 않을 때 작업 후 원본을 삭제합니다.

## 오디오 편집
각 경로는 작업을 반환합니다.

| 메서드 | 경로 | 본문 | 기능 |
| --- | --- | --- | --- |
| `POST` | `/v1/audio-separation` | `{ audioUrl, mode?, quality? }` | 트랙을 분리합니다. `vocal_instrumental`(기본값) 또는 전체 `stems`이며, 품질은 `auto`, `fast`, `best`입니다. [음원 분리](https://nodaro.ai/docs/nodes/audio/audio-separation)를 참고하세요. |
| `POST` | `/v1/audio-isolation` | `{ audioUrl }` | 주 음성만 남기고 배경을 제거합니다. [음성 추출](https://nodaro.ai/docs/nodes/audio/voice-extractor)을 참고하세요. |
| `POST` | `/v1/audio-fx` | `{ audioUrl, preset?, mix?, delayMs?, decay?, eqLow?, eqHigh? }` | 리버브, 에코, 전화, 메가폰 효과입니다. [오디오 효과](https://nodaro.ai/docs/nodes/audio/audio-fx)를 참고하세요. |
| `POST` | `/v1/mix-audio` | `{ audioUrls, trackVolumes?, duck?, endAt? }` | 트랙 2~20개를 각각 0~200%로 겹칩니다. `duck`은 `{ under, amount?, thresholdDb?, ratio?, attackMs?, releaseMs? }`이며, `audioUrls[under]`의 소리가 클 때 나머지 모든 트랙의 볼륨이 낮아집니다. 요금은 `duck`이 있든 없든 같습니다. `endAt`은 `"longest"`(기본값) 또는 `"voice"`이며, `"voice"`는 `duck.under` 트랙이 끝날 때 믹스를 끝내고 `duck`이 필요합니다. [오디오 믹스](https://nodaro.ai/docs/nodes/audio/mix-audio)를 참고하세요. |
| `POST` | `/v1/adjust-volume` | `{ audioUrl or videoUrl, volume?, normalize?, fadeIn?, fadeOut? }` | 레벨을 바꾸거나, 노멀라이즈하거나, 페이드를 겁니다. [볼륨 조절](https://nodaro.ai/docs/nodes/audio/adjust-volume)을 참고하세요. |
| `POST` | `/v1/pitch-tempo` | `{ audioUrl or videoUrl, semitones?, tempo?, preserveFormants? }` | 피치를 -12~12반음 옮기거나, 피치를 바꾸지 않고 템포를 0.5~2로 바꿉니다. 동영상은 화면을 그대로 유지하며 템포는 1로 고정됩니다. 입력의 시작된 1분마다 22크레딧이며, 최대 30분입니다. [피치 및 템포](https://nodaro.ai/docs/nodes/audio/pitch-tempo)를 참고하세요. |
| `POST` | `/v1/combine-audio` | `{ segments: [{ url, startTime?, endTime? }] }` | 세그먼트를 끝과 끝으로 잇습니다. [오디오 합치기](https://nodaro.ai/docs/nodes/audio/combine-audio)를 참고하세요. |
| `POST` | `/v1/trim-audio` | `{ audioUrl or videoUrl, startTime?, endTime?, audioFormat? }` | 오디오를 자르거나 동영상에서 오디오를 꺼내 `mp3`(기본값), `wav`, `aac`로 만듭니다. [오디오 자르기](https://nodaro.ai/docs/nodes/audio/trim-audio)를 참고하세요. |
| `POST` | `/v1/silence-detect` | `{ audioUrl, thresholdDb?, minSilenceMs?, padMs? }` | 녹음에서 무음 구간을 찾습니다. 11크레딧입니다. [무음 감지](https://nodaro.ai/docs/nodes/audio/silence-detect)를 참고하세요. |
| `POST` | `/v1/audio-sync` | `{ sources: [{ id, url }], reference? }` | 한 대화를 녹음한 2~6개 파일 사이의 오프셋을 측정합니다. [오디오 동기화](https://nodaro.ai/docs/nodes/audio/audio-sync)를 참고하세요. |

`silence-detect`의 기본값은 `thresholdDb: -35`, `minSilenceMs: 700`, `padMs: 120`이며, 오디오와 동영상을 모두 받습니다. `output_data.json`은 `{ version, ranges: [{ startMs, endMs }], durationMs }`입니다.

`audio-sync`는 첫 번째 이후의 소스마다 11크레딧이 듭니다. 소스 2개는 11크레딧, 4개는 33크레딧, 6개는 55크레딧입니다. ID는 고유해야 하며, `reference`는 그중 하나여야 합니다(기본값은 첫 번째 소스). `output_data.json`은 `{ version, reference, offsets: [{ sourceId, offsetMs, confidence, driftMsPerHour }], notes }`이며, 레퍼런스의 시간은 소스의 시간에 `offsetMs`를 더한 값과 같습니다. 드리프트는 측정해서 기록할 뿐, 보정하지 않습니다.

## 음성 텍스트 변환
`POST /v1/transcribe`는 음성을 텍스트로 바꾸고 작업을 반환합니다. 본문은 `{ audioUrl, provider?, language?, diarize?, tagAudioEvents?, wordTimestamps? }`이며, `audioUrl`에는 동영상도 넣을 수 있습니다.

| `provider` | 단어 타이밍 | 참고 |
| --- | --- | --- |
| [`elevenlabs-stt`](https://nodaro.ai/docs/models/audio/elevenlabs-stt) | 항상 | `diarize`(각 단어를 누가 말했는지)와 `tagAudioEvents`(웃음, 박수)를 지원하는 유일한 엔진입니다. |
| [`incredibly-fast-whisper`](https://nodaro.ai/docs/models/audio/incredibly-fast-whisper) | `wordTimestamps: true`일 때 | 플래그가 없으면 작업은 성공하고 요금도 부과되지만, 구 단위로만 반환합니다. |
| [`whisper`](https://nodaro.ai/docs/models/audio/whisper) | 반환하지 않음 | 구 단위로만 반환합니다. `wordTimestamps: true`를 보내면 크레딧이 쓰이기 전에 `400 validation_error`로 응답합니다. |

`provider`를 생략하면 `whisper`가 실행되므로, 단어 타이밍이 필요할 때는 항상 엔진을 지정하세요. 완료된 작업의 `output_data`에는 `text`, `language`, `words`(단어마다 하나씩, 밀리초 단위의 `{ text, startMs, endMs, speaker? }`), `json`(정규화된 대본, 역시 밀리초 단위), 그리고 이전 엔진에서만 초 단위의 `segments`가 들어 있습니다. `words`는 `POST /v1/add-captions`의 `captions`에 그대로 넣을 수 있습니다.

**curl**

```bash
curl -X POST https://app.nodaro.ai/v1/transcribe \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "audioUrl": "https://cdn.nodaro.ai/uploads/talk.mp3", "provider": "elevenlabs-stt", "diarize": true }'
```

**TypeScript SDK**

```ts
const { jobId } = await client.audio.transcribe({
audioUrl: 'https://cdn.nodaro.ai/uploads/talk.mp3',
provider: 'elevenlabs-stt',
})
// once completed, burn the words in as kinetic captions
const words = (await client.jobs.get(jobId)).output_data.words
await client.media.addCaptions({
videoUrl: 'https://cdn.nodaro.ai/uploads/talk.mp4',
captions: words,
style: 'word-highlight',
autoTranscribe: false,
})
```

**CLI**

```bash
nodaro audio transcribe --audio https://cdn.nodaro.ai/uploads/talk.mp3 --provider elevenlabs-stt --watch
nodaro jobs get <jobId> --json | jq '.output_data.words' > words.json
nodaro media add-captions https://cdn.nodaro.ai/uploads/talk.mp4 \
  --captions-file words.json --style word-highlight --no-auto-transcribe --watch
```

## SNS에 게시
연결 흐름은 팝업 창을 열기 때문에 웹 앱에서 쓰도록 만들어졌습니다. 게시는 개인 API 토큰으로 할 수 있습니다. OAuth 앱 토큰으로는 연결을 관리할 수 없습니다.

| 메서드 | 경로 | 기능 |
| --- | --- | --- |
| `GET` | `/v1/social/providers` | 지원하는 네트워크와, 각 네트워크를 이 배포 환경에서 사용할 수 있는지입니다. |
| `GET` | `/v1/social/connections` | 연결된 내 계정입니다. |
| `DELETE` | `/v1/social/connections/:id` | 계정 연결을 해제합니다. |
| `POST` | `/v1/social/telegram/connect` | 봇 토큰으로 Telegram을 연결합니다: `{ botToken }`. |
| `POST` | `/v1/social/connect/custom` | 로그인 대신 필드를 쓰는 네트워크를 연결합니다: `{ platform, fields }`. |
| `POST` | `/v1/social/publish` | 지금 게시합니다. 작업을 반환합니다. 11크레딧입니다. |
| `POST` | `/v1/social/scheduled-posts` | 게시물을 예약합니다. 11크레딧이며, 게시될 때 부과됩니다. |
| `GET` | `/v1/social/scheduled-posts` | 예약된 내 게시물을 `from`, `to`, `status`로 필터링해 반환합니다. |
| `PATCH` | `/v1/social/scheduled-posts/:id` | `queued` 또는 `draft` 상태인 동안 게시물을 편집합니다. |
| `DELETE` | `/v1/social/scheduled-posts/:id` | 대기 중인 게시물을 취소합니다. 기록은 유지됩니다. |

`GET /v1/social/providers`는 모든 네트워크를 `{ id, label, connectKind, editor, category, capabilities, available }`로 나열합니다. `category`는 `social`(게시하는 피드) 또는 `publishing`(기사를 게시하는 사이트)입니다. 배포 환경에서 설정하지 않은 네트워크는 숨기지 않고 `available: false`로 나열합니다. 필드로 연결하는 네트워크(Bluesky, Dev.to, Hashnode, Medium, WordPress, Lemmy)는 `customFields`에 필드를 설명하며, Nodaro는 저장하기 전에 해당 네트워크에서 자격 증명을 확인합니다.

`POST /v1/social/publish`는 `{ platform, action, connectionId?, caption?, mediaUrl or mediaItems, … }`를 받습니다. 두 가지 실패는 다르게 처리해야 합니다.

- **`503 publish_retryable`**: 아무것도 게시되지 않았습니다. 같은 요청을 다시 보내도 안전합니다.
- **`500 publish_failed`**: 결과를 알 수 없습니다. 다시 보내면 두 번 게시될 수 있으므로 먼저 네트워크를 확인하세요.

예약 게시물은 `{ connectionId, action, scheduledAt, caption?, media? }`를 받으며, 각 미디어 항목은 `{ type, r2Key or url }`입니다. 미디어는 이 배포 환경에 저장된 파일이어야 하며, Nodaro가 게시할 때 새 링크로 바꿉니다. 다른 사이트의 링크는 거부됩니다. 이미 게시 중인 게시물을 편집하면 `409 not_editable`로 응답합니다. [SNS에 게시](https://nodaro.ai/docs/nodes/publish/publish-to-social)와 [SNS 게시 가이드](https://nodaro.ai/docs/guides/publishing-to-social)를 참고하세요.

## SDK 및 CLI 대응
| 영역 | TypeScript SDK | CLI |
| --- | --- | --- |
| 보이스와 보이스 변경 | `client.voices.list`, `searchLibrary`, `listClones`, `deleteClone`, `change`, `recast`, `analyze`, `exportMix`, `design`, `remix`, `dub` | `nodaro voice list`, `changer`, `recast`, `analyze`, `export`, `design`, `remix`, `dub`, `clones` |
| 미디어 | `client.media.downloadVideo`, `downloadVideoProgress`, `videoMetadata`, `trimVideo`, `trimAudio`, `addCaptions`, `stillToVideo`, `slideshow`, `videoOverlay`, `saveToStorage`, `process` | `nodaro media download`, `metadata`, `trim-video`, `trim-audio`, `add-captions`, `still-to-video`, `slideshow`, `video-overlay`, `save` |
| 오디오 | `client.audio.separate`, `isolate`, `applyFx`, `mix`, `adjustVolume`, `pitchTempo`, `combine`, `transcribe` | `nodaro audio separate`, `isolate`, `fx`, `mix`, `adjust-volume`, `pitch-tempo`, `combine`, `transcribe` |
| 편집 도우미 | `client.edit.silenceDetect`, `audioSync` | `nodaro edit silence-detect`, `audio-sync` |

MCP에서는 같은 경로가 `voice_changer`, `voice_changer_pro`, `voice_changer_pro_analyze`, `voice_changer_pro_export`, `dubbing`, `transcribe`, `add_captions`, `separate_audio` 같은 도구로 제공됩니다. [MCP 도구 레퍼런스](https://nodaro.ai/docs/mcp/tools)를 참고하세요.

## 오류
| 상태 | 코드 | 의미 |
| --- | --- | --- |
| `400` | `validation_error` | 필드가 없거나 잘못되었습니다. 예를 들어 `whisper`에 보낸 `wordTimestamps`, 키네틱 자막 스타일이 필요한 스타일 필드, 모든 트랙을 음소거한 내보내기가 있습니다. |
| `400` | `provider_not_configured` | 배포 환경이 해당 SNS를 설정하지 않았습니다. |
| `401` | `unauthorized` | 토큰이 없거나, 잘못되었거나, 폐기되었습니다. |
| `402` | `insufficient_credits` | Nodaro Cloud 전용입니다. 계정의 크레딧이 작업 비용에 못 미칩니다. |
| `404` | `not_found` | 보이스 체인저 Pro를 제공하지 않는 배포 환경에서 그 경로를 호출했습니다. |
| `409` | `not_editable` | 예약 게시물이 이미 게시 중입니다. |
| `410` | `voice_cloning_retired` | 보이스 클로닝은 더 이상 제공되지 않습니다. |
| `413` | — | 더빙하는 구간이 30분보다 깁니다. |
| `429` | `too_many_downloads` | 내 계정에서 동영상 가져오기가 이미 4개 실행 중입니다. |
| `500` | `publish_failed` | 게시물의 결과를 알 수 없습니다. 다시 보내기 전에 네트워크를 확인하세요. |
| `503` | `publish_retryable` | 아무것도 게시되지 않았습니다. 같은 요청을 다시 보내도 안전합니다. |

## 자주 묻는 질문

### 동영상 하나에서 여러 화자의 보이스를 바꾸려면 어떻게 하나요?

감지된 화자 순서대로 화자마다 항목을 하나씩 담은 orderedVoices와 함께 POST /v1/voice-changer-pro를 사용하세요. null 항목은 그 화자의 원래 보이스를 유지합니다. 화자를 먼저 확인하려면 분석 단계를 실행한 뒤 스템으로 음색을 변환하고 직접 믹스를 내보내세요.

### API로 보이스를 복제할 수 있나요?

아니요. 보이스 클로닝은 종료되었으며, 클론 경로는 410 voice_cloning_retired로 응답합니다. 이전에 만든 클론은 보이스 ID로 계속 동작합니다. 대신 POST /v1/voice-design으로 설명에서 새 보이스를 디자인하세요.

### 단어 타이밍을 주는 음성 텍스트 변환 엔진은 무엇인가요?

elevenlabs-stt는 항상 단어 타이밍을 반환하고, incredibly-fast-whisper는 wordTimestamps를 true로 보낼 때 반환합니다. whisper는 구 단위로만 반환합니다. provider를 생략하면 whisper가 실행되므로, 단어 타이밍이 필요하면 엔진을 지정하세요.

### 더빙할 수 있는 클립의 길이는 얼마인가요?

더빙하는 구간은 최대 30분이며, 최소 1분으로 분 단위로 가격이 매겨집니다. 더 긴 원본은 startTime과 endTime으로 구간을 정해 더빙하세요.

### 어떤 미디어 도구가 무료인가요?

정지 이미지로 동영상 만들기, 슬라이드쇼, 동기식 자르기·크롭 경로인 POST /v1/media/process는 0크레딧입니다. 동영상 오버레이는 실행당 22크레딧, 무음 감지는 11크레딧입니다.
