audio-to-text-node
Version:
Backend audio file to text transcription using Web Speech API with Puppeteer
124 lines • 5.97 kB
JavaScript
import { exec, execSync } from "child_process";
import chalk from "chalk";
const audioRoutingConfig = {
speakerDevice: 'virtual_speaker',
speakerDeviceMonitor: 'virtual_speaker.monitor',
microphoneDevice: 'virtual_microphone',
originalDefaultSource: null,
modulesList: null,
nullSinkModuleId: null,
remapSourceModuleId: null,
};
/**
* Load the list of loaded modules.
*/
function loadModules() {
audioRoutingConfig.modulesList = execSync('pactl list short modules').toString().trim();
}
/**
* Get the module ID by name from the list of loaded modules.
* @param name The name to check for.
* @returns The module ID if found, otherwise null.
*/
function getModuleIdByName(name) {
if (!audioRoutingConfig.modulesList) {
loadModules();
}
if (audioRoutingConfig.modulesList) {
const line = audioRoutingConfig.modulesList.split('\n').find(line => line.includes(name));
if (line) {
const parts = line.split('\t');
if (parts.length > 0 && parts[0]) {
return parseInt(parts[0]);
}
}
}
return null;
}
/**
* Setup audio routing for virtual microphone and speaker.
* This function creates a null sink for the speaker and a remap source for the microphone.
* It also sets the default source to the virtual microphone.
* @param speakerDevice The name of the virtual speaker device.
* @param microphoneDevice The name of the virtual microphone device.
* @returns void
*/
export function setupAudioRouting(speakerDevice = audioRoutingConfig.speakerDevice, microphoneDevice = audioRoutingConfig.microphoneDevice) {
audioRoutingConfig.speakerDevice = speakerDevice;
audioRoutingConfig.microphoneDevice = microphoneDevice;
// Get and store the original default source (if available)
audioRoutingConfig.originalDefaultSource = execSync('pactl get-default-source').toString().trim();
if (audioRoutingConfig.originalDefaultSource) {
console.log(chalk.cyan(`- Original default source: ${audioRoutingConfig.originalDefaultSource}`));
}
else {
console.log(chalk.yellow("- No original default source detected."));
}
audioRoutingConfig.speakerDeviceMonitor = `${audioRoutingConfig.speakerDevice}.monitor`;
// Check for null-sink by sink_name and get its id
audioRoutingConfig.nullSinkModuleId = getModuleIdByName(`sink_name=${audioRoutingConfig.speakerDevice}`);
if (audioRoutingConfig.nullSinkModuleId == null) {
console.log(chalk.blue("- Loading module-null-sink..."));
audioRoutingConfig.nullSinkModuleId = parseInt(execSync(`pactl load-module module-null-sink sink_name=${audioRoutingConfig.speakerDevice}`).toString().trim());
}
// Check for remap-source by source_name and get its id
audioRoutingConfig.remapSourceModuleId = getModuleIdByName(`source_name=${audioRoutingConfig.microphoneDevice}`);
if (audioRoutingConfig.remapSourceModuleId == null) {
console.log(chalk.blue("- Loading module-remap-source..."));
audioRoutingConfig.remapSourceModuleId = parseInt(execSync(`pactl load-module module-remap-source master=${audioRoutingConfig.speakerDeviceMonitor} source_name=${audioRoutingConfig.microphoneDevice}`).toString().trim());
}
if (audioRoutingConfig.microphoneDevice != audioRoutingConfig.originalDefaultSource) {
console.log(chalk.blue(`- Setting default source to ${audioRoutingConfig.microphoneDevice}...`));
execSync(`pactl set-default-source ${audioRoutingConfig.microphoneDevice}`);
}
console.log(chalk.green("✔ Virtual speaker and microphone setup complete."));
}
/**
* Play audio using the configured virtual speaker device.
* @param filePath The path to the audio file to play.
* @returns Promise<void>
*/
export async function playAudio(filePath) {
return new Promise((resolve, reject) => {
exec(`paplay --device=${audioRoutingConfig.speakerDevice} "${filePath}"`, (error) => {
if (error) {
console.error(chalk.red("- Error playing audio:"), error);
reject(error);
}
else {
console.log(chalk.green("✔ Audio playback finished."));
resolve();
}
});
});
}
/**
* Restore audio routing by restoring the original default source and unloading virtual modules.
* @returns void
*/
export function restoreAudioRouting() {
// Restore the original default source if it exists
if (audioRoutingConfig.originalDefaultSource) {
execSync(`pactl set-default-source ${audioRoutingConfig.originalDefaultSource}`);
console.log(chalk.cyan(`- Restored original default source: ${audioRoutingConfig.originalDefaultSource}`));
}
// must be reloaded to ensure the latest state
loadModules();
// Unload remap-source module if loaded
if (audioRoutingConfig.remapSourceModuleId != null &&
getModuleIdByName(`${audioRoutingConfig.remapSourceModuleId}\tmodule-remap-source\tmaster=${audioRoutingConfig.speakerDeviceMonitor} source_name=${audioRoutingConfig.microphoneDevice}`)) {
execSync(`pactl unload-module ${audioRoutingConfig.remapSourceModuleId}`);
console.log(chalk.cyan(`- Unloaded module-remap-source: ${audioRoutingConfig.remapSourceModuleId}`));
audioRoutingConfig.remapSourceModuleId = null;
}
// must be reloaded to ensure the latest state
loadModules();
// Unload null-sink module if loaded
if (audioRoutingConfig.nullSinkModuleId != null &&
getModuleIdByName(`${audioRoutingConfig.nullSinkModuleId}\tmodule-null-sink\tsink_name=${audioRoutingConfig.speakerDevice}`)) {
execSync(`pactl unload-module ${audioRoutingConfig.nullSinkModuleId}`);
console.log(chalk.cyan(`- Unloaded module-null-sink: ${audioRoutingConfig.nullSinkModuleId}`));
audioRoutingConfig.nullSinkModuleId = null;
}
}
//# sourceMappingURL=routing.js.map