編集
TypeScript から、ポッドキャストや長い動画を編集します。無音を検出し、複数の録音を同期し、文字起こしからカットをプランし、編集決定リストをレンダリングします。
client.edit には、ポッドキャストや長い動画のための編集ツールがまとまっています。無音を見つけ、複数の録音がどれだけずれているかを測り、文字起こしからカットをプランし、編集決定リスト(EDL)を完成した動画ファイルまたは音声ファイルにレンダリングします。4 つのメソッドはジョブを開始して { jobId } を返し、client.jobs.getStatus() でポーリングします。5 つ目の remapTranscript() は、リクエストなしでローカルに実行されます。
メソッド
| メソッド | エンドポイント | 内容 |
|---|---|---|
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) | なし、ローカル | 文字起こしのタイミングを、編集後のタイムラインに移します |
コードでポッドキャストを編集する
録音を文字起こしする
単語単位のタイミングを返すエンジンを指定して、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)同じ手順が、ノードとしても存在します。文字起こし(Transcribe)、無音検出(Silence Detect)、編集プラン(Edit Plan)、EDL 適用(Apply EDL)です。
client.edit
edit.silenceDetect(input)
オーディオまたは動画のソースから、無音の範囲を見つけます。サーバー上で、AI モデルなしに実行されます。
silenceDetect(input: SilenceDetectInput): Promise<{ jobId: string }>Prop
Type
const { jobId } = await client.edit.silenceDetect({ audioUrl: masterUrl, minSilenceMs: 900 })完了したジョブの output_data.json は、SilenceRanges オブジェクトです。{ version, ranges: [{ startMs, endMs }], durationMs } です。このオブジェクト全体を、silence として editPlan() に渡します。
edit.audioSync(input)
1 つの会話を収録した 2〜6 件の録音について、その音から、時計がどれだけずれているかを測ります。音声同期(Audio Sync)ノードと同じ処理です。サーバー上で、AI モデルなしに実行されます。料金は 10 × (sources − 1) クレジットです。2 件のソースで 10、4 件で 30、6 件で 50 です。
audioSync(input: AudioSyncInput): Promise<{ jobId: string }>Prop
Type
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)
タイミング付きの文字起こしから、編集をプランします。編集プランノードと同じ処理です。3 種類のプランのいずれかを作成します。録音全体を詰めたカット、短いクリップのセット、チャプターです。
editPlan(input: EditPlanInput): Promise<{ jobId: string }>Prop
Type
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 モードでは Edl を、clips モードでは Edl の配列を、chapters モードでは ChapterSet を返します。
セルフホスティング環境では、このメソッドに Nodaro Cloud への接続が必要です。接続がない場合、503 nodaro_connection_required で失敗します。
edit.applyEdl(input)
編集決定リストを、動画ファイルまたは音声ファイルにレンダリングします。EDL 適用ノードと同じ処理です。
applyEdl(input: ApplyEdlInput): Promise<{ jobId: string }>Prop
Type
const { jobId } = await client.edit.applyEdl({ edl, output: "video", quality: "proxy", crossfadeMs: 80 })EDL は、クレジットが確保される前にチェックされます。不明なソース、動画編集で映像のないセグメント、1 回のレンダリングで出力が 180 分を超える場合のいずれかは、code が invalid_edl の NodaroError で失敗します。長さは、クロスフェードを適用した後で測られます。message には、問題の内容が示されます。たとえば、編集が 200 分になり、最大 180 分の部分に分割する必要がある、といった内容です。
edit.remapTranscript(edl, transcript)
文字起こしを、リクエストなしにローカルで、編集のタイムラインに移します。カットされた範囲内の単語は取り除かれ、カットをまたぐ単語は切り詰められ、すべてのタイミングが編集後の出力に合わせてずらされます。transcript を渡した場合、applyEdl() はサーバー上で同じ処理を行います。
remapTranscript(edl: Edl, transcript: Transcript): TranscriptProp
Type
const editedTranscript = client.edit.remapTranscript(edl, transcript)
// caption the edited video without another transcriptionレンダリングや 2 回目の文字起こしなしに、編集に字幕を付けるために使います。新しいタイミングだけが必要な場合、大きな文字起こしに対しては、こちらのほうが高速な選択肢でもあります。
よくある質問
関連ページ
ボイスとオーディオ
メディアとアップロード
編集プラン
EDL 適用
無音検出
最終更新