What actually happens
Four steps, and not one of them is an upload.
- Your video stays in this tab. Choosing a file does not send it anywhere — there is no server here to send it to.
- FFmpeg arrives as WebAssembly, on demand — about 32 MB, the first time you
press the button, then cached by your browser. A visitor who only reads this page
downloads none of it. It is fetched from
unpkg.comvia SGraph'ssg-video.js. - The audio track is lifted out, not re-made:
-vn -c:a copydrops the picture and copies the sound across untouched, so it is fast and loses nothing. The result is an.m4a. - Then it is yours — play it, download it, or hand it to the app for transcription without it ever touching a disk.
When it has to re-encode. Copying only works if
the audio can live in an MP4 container. A phone or WhatsApp video is AAC and copies
perfectly; a screen recording or a web download is usually Opus in WebM, which cannot
be copied into .m4a. Rather than tell you the file has no sound — which is what
the underlying error says, and it is wrong — this tool notices, re-encodes to AAC, and says
so on the result.
How this works, in full →
the whole sequence from pressing the button to the .m4a, why WebAssembly
is what makes a tool with no server possible, what it costs, and the two failures
this design produced.
For agents: the JS API
The page publishes window.__tool — the same
SgToolApi contract the app and the
text-to-speech tool use.
checking…
| Action | params | returns |
|---|---|---|
extractAudio | { file, withAudio?, onProgress? } |
{ filename, mime, bytes, sourceName, reencoded, tookMs } — plus base64 when withAudio is set |
prepare | {} | loads the 32 MB core up front, so a later call is instant |
isSupported | {} | { supported } — WebAssembly available |
probe | { file } | duration and dimensions, without loading FFmpeg |
getLastAudio | {} | the last result's metadata |
saveLastAudio | { filename? } | a real download Playwright can capture |
sendToApp | {} | hands the audio to /app/ for transcription |
Audio is opt-in here. A film's soundtrack can be
tens of megabytes and base64 makes it a third bigger again, so extractAudio
returns metadata by default. Ask for withAudio: true when you want the bytes, or
use saveLastAudio and catch the download.
// Playwright: a video on disk, its audio out, no UI involved.
await page.goto('https://voicedebrief.ai/tools/extract-audio/')
await page.waitForFunction(() => window.__tool)
await page.setInputFiles('#file', 'holiday.mp4')
const r = await page.evaluate(() => window.__tool.extractAudio({
file: document.getElementById('file').files[0], withAudio: true }))
console.log(r.filename, r.bytes, r.reencoded) // reencoded === true means it was not AAC
await writeFile('holiday.m4a', Buffer.from(r.base64, 'base64'))