From 8c6cec1d3a2da11c6757615e05c653498cbc3188 Mon Sep 17 00:00:00 2001 From: AssemblyAI Date: Thu, 20 Jun 2024 13:58:58 +0300 Subject: [PATCH] Project import generated by Copybara. GitOrigin-RevId: 7996449a6959276eaee83b691e7f2cce01e63f9f --- .eslintrc.json | 2 +- samples/streaming-stt-from-mic/.env.sample | 1 + samples/streaming-stt-from-mic/README.md | 30 + samples/streaming-stt-from-mic/index.js | 47 ++ samples/streaming-stt-from-mic/index.ts | 61 ++ .../streaming-stt-from-mic/package-lock.json | 551 ++++++++++++++++++ samples/streaming-stt-from-mic/package.json | 19 + samples/streaming-stt-from-mic/sox.js | 100 ++++ samples/streaming-stt-from-mic/sox.ts | 134 +++++ samples/streaming-stt-from-mic/tsconfig.json | 13 + 10 files changed, 957 insertions(+), 1 deletion(-) create mode 100644 samples/streaming-stt-from-mic/.env.sample create mode 100644 samples/streaming-stt-from-mic/README.md create mode 100644 samples/streaming-stt-from-mic/index.js create mode 100644 samples/streaming-stt-from-mic/index.ts create mode 100644 samples/streaming-stt-from-mic/package-lock.json create mode 100644 samples/streaming-stt-from-mic/package.json create mode 100644 samples/streaming-stt-from-mic/sox.js create mode 100644 samples/streaming-stt-from-mic/sox.ts create mode 100644 samples/streaming-stt-from-mic/tsconfig.json diff --git a/.eslintrc.json b/.eslintrc.json index b3ad3e9..5874c1a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,7 +9,7 @@ "rules": { "tsdoc/syntax": "warn" }, - "ignorePatterns": ["/*.js", "/*.ts", "dist", "node_modules"], + "ignorePatterns": ["/*.js", "/*.ts", "samples", "dist", "node_modules"], "overrides": [ { "files": ["tests/**/*"], diff --git a/samples/streaming-stt-from-mic/.env.sample b/samples/streaming-stt-from-mic/.env.sample new file mode 100644 index 0000000..c87154a --- /dev/null +++ b/samples/streaming-stt-from-mic/.env.sample @@ -0,0 +1 @@ +ASSEMBLYAI_API_KEY=[YOUR_ASSEMBLYAI_API_KEY] diff --git a/samples/streaming-stt-from-mic/README.md b/samples/streaming-stt-from-mic/README.md new file mode 100644 index 0000000..e962a1c --- /dev/null +++ b/samples/streaming-stt-from-mic/README.md @@ -0,0 +1,30 @@ +# Transcribe streaming audio from a microphone in TypeScript + +This sample lets you transcribe audio from your microphone in real time using AssemblyAI Streaming Speech-to-Text. +For step-by-step instructions on how to build this sample yourself, see [Transcribe streaming audio from a microphone in TypeScript](https://www.assemblyai.com/docs/getting-started/transcribe-streaming-audio-from-a-microphone/typescript). + +To run the sample, you'll need the following: + +- [Node.js](https://nodejs.org/) +- [SoX](https://sourceforge.net/projects/sox/) +- An AssemblyAI account with a credit card set up + +Install the dependencies: + +```bash +npm install +``` + +Configure the `ASSEMBLYAI_API_KEY` environment variable in your shell, or create a `.env` file with the following contents and replace `[YOUR_ASSEMBLYAI_API_KEY]` with your API key: + +```plaintext +ASSEMBLYAI_API_KEY=[YOUR_ASSEMBLYAI_API_KEY] +``` + +Run the sample: + +```bash +npm run start +``` + +Credits: `sox.ts` is adapted from the [node-record-lpcm16](https://github.com/gillesdemey/node-record-lpcm16) project by Gilles De Mey. diff --git a/samples/streaming-stt-from-mic/index.js b/samples/streaming-stt-from-mic/index.js new file mode 100644 index 0000000..8367e7c --- /dev/null +++ b/samples/streaming-stt-from-mic/index.js @@ -0,0 +1,47 @@ +import "dotenv/config"; +import { AssemblyAI } from "assemblyai"; +import { SoxRecording } from "./sox.js"; +const SAMPLE_RATE = 16000; +const client = new AssemblyAI({ + apiKey: process.env.ASSEMBLYAI_API_KEY, +}); +const transcriber = client.realtime.transcriber({ + sampleRate: SAMPLE_RATE, +}); +transcriber.on("open", ({ sessionId }) => { + console.log(`Session opened with ID: ${sessionId}`); +}); +transcriber.on("error", (error) => { + console.error("Error:", error); +}); +transcriber.on("close", (code, reason) => + console.log("Session closed:", code, reason), +); +transcriber.on("transcript", (transcript) => { + if (!transcript.text) { + return; + } + if (transcript.message_type === "PartialTranscript") { + console.log("Partial:", transcript.text); + } else { + console.log("Final:", transcript.text); + } +}); +console.log("Connecting to real-time transcript service"); +await transcriber.connect(); +console.log("Starting recording"); +const recording = new SoxRecording({ + channels: 1, + sampleRate: SAMPLE_RATE, + audioType: "wav", // Linear PCM +}); +recording.stream().pipeTo(transcriber.stream()); +// Stop recording and close connection using Ctrl-C. +process.on("SIGINT", async function () { + console.log(); + console.log("Stopping recording"); + recording.stop(); + console.log("Closing real-time transcript connection"); + await transcriber.close(); + process.exit(); +}); diff --git a/samples/streaming-stt-from-mic/index.ts b/samples/streaming-stt-from-mic/index.ts new file mode 100644 index 0000000..e546637 --- /dev/null +++ b/samples/streaming-stt-from-mic/index.ts @@ -0,0 +1,61 @@ +import "dotenv/config"; +import { AssemblyAI, RealtimeTranscript } from "assemblyai"; +import { SoxRecording } from "./sox.js"; + +const SAMPLE_RATE = 16_000; + +const client = new AssemblyAI({ + apiKey: process.env.ASSEMBLYAI_API_KEY!, +}); + +const transcriber = client.realtime.transcriber({ + sampleRate: SAMPLE_RATE, +}); + +transcriber.on("open", ({ sessionId }) => { + console.log(`Session opened with ID: ${sessionId}`); +}); + +transcriber.on("error", (error: Error) => { + console.error("Error:", error); +}); + +transcriber.on("close", (code: number, reason: string) => + console.log("Session closed:", code, reason), +); + +transcriber.on("transcript", (transcript: RealtimeTranscript) => { + if (!transcript.text) { + return; + } + + if (transcript.message_type === "PartialTranscript") { + console.log("Partial:", transcript.text); + } else { + console.log("Final:", transcript.text); + } +}); + +console.log("Connecting to real-time transcript service"); +await transcriber.connect(); + +console.log("Starting recording"); +const recording = new SoxRecording({ + channels: 1, + sampleRate: SAMPLE_RATE, + audioType: "wav", // Linear PCM +}); + +recording.stream().pipeTo(transcriber.stream()); + +// Stop recording and close connection using Ctrl-C. +process.on("SIGINT", async function () { + console.log(); + console.log("Stopping recording"); + recording.stop(); + + console.log("Closing real-time transcript connection"); + await transcriber.close(); + + process.exit(); +}); diff --git a/samples/streaming-stt-from-mic/package-lock.json b/samples/streaming-stt-from-mic/package-lock.json new file mode 100644 index 0000000..9c81a0d --- /dev/null +++ b/samples/streaming-stt-from-mic/package-lock.json @@ -0,0 +1,551 @@ +{ + "name": "streaming-stt-from-mic", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "streaming-stt-from-mic", + "version": "1.0.0", + "dependencies": { + "assemblyai": "^4.5.0", + "dotenv": "^16.4.5" + }, + "devDependencies": { + "@types/node": "^20.14.5", + "tsx": "^4.15.6", + "typescript": "^5.4.5" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/node": { + "version": "20.14.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.5.tgz", + "integrity": "sha512-aoRR+fJkZT2l0aGOJhuA8frnCSoNX6W7U2mpNq63+BxBIj5BQFt8rHy627kijCmm63ijdSdwvGgpUsU6MBsZZA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/assemblyai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/assemblyai/-/assemblyai-4.5.0.tgz", + "integrity": "sha512-5GnxD5NAvw+bwUyNDUh3UlqND4XnHiLzog4ObCgGrTcZMSBPRGJ3aqTWKemoeVPu7H7lJaAdQvLtaqMkNKiy2Q==", + "dependencies": { + "ws": "^8.17.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.15.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.15.6.tgz", + "integrity": "sha512-is0VQQlfNZRHEuSSTKA6m4xw74IU4AizmuB6lAYLRt9XtuyeQnyJYexhNZOPCB59SqC4JzmSzPnHGBXxf3k0hA==", + "dev": true, + "dependencies": { + "esbuild": "~0.21.4", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/samples/streaming-stt-from-mic/package.json b/samples/streaming-stt-from-mic/package.json new file mode 100644 index 0000000..e9dd990 --- /dev/null +++ b/samples/streaming-stt-from-mic/package.json @@ -0,0 +1,19 @@ +{ + "name": "streaming-stt-from-mic", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "run": "tsx index.ts", + "start": "tsx index.ts" + }, + "devDependencies": { + "@types/node": "^20.14.5", + "tsx": "^4.15.6", + "typescript": "^5.4.5" + }, + "dependencies": { + "assemblyai": "^4.5.0", + "dotenv": "^16.4.5" + } +} diff --git a/samples/streaming-stt-from-mic/sox.js b/samples/streaming-stt-from-mic/sox.js new file mode 100644 index 0000000..c9028d4 --- /dev/null +++ b/samples/streaming-stt-from-mic/sox.js @@ -0,0 +1,100 @@ +// This code is a simplified and typed version adapted from the 'node-record-lpcm16' project by Gilles De Mey. +// Original source code: https://github.com/gillesdemey/node-record-lpcm16 +import { ok as assert } from "assert"; +import { spawn } from "child_process"; +import { Readable } from "stream"; +const debug = + !!process.env.DEBUG && process.env.DEBUG.indexOf("record") !== -1 + ? console.debug + : () => {}; +export class SoxRecording { + options; + process; + soxStream; + constructor(options = {}) { + const defaults = { + sampleRate: 16000, + channels: 1, + compress: false, + threshold: 0.5, + silence: "1.0", + recorder: "sox", + endOnSilence: false, + audioType: "wav", + }; + this.options = Object.assign(defaults, options); + debug("Started recording"); + debug(this.options); + return this.start(); + } + start() { + const cmd = "sox"; + const args = [ + "--default-device", + "--no-show-progress", + "--rate", + this.options.sampleRate.toString(), + "--channels", + this.options.channels.toString(), + "--encoding", + "signed-integer", + "--bits", + "16", + "--type", + this.options.audioType, + "-", // pipe + ]; + debug(` ${cmd} ${args.join(" ")}`); + const cp = spawn(cmd, args, { + encoding: "binary", + stdio: "pipe", + }); + const rec = cp.stdout; + const err = cp.stderr; + this.process = cp; // expose child process + this.soxStream = cp.stdout; // expose output stream + cp.on("close", (code) => { + if (code === 0) return; + rec?.emit( + "error", + `${cmd} has exited with error code ${code}. + +Enable debugging with the environment variable debug=record.`, + ); + }); + err?.on("data", (chunk) => { + debug(`STDERR: ${chunk}`); + }); + rec?.on("data", (chunk) => { + debug(`Recording ${chunk.length} bytes`); + }); + rec?.on("end", () => { + debug("Recording ended"); + }); + return this; + } + stop() { + assert(this.process, "Recording not yet started"); + this.process.kill(); + } + pause() { + assert(this.process, "Recording not yet started"); + this.process.kill("SIGSTOP"); + this.soxStream?.pause(); + debug("Paused recording"); + } + resume() { + assert(this.process, "Recording not yet started"); + this.process.kill("SIGCONT"); + this.soxStream?.resume(); + debug("Resumed recording"); + } + isPaused() { + assert(this.process, "Recording not yet started"); + return this.soxStream?.isPaused(); + } + stream() { + assert(this?.soxStream, "Recording not yet started"); + return Readable.toWeb(this?.soxStream); + } +} diff --git a/samples/streaming-stt-from-mic/sox.ts b/samples/streaming-stt-from-mic/sox.ts new file mode 100644 index 0000000..f64f2d1 --- /dev/null +++ b/samples/streaming-stt-from-mic/sox.ts @@ -0,0 +1,134 @@ +// This code is a simplified and typed version adapted from the 'node-record-lpcm16' project by Gilles De Mey. +// Original source code: https://github.com/gillesdemey/node-record-lpcm16 + +import { ok as assert } from "assert"; +import { ChildProcess, ExecFileOptions, spawn } from "child_process"; +import { Readable } from "stream"; +import { ReadableStream } from "stream/web"; + +export type SoxRecordingOptions = { + sampleRate: number; + channels: number; + compress: boolean; + threshold: number; + silence: string; + endOnSilence: boolean; + audioType: string; +}; + +const debug = + !!process.env.DEBUG && process.env.DEBUG.indexOf("record") !== -1 + ? console.debug + : () => {}; + +export class SoxRecording { + private options: SoxRecordingOptions; + private process: ChildProcess | undefined; + private soxStream: Readable | null | undefined; + + constructor(options = {}) { + const defaults = { + sampleRate: 16000, + channels: 1, + compress: false, + threshold: 0.5, + silence: "1.0", + recorder: "sox", + endOnSilence: false, + audioType: "wav", + }; + + this.options = Object.assign(defaults, options); + + debug("Started recording"); + debug(this.options); + + return this.start(); + } + + start() { + const cmd = "sox"; + const args = [ + "--default-device", + "--no-show-progress", // show no progress + "--rate", + this.options.sampleRate.toString(), // sample rate + "--channels", + this.options.channels.toString(), // channels + "--encoding", + "signed-integer", // sample encoding + "--bits", + "16", // precision (bits) + "--type", + this.options.audioType, // audio type + "-", // pipe + ]; + debug(` ${cmd} ${args.join(" ")}`); + + const cp = spawn(cmd, args, { + encoding: "binary", + stdio: "pipe", + } as ExecFileOptions); + const rec = cp.stdout; + const err = cp.stderr; + + this.process = cp; // expose child process + this.soxStream = cp.stdout; // expose output stream + + cp.on("close", (code) => { + if (code === 0) return; + rec?.emit( + "error", + `${cmd} has exited with error code ${code}. + +Enable debugging with the environment variable debug=record.`, + ); + }); + + err?.on("data", (chunk) => { + debug(`STDERR: ${chunk}`); + }); + + rec?.on("data", (chunk) => { + debug(`Recording ${chunk.length} bytes`); + }); + + rec?.on("end", () => { + debug("Recording ended"); + }); + + return this; + } + + stop() { + assert(this.process, "Recording not yet started"); + this.process.kill(); + } + + pause() { + assert(this.process, "Recording not yet started"); + + this.process.kill("SIGSTOP"); + this.soxStream?.pause(); + debug("Paused recording"); + } + + resume() { + assert(this.process, "Recording not yet started"); + + this.process.kill("SIGCONT"); + this.soxStream?.resume(); + debug("Resumed recording"); + } + + isPaused() { + assert(this.process, "Recording not yet started"); + + return this.soxStream?.isPaused(); + } + + stream(): ReadableStream { + assert(this?.soxStream, "Recording not yet started"); + return Readable.toWeb(this?.soxStream); + } +} diff --git a/samples/streaming-stt-from-mic/tsconfig.json b/samples/streaming-stt-from-mic/tsconfig.json new file mode 100644 index 0000000..87dfb9d --- /dev/null +++ b/samples/streaming-stt-from-mic/tsconfig.json @@ -0,0 +1,13 @@ +{ + "include": ["./*.ts", "./**/*.ts"], + "compilerOptions": { + "rootDir": ".", + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +}