From 7ca1514995e7bca43dbe3b7aa94f3694c32d2d52 Mon Sep 17 00:00:00 2001 From: Tadas Petra <60107328+tadaspetra@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:38:19 -0500 Subject: [PATCH 1/2] use the new projects api --- dubbing/nextjs/quickstart/PROMPT.md | 37 +++++---- dubbing/nextjs/quickstart/README.md | 2 +- dubbing/nextjs/quickstart/example/README.md | 2 +- .../[dubbingId]/audio/[languageCode]/route.ts | 83 ------------------- .../[projectId]/audio/[languageId]/route.ts | 71 ++++++++++++++++ .../{[dubbingId] => [projectId]}/route.ts | 28 +++++-- .../example/app/api/dubbing/route.ts | 15 ++-- .../nextjs/quickstart/example/app/page.tsx | 61 +++++++------- .../nextjs/quickstart/example/package.json | 2 +- 9 files changed, 154 insertions(+), 147 deletions(-) delete mode 100644 dubbing/nextjs/quickstart/example/app/api/dubbing/[dubbingId]/audio/[languageCode]/route.ts create mode 100644 dubbing/nextjs/quickstart/example/app/api/dubbing/[projectId]/audio/[languageId]/route.ts rename dubbing/nextjs/quickstart/example/app/api/dubbing/{[dubbingId] => [projectId]}/route.ts (60%) diff --git a/dubbing/nextjs/quickstart/PROMPT.md b/dubbing/nextjs/quickstart/PROMPT.md index fa2530cc..0cdb1201 100644 --- a/dubbing/nextjs/quickstart/PROMPT.md +++ b/dubbing/nextjs/quickstart/PROMPT.md @@ -1,33 +1,36 @@ Before writing any code, invoke the `/text-to-speech` skill to learn the correct ElevenLabs SDK patterns. +This example uses the Dubbing Projects API (dubbing v2): a **project** holds the source media and its transcript, and each **language target** produces one dubbed output. + ## 1. `app/api/dubbing/route.ts` -Secure POST endpoint that starts a dubbing job from an uploaded recording. +Secure POST endpoint that starts a dubbing project from an uploaded recording. - Read `ELEVENLABS_API_KEY` from `process.env`. Return 500 if missing. - Accept `audio` (File), `targetLang` (string), and optional `sourceLang` (string, default `auto`) from request `FormData`. - Return 400 for missing or invalid audio, or a missing `targetLang`. -- Use `ElevenLabsClient` and call `client.dubbing.create({ file: audio, targetLang, sourceLang: sourceLang === "auto" ? undefined : sourceLang, name: "Browser dubbing demo" })`. -- Read the job id from the SDK response (`dubbingId`) and return JSON `{ dubbingId, expectedDurationSec }`. +- Use `ElevenLabsClient` and call `client.dubbing.project.create({ file: audio, targetLanguage: targetLang, sourceLanguage: sourceLang === "auto" ? undefined : sourceLang, modelId: "dubbing_v2", reference: "Browser dubbing demo" })`. The `targetLanguage` shortcut also queues a language target that starts automatically once the project finishes transcribing. +- Return JSON `{ projectId, languageId }`, reading `languageId` from `project.languageIds?.[0] ?? null`. - Wrap failures in readable JSON errors. -## 2. `app/api/dubbing/[dubbingId]/route.ts` +## 2. `app/api/dubbing/[projectId]/route.ts` -Secure GET endpoint that returns dubbing status metadata for polling. +Secure GET endpoint that returns combined project and language status for polling. -- Read and validate `dubbingId` from the route params. -- Call `client.dubbing.get(dubbingId)`. -- Return JSON with `status`, `error`, `sourceLanguage`, and `targetLanguages`. +- Read and validate `projectId` from the route params. +- Call `client.dubbing.project.get(projectId)`. Project statuses are `queued`, `preparing`, `processing`, `ready`, or `failed`. +- If the project has a language target (`project.languageIds?.[0]`), also call `client.dubbing.project.language.get(projectId, languageId)`. Language statuses are `queued`, `processing`, `completed`, `stale`, or `failed`. +- Return JSON with `projectStatus`, `languageId`, and `languageStatus`. - Keep the response small and friendly for client polling. -## 3. `app/api/dubbing/[dubbingId]/audio/[languageCode]/route.ts` +## 3. `app/api/dubbing/[projectId]/audio/[languageId]/route.ts` -Secure GET endpoint that proxies the dubbed audio file. +Secure GET endpoint that proxies the dubbed audio output. -- Read and validate `dubbingId` and `languageCode` from the route params. -- Call `client.dubbing.audio.get(dubbingId, languageCode)`. -- Collect the returned stream into a `Buffer` and respond with `audio/mpeg`. -- Return readable JSON errors when the dub is not ready or fails. +- Read and validate `projectId` and `languageId` from the route params. +- Call `client.dubbing.project.language.get(projectId, languageId)`. Once the language is `completed`, `outputs.losslessAudio` carries a signed, time-limited download URL. +- Return 503 with a readable JSON error if the language is not `completed` yet or has no output URL. +- Fetch the signed URL server-side and stream the body back with the upstream content type (default `audio/wav`). ## 4. `app/page.tsx` @@ -38,8 +41,8 @@ In-browser voice recorder and dubbing page. - After stopping, convert the recorded blob to a WAV `File` in the browser before upload. Do not send raw `audio/webm;codecs=opus` to `/api/dubbing`, because the Dubbing API rejects that content type. - Show clear states: idle, recording, preparing, polling, ready, and error. While recording, show elapsed time and a pulsing red indicator. - After recording, show the original audio player plus source-language and target-language selects. Prevent choosing the same explicit source and target language. -- On **Dub Recording**, `POST` `FormData` with the converted WAV file to `/api/dubbing`. -- Poll `/api/dubbing/${dubbingId}` every 5 seconds until the status is `dubbed`; stop early and show the API error if one is returned. -- When ready, fetch `/api/dubbing/${dubbingId}/audio/${targetLang}`, create an object URL, and render a dubbed `