diff --git a/dubbing/nextjs/quickstart/PROMPT.md b/dubbing/nextjs/quickstart/PROMPT.md index fa2530cc..af287078 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 in a single language. + ## 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, reference: "Browser dubbing demo" })`. The `targetLanguage` shortcut also queues a language target that starts generating 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 download URL that expires after about an hour; fetching the language again returns a fresh one. +- 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 `