# 音声とメディア

> ボイスカタログ、ボイスチェンジャー、複数話者の声質変換、吹き替え、動画のインポート、字幕、トリミング、オーディオツール、SNS への公開を、Nodaro の REST エンドポイントとして提供します。

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

**音声とメディアのエンドポイント**は、Nodaro の音声とメディアのツールを、そのまま REST として提供します。ボイスカタログ、ボイスチェンジャーと複数話者の声質変換、吹き替え、ボイスデザイン、動画のインポート、字幕、トリミング、オーディオツール、文字起こし、SNS への公開が含まれます。そのほとんどはジョブです。`POST` はすぐに `{ jobId }` を返すので、ステータスが `completed` になるまで `GET /v1/jobs/:id/status` をポーリングし、結果を `output_data` から読み取ります。

これらのルートは、下記で示す例外を除き、すべてのエディションで動作し、ベアラートークンを使います。[認証](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` が含まれることがあります。これは、そのボイスの動作が確認されているテキスト読み上げモデルです。アプリにモデルピッカーがない場合は、これを[**テキストから音声**（Text to Speech）](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 }` | クローンを使わずに、説明したボイスで話す音声です。 |

[**ボイスデザイン**（Voice Design）](https://nodaro.ai/docs/nodes/audio/voice-design)では、`text` は 100〜1,000 文字のプレビュー用の文で、`loudness` は -1〜1、`guidanceScale` は 0〜100 です。[**ボイスリミックス**（Voice Remix）](https://nodaro.ai/docs/nodes/audio/voice-remix)では、`text` は 1〜5,000 文字です。どちらもジョブを返します。

## 録音の声を変更する
`POST /v1/voice-changer` は、オーディオトラック、または話者が話している動画全体の声を、1 つのターゲットボイスに置き換えます。`audioUrl` か `videoUrl` のどちらか 1 つだけを送ってください。両方送った場合は、動画が優先されます。動画の場合、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` | いずれか 1 つが必須です。変更する録音です。 |
| `model` | speech-to-speech モデルです。 |
| `stability`、`similarityBoost` | 0〜1 です。話し方の安定度と、ターゲットのボイスにどれだけ近いかを指定します。 |
| `style` | 0〜1 で、デフォルトは 0 です。話し方を強調しますが、速度と安定度が多少犠牲になります。 |
| `useSpeakerBoost` | ターゲットのボイスへの似せ方をより際立たせます。わずかに処理が遅くなります。 |
| `seed` | 出力を再現可能にする整数です。 |
| `removeBackgroundNoise` | `true` の場合はクリーンな声だけを返し、`false` の場合は音楽や効果音を新しい声の下に残します。 |

料金とコツについては、[**ボイスチェンジャー**（Voice Changer）](https://nodaro.ai/docs/nodes/audio/voice-changer)を参照してください。

## 複数の話者の声を差し替える
[**ボイスチェンジャー Pro**（Voice Changer Pro）](https://nodaro.ai/docs/nodes/audio/voice-changer-pro)は、録音の中の話者をそれぞれ検出し、言葉とタイミングを保ったまま、話者ごとに異なるボイスを割り当てます。Nodaro Cloud 上で動作します。Nodaro Cloud に接続したセルフホスティング環境では、その接続を通じてワンショットでの声質変換を実行できますが、analyze と export のステップには 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 専用です。 |

### ボイスを話者にマッピングする
`orderedVoices` は、検出順に話者をマッピングします。話者 0 にはエントリー 0、話者 1 にはエントリー 1 が対応し、以降も同様です。エントリーは 1〜8 個です。リストの範囲を超えた話者は、自分の声のままになります。各エントリーは、次のいずれかです。

- **ボイス ID**：`"Rachel"` のような ID です。
- **`null`**：声を維持するスロットです。その話者は自分の声のままになり、後に続く話者はそれでも声が差し替えられます。声を維持するスロットは無料ですが、少なくとも 1 つのエントリーはボイスを指定する必要があります。
- **オブジェクト**：話者ごとの設定です。`{ voiceId, engine?, stability?, similarityBoost?, style?, useSpeakerBoost?, seed?, volumeMode?, volume? }`。`engine` は `sts`（デフォルト、speech-to-speech）または `v3` のいずれかです。`v3` は、文字起こしからそのセリフを ElevenLabs v3 でもう一度発話し直すもので、`stability` は 0、0.5、1 のいずれかしか指定できません。`volumeMode` は `match`（デフォルト）、`normalize`、`manual` のいずれかで、`manual` では `volume` を 0〜200% で指定します。

声を差し替える前に、Nodaro は必ず、声を音楽や効果音から分離します。残りのボディフィールドで、結果を調整します。

| フィールド | 意味 |
| --- | --- |
| `audioUrl` または `videoUrl` | いずれか 1 つが必須です。動画の場合、差し替えた音声は元の映像に戻されます。 |
| `preserveBackground` | 音楽や効果音を、新しい声の下にミックスして戻します。デフォルトは `true` です。 |
| `separationQuality` | `fast`（デフォルト、声をより多く残します）または `best`（より精細な分離）です。 |
| `removeBackgroundNoise` | 結果からさらにノイズを除去します。 |
| `musicVolumeMode`、`musicVolume` | 保持する背景音のレベルです。`match`（デフォルト）、`normalize`、または `manual`（0〜200% を指定）のいずれかです。 |
| `voiceFx` | 声だけにかけるリバーブやエコーです。`{ preset, wetDryMix?, delayMs?, decay? }`。`room`、`hall`、`church` などのリバーブプリセットは `wetDryMix` を使い、`echo` と `custom` は `delayMs` と `decay` を使います。 |
| `output` | `video`（デフォルト、完成した結果）または `stems`（自分でミックスするための、エフェクトもレベル調整もかけていないトラック）です。 |
| `analysis` | 以前の analyze ジョブの `output_data` です。声質変換で、検出をやり直す代わりに、その話者とステムを再利用します。 |

声質変換は、マッピングした話者ごとに課金されます。analyze と export は、一律料金です。料金については、[ボイスチェンジャー Pro](https://nodaro.ai/docs/nodes/audio/voice-changer-pro) を参照してください。

### インタラクティブなフロー：analyze、ステムへの声質変換、export
ワンショットの声質変換は、1 回の呼び出しで完成した動画をレンダリングします。インタラクティブなフローでは、声質変換の料金を払う前に話者を確認し、レンダリングする前に結果をミックスできます。

### 話者を検出する
`POST /v1/voice-changer-pro/analyze` に `audioUrl` または `videoUrl` を指定し、必要に応じて `separationQuality` と `suggestTitle` も指定します。完了したジョブの `output_data` には、分離された `vocalsUrl` と `backgroundUrl`、検出された `languageCode` と `languageProbability` が含まれます。`speakers` のリストには、話者ごとの `id`、`segments`、`firstStartSec`、`wordCount`、`snippet` が入ります。`suggestTitle` を指定した場合は、`suggestedTitle` も含まれます。拍手のような、人ではない「話者」が検出されることもあるので注意してください。

### ステムに声質変換する
`POST /v1/voice-changer-pro` に `output: "stems"` と、analysis の `output_data` 全体を `analysis` として指定します。声質変換は検出をスキップするため、検出の料金を再度払うことなく、別のボイスでもう一度声質変換できます。

### ミックスして書き出す
各ステムのレベルは、自分のインターフェースで設定します。これは無料です。次に、元の `videoUrl` と、最大 16 個の `tracks`（それぞれ `{ url, gain, muted, kind? }`）を指定して `POST /v1/voice-changer-pro/export` を呼びます。`gain` は 0〜200、`kind` は `voice` または `background` で、少なくとも 1 つのトラックはミュートを解除しておく必要があります。任意の `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 のページなど、吹き替えモデル自身が取得できる公開リンク）のいずれか 1 つだけを送ります。動画の場合、`output_data.videoUrl` に吹き替え済みの動画が返り、`output_data.audioUrl` には吹き替えたトラックだけが入ります。

| フィールド | 意味 |
| --- | --- |
| `targetLanguage` | 必須です。`es` や `pt-BR` のような ISO コードです。 |
| `sourceLanguage` | 話されている言語です。省略すると自動検出されます。 |
| `numSpeakers` | `0`（デフォルト）は自動検出します。話者数がわかっている場合は、1〜20 で指定すると分離の精度が上がります。 |
| `startTime`、`endTime` | ソースのこの区間だけを、秒単位で吹き替えます。 |
| `disableVoiceCloning`、`dropBackgroundAudio` | 各話者自身の声を再現しない、または音楽や効果音を取り除きます。 |
| `highestResolution` | 動画のソースの解像度を保ちます。 |
| `useProfanityFilter`、`targetAccent`、`watermark` | 不適切な言葉のフィルター、対象言語のアクセント、動画上への吹き替えモデルのウォーターマークです。 |

吹き替えの料金は、吹き替えた区間の分数単位で、最低 1 分からです。区間は最大 30 分で、それより長い場合は `413` が返されるため、`startTime` と `endTime` で区間を指定してください。[**吹き替え**（Dubbing）](https://nodaro.ai/docs/nodes/audio/dubbing)を参照してください。

## リンクから動画をインポートする
`POST /v1/download-video` は、SNS の動画（YouTube、TikTok、Instagram、X、Facebook）や、動画ファイルへの直接リンクを、あなたのストレージにインポートします。返るのはジョブ ID ではなく `{ downloadId }` です。完成したファイルは、ライブラリに保存されます。

| フィールド | 意味 |
| --- | --- |
| `url` | 必須です。インポートするページまたはファイルです。 |
| `maxHeight` | 解像度の上限で、たとえば `720` です。省略すると、利用できる最高の解像度になります。 |
| `sectionStartSec`、`sectionEndSec` | この範囲だけを秒単位でインポートします。両方送るか、どちらも送らないかのいずれかにしてください。 |
| `requireAudio` | 音声がない結果は、デフォルトで失敗します。多くの場合、ソースが劣化していることを意味するためです。無音のクリップを受け入れる場合は `false` を送ります。 |

ダウンロードの進行状況は、サーバー送信イベントのストリームである `GET /v1/download-video/progress/:downloadId` で追跡します。約 500 ミリ秒ごとに `{ phase, percent, videoUrl?, thumbnailUrl?, error? }` が送られ、`completed`（保存された `videoUrl` を伴う）または `failed`（`error` を伴う）で終わります。ダウンロードが終わった後、進行状況はごく短い間しか保持されないため、インポートが始まったらすぐに待ち受けを開始してください。1 アカウントにつき同時に実行できるダウンロードは最大 4 件で、5 件目は `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"
```

関連する 2 つのルートは、すぐに応答します。

- `POST /v1/video-metadata` に `{ url }` を送ると、動画をダウンロードせずに、長さ、サイズ、タイトル、ライブ配信中かどうかを読み取れます。一部の区間だけをインポートするかどうかを判断するのに使えます。
- `POST /v1/save-to-storage` に `{ mediaUrl, filename?, mediaType? }` を送ると、外部のメディア URL をサーバー上であなたのストレージにコピーします。ジョブを返します。

## 動画を編集する
| メソッド | パス | 説明 | 料金 |
| --- | --- | --- | --- |
| `POST` | `/v1/trim-video` | 動画を指定した範囲にカットします。 | [**動画のトリミング**（Trim Video）](https://nodaro.ai/docs/nodes/video/trim-video) |
| `POST` | `/v1/add-captions` | 動画に字幕を焼き込みます。 | [**字幕を追加**（Add Captions）](https://nodaro.ai/docs/nodes/video/add-captions) |
| `POST` | `/v1/still-to-video` | 1 枚の画像と 1 つのオーディオトラックから、MP4 を作ります。 | 0 クレジット |
| `POST` | `/v1/slideshow` | 2〜100 枚の画像と、任意のオーディオトラックから、MP4 を作ります。 | 0 クレジット |
| `POST` | `/v1/video-overlay` | 動画の上に、タイミング付きの画像レイヤーを 1〜20 個配置します。 | 20 クレジット |
| `POST` | `/v1/media/process` | 保存済みのファイルをカットまたはクロップし、すぐに応答します。 | 無料 |

### 動画をトリミングする
`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` の場合：字幕そのもので、動画全体にわたる 1 つの静的なブロックとして焼き込まれます。キネティックスタイルの場合：文字起こしで何も見つからなかったときのフォールバックとしてのみ使われます。 |
| `captions` | 単語ごとのタイミング付きエントリー `{ text, startMs, endMs }` で、キネティックスタイルでは単語ごとに 1 つ指定します。文字起こしの `words` は、そのまま使えます。 |
| `auto_transcribe`、`transcribe_provider` | ほかに単語が得られない場合に、音声を文字起こしします。キネティックスタイルには単語単位のタイミングが必要です：`incredibly-fast-whisper`（デフォルト）または `elevenlabs-stt` です。 |
| `look` | キネティックスタイルではデフォルトが `outline`、`subtitle` ではデフォルトが `clean` です。 |
| `maxWordsPerLine` | 1 行あたり 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)を参照してください。

### 静止画を動画にする
- **`POST /v1/still-to-video`** は `{ imageUrl, audioUrl, motion?, intensity?, resolution?, aspectRatio?, fps?, fit?, padColor? }` を受け取ります。動画の長さはオーディオと同じで、長さを指定するフィールドはありません。`motion` は `none`（デフォルト）、`zoom-in`、`zoom-out`、`pan-left`、`pan-right`、`ken-burns` のいずれかで、`intensity` は 1〜10 です。[**静止画から動画**（Still to Video）](https://nodaro.ai/docs/nodes/video/still-to-video)を参照してください。
- **`POST /v1/slideshow`** は `imageUrls`（2〜100 枚）、任意の `audioUrl`、`imageDurations`（画像ごとの秒数、自動の場合は `null`）または `perImageDuration`、`transition`、そして同じ見た目のフィールドを受け取ります。オーディオがある場合、スライドショーの長さはオーディオと同じになります。指定した長さの合計が一致しない場合は自動的に調整され、ジョブの出力にその旨が示されます。オーディオがない場合は、画像の枚数に `perImageDuration` を掛けた長さになり、無音になります。[**スライドショー**（Slideshow）](https://nodaro.ai/docs/nodes/video/slideshow)を参照してください。

どちらも、AI モデルを使わずにサーバー上でレンダリングされ、0 クレジットです。`resolution` は `720p`、`1080p`、`4K` のいずれか、`fps` は `24` または `30`、`fit: "contain"` にすると、クロップの代わりに `padColor` でレターボックスにします。

### 動画に画像を重ねる
`POST /v1/video-overlay` は、1 回のレンダリングで動画の上に 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 }` を返します。レイヤーの数にかかわらず、実行ごとに 20 クレジットで、1 ユーザーにつき 1 分あたり 30 リクエストまで送れます。[**動画オーバーレイ**（Video Overlay）](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` です。[**音源分離**（Audio Separation）](https://nodaro.ai/docs/nodes/audio/audio-separation)を参照してください。 |
| `POST` | `/v1/audio-isolation` | `{ audioUrl }` | メインの声を残し、背景音を取り除きます。[**音声抽出**（Voice Extractor）](https://nodaro.ai/docs/nodes/audio/voice-extractor)を参照してください。 |
| `POST` | `/v1/audio-fx` | `{ audioUrl, preset?, mix?, delayMs?, decay?, eqLow?, eqHigh? }` | リバーブ、エコー、電話、メガホンのいずれかの効果です。[**オーディオ FX**（Audio FX）](https://nodaro.ai/docs/nodes/audio/audio-fx)を参照してください。 |
| `POST` | `/v1/mix-audio` | `{ audioUrls, trackVolumes? }` | 2〜20 個のトラックを、それぞれ 0〜200% の音量で重ねます。[**オーディオをミックス**（Mix Audio）](https://nodaro.ai/docs/nodes/audio/mix-audio)を参照してください。 |
| `POST` | `/v1/adjust-volume` | `{ audioUrl or videoUrl, volume?, normalize?, fadeIn?, fadeOut? }` | レベルの変更、ノーマライズ、フェードを行います。[**音量調整**（Adjust Volume）](https://nodaro.ai/docs/nodes/audio/adjust-volume)を参照してください。 |
| `POST` | `/v1/combine-audio` | `{ segments: [{ url, startTime?, endTime? }] }` | セグメントを順につなぎます。[**オーディオを結合**（Combine Audio）](https://nodaro.ai/docs/nodes/audio/combine-audio)を参照してください。 |
| `POST` | `/v1/trim-audio` | `{ audioUrl or videoUrl, startTime?, endTime?, audioFormat? }` | オーディオをカットするか、動画から取り出します。形式は `mp3`（デフォルト）、`wav`、`aac` のいずれかです。[**オーディオのトリミング**（Trim Audio）](https://nodaro.ai/docs/nodes/audio/trim-audio)を参照してください。 |
| `POST` | `/v1/silence-detect` | `{ audioUrl, thresholdDb?, minSilenceMs?, padMs? }` | 録音の無音区間を見つけます。10 クレジットです。[**無音検出**（Silence Detect）](https://nodaro.ai/docs/nodes/audio/silence-detect)を参照してください。 |
| `POST` | `/v1/audio-sync` | `{ sources: [{ id, url }], reference? }` | 1 つの会話を録音した 2〜6 件の録音の間のオフセットを測定します。[**音声同期**（Audio Sync）](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` は、最初の 1 件を除くソースごとに 10 クレジットかかります。2 件で 10 クレジット、4 件で 30 クレジット、6 件で 50 クレジットです。ID は重複してはならず、`reference` はそのいずれか 1 つを指定します（デフォルトは最初のソースです）。`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`（1 単語につき 1 つの `{ 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 に公開する
接続フローはポップアップウィンドウを開くため、Web アプリでの利用を想定しています。公開は、個人用 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` | 今すぐ公開します。ジョブを返します。10 クレジットです。 |
| `POST` | `/v1/social/scheduled-posts` | 投稿をスケジュールします。10 クレジットで、公開時に課金されます。 |
| `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, … }` を受け取ります。2 種類の失敗は、それぞれ異なる対応が必要です。

- **`503 publish_retryable`**：何も投稿されていません。同一のリクエストを再送しても安全です。
- **`500 publish_failed`**：結果が不明です。再送すると二重に投稿される可能性があるため、先にネットワーク側を確認してください。

スケジュール投稿は `{ connectionId, action, scheduledAt, caption?, media? }` を受け取り、各メディア項目は `{ type, r2Key or url }` です。メディアは、このデプロイ環境に保存されたファイルである必要があります。投稿が送信されるときに、Nodaro がこれを新しいリンクに変換します。ほかのサイトへのリンクは拒否されます。すでに公開処理中の投稿を編集すると、`409 not_editable` が返されます。[**SNS に公開**（Publish to Social）](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`、`combine`、`transcribe` | `nodaro audio separate`、`isolate`、`fx`、`mix`、`adjust-volume`、`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`、キネティックな字幕スタイルが必要なスタイル関連フィールド、すべてミュートされた export などです。 |
| `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` | 投稿の結果が不明です。再送する前に、SNS 側を確認してください。 |
| `503` | `publish_retryable` | 何も投稿されていません。同一のリクエストを再送しても安全です。 |

## Frequently asked questions

### 1 本の動画に登場する複数の話者の声を変更するには、どうすればよいですか？

POST /v1/voice-changer-pro を orderedVoices とともに使い、検出された話者の順に 1 人につき 1 つのエントリーを指定します。null のエントリーを使うと、その話者は自分の声のままになります。先に話者を確認したい場合は、analyze のステップを実行してから、ステム出力で声質変換し、自分のミックスを書き出してください。

### API でボイスクローンを作成できますか？

いいえ。ボイスクローンは廃止されており、クローン用のルートは 410 voice_cloning_retired を返します。廃止前に作成したクローンは、これまでどおりボイス ID として使えます。代わりに、POST /v1/voice-design で、説明文から新しいボイスを設計してください。

### 単語単位のタイミングが得られる文字起こしエンジンはどれですか？

elevenlabs-stt は常に単語単位のタイミングを返します。incredibly-fast-whisper は、wordTimestamps を true にして送ると返します。whisper はフレーズ単位のみです。provider を省略すると whisper が実行されるため、単語単位のタイミングが必要な場合はエンジンを指定してください。

### 吹き替えできるクリップの長さは、どのくらいですか？

吹き替えの対象区間は最大 30 分です。料金は 1 分単位で、最低 1 分からです。ソースがそれより長い場合は、startTime と endTime で区間を指定して吹き替えてください。

### 無料で使えるメディアツールはどれですか？

静止画から動画（Still to Video）、スライドショー（Slideshow）、同期処理でカットとクロップを行う POST /v1/media/process は 0 クレジットです。動画オーバーレイ（Video Overlay）は実行ごとに 20 クレジット、無音検出（Silence Detect）は 10 クレジットです。
