echogarden
Version:
An easy-to-use speech toolset. Includes tools for synthesis, recognition, alignment, speech translation, language detection, source separation and more.
217 lines • 7.31 kB
JavaScript
import { spawn } from 'child_process';
import { encodeRawAudioToWave, decodeWaveToRawAudio } from '../audio/AudioUtilities.js';
import { Logger } from '../utilities/Logger.js';
import { commandExists, concatUint8Arrays, isUint8Array, logToStderr } from '../utilities/Utilities.js';
import { loadPackage } from '../utilities/PackageManager.js';
import { getGlobalOption } from '../api/GlobalOptions.js';
import { existsSync } from '../utilities/FileSystem.js';
import { joinPath } from '../utilities/PathUtilities.js';
const log = logToStderr;
export async function encodeFromChannels(rawAudio, outputOptions) {
return transcode(encodeRawAudioToWave(rawAudio), outputOptions);
}
export async function decodeToChannels(input, outSampleRate, outChannelCount) {
const outputOptions = {
codec: 'pcm_f32le',
format: 'wav',
sampleRate: outSampleRate,
channelCount: outChannelCount,
audioOnly: true
};
const waveAudio = await transcode(input, outputOptions);
const logger = new Logger();
logger.start(`Convert wave buffer to raw audio`);
const { rawAudio } = decodeWaveToRawAudio(waveAudio);
logger.end();
return rawAudio;
}
export async function transcode(input, outputOptions) {
const executablePath = await getFFMpegExecutablePath();
if (!executablePath) {
throw new Error(`The ffmpeg utility wasn't found. Please ensure it is available on the system path.`);
}
return transcode_CLI(executablePath, input, outputOptions);
}
async function transcode_CLI(ffmpegCommand, input, outputOptions) {
return new Promise((resolve, reject) => {
const logger = new Logger();
logger.start('Transcode with command-line ffmpeg');
const args = buildCommandLineArguments(isUint8Array(input) ? '-' : input, outputOptions);
const process = spawn(ffmpegCommand, args);
if (isUint8Array(input)) {
process.stdin.end(input);
}
else if (typeof input === 'string') {
if (!existsSync(input)) {
reject(`Audio file was not found: ${input}`);
return;
}
}
const stdoutChunks = [];
let stderrOutput = '';
process.stdout.on('data', (data) => {
stdoutChunks.push(data);
});
process.stderr.setEncoding('utf8');
process.stderr.on('data', (data) => {
//log(data)
stderrOutput += data;
});
process.on('error', (e) => {
reject(e);
});
process.on('close', (exitCode) => {
if (exitCode == 0) {
const concatenatedChunks = concatUint8Arrays(stdoutChunks);
resolve(concatenatedChunks);
}
else {
reject(`ffmpeg exited with code ${exitCode}`);
log(stderrOutput);
}
logger.end();
});
});
}
function buildCommandLineArguments(inputFilename, outputOptions) {
outputOptions = { ...outputOptions };
if (!outputOptions.filename) {
outputOptions.filename = '-';
}
const args = [];
args.push(`-i`, `${inputFilename}`);
if (outputOptions.audioOnly) {
args.push(`-map`, `a`);
}
if (outputOptions.codec) {
args.push(`-c:a`, `${outputOptions.codec}`);
}
if (outputOptions.format) {
args.push(`-f:a`, `${outputOptions.format}`);
}
if (outputOptions.sampleRate) {
args.push(`-ar`, `${outputOptions.sampleRate}`);
}
if (outputOptions.sampleFormat) {
args.push(`-sample_fmt`, `${outputOptions.sampleFormat}`);
}
if (outputOptions.channelCount) {
args.push(`-ac`, `${outputOptions.channelCount}`);
}
if (outputOptions.bitrate) {
args.push(`-ab`, `${outputOptions.bitrate}k`);
}
args.push(`-y`);
if (outputOptions.customOptions) {
args.push(...outputOptions.customOptions);
}
args.push(outputOptions.filename);
return args;
}
async function getFFMpegExecutablePath() {
// If a global option set for the path, use it
if (getGlobalOption('ffmpegPath')) {
return getGlobalOption('ffmpegPath');
}
// If an 'ffmpeg' command exist in system path, use it
if (await commandExists('ffmpeg')) {
return 'ffmpeg';
}
// Otherwise, download and use an internal ffmpeg package
const platform = process.platform;
const arch = process.arch;
let packageName;
if (platform === 'win32' && arch === 'x64') {
packageName = 'ffmpeg-6.0-win32-x64';
}
else if (platform === 'win32' && arch === 'ia32') {
packageName = 'ffmpeg-6.0-win32-ia32';
}
else if (platform === 'win32' && arch === 'arm64') {
packageName = 'ffmpeg-6.1-win32-arm64';
}
else if (platform === 'darwin' && arch === 'x64') {
packageName = 'ffmpeg-6.0-darwin-x64';
}
else if (platform === 'darwin' && arch === 'arm64') {
packageName = 'ffmpeg-6.0-darwin-arm64';
}
else if (platform === 'linux' && arch === 'x64') {
packageName = 'ffmpeg-6.0-linux-x64';
}
else if (platform === 'linux' && arch === 'ia32') {
packageName = 'ffmpeg-6.0-linux-ia32';
}
else if (platform === 'linux' && arch === 'arm64') {
packageName = 'ffmpeg-6.0-linux-arm64';
}
else if (platform === 'linux' && arch === 'arm') {
packageName = 'ffmpeg-6.0-linux-arm';
}
else if (platform === 'freebsd' && arch === 'x64') {
packageName = 'ffmpeg-6.0-freebsd-x64';
}
else {
return undefined;
}
const ffmpegPackagePath = await loadPackage(packageName);
let filename = packageName;
if (platform === 'win32') {
filename += '.exe';
}
return joinPath(ffmpegPackagePath, filename);
}
export function getDefaultFFMpegOptionsForSpeech(fileExtension, customBitrate) {
let ffmpegOptions;
if (fileExtension === 'mp3') {
ffmpegOptions = {
format: 'mp3',
codec: 'libmp3lame',
bitrate: 64,
customOptions: []
};
}
else if (fileExtension === 'opus') {
ffmpegOptions = {
format: 'ogg',
codec: 'libopus',
bitrate: 48,
customOptions: []
};
}
else if (fileExtension === 'm4a') {
ffmpegOptions = {
format: 'mp4',
codec: 'aac',
bitrate: 48,
customOptions: ['-profile:a', 'aac_low', '-movflags', 'frag_keyframe+empty_moov']
};
}
else if (fileExtension === 'ogg') {
ffmpegOptions = {
format: 'ogg',
codec: 'libvorbis',
bitrate: 48,
customOptions: []
};
}
else if (fileExtension === 'flac') {
ffmpegOptions = {
format: 'flac',
customOptions: ['-compression_level', '6']
};
}
else if (fileExtension === 'wav') {
ffmpegOptions = {
format: 'wav'
};
}
else {
throw new Error(`Unsupported codec extension: '${fileExtension}'`);
}
if (customBitrate != null) {
ffmpegOptions.bitrate = customBitrate;
}
return ffmpegOptions;
}
//# sourceMappingURL=FFMpegTranscoder.js.map