const fs = require("fs"); const path = require("path"); const { exec } = require("node:child_process"); const { ECDH } = require("crypto"); const SAMPLE = path.join(__dirname, "..", "samples", "genericfemale.wav"); const WORDS_FILE = path.join(__dirname, "..", "words.txt"); const SOURCE_DIR = path.join(__dirname, "..", "source"); const OUT_DIR = path.join(__dirname, "..", "out"); const TTS_MODEL = "tts_models/multilingual/multi-dataset/xtts_v2"; const TTS_COMMAND = `tts --text "$word" --model_name $model --speaker_wav "$sample" --language_idx en --out_path "$outPath"`; const FFMPEG_COMMAND = `ffmpeg -i "$file" -filter:a "volume=2.5, atempo=1.65" "$outPath"`; if (!fs.existsSync(SOURCE_DIR)) { fs.mkdirSync(SOURCE_DIR, { recursive: true }); } if (!fs.existsSync(OUT_DIR)) { fs.mkdirSync(OUT_DIR, { recursive: true }); } const temp = SAMPLE.replace(".wav", "").split("\\"); const sampleName = temp.at(-1); const OUTPUT = path.join(OUT_DIR, sampleName); const SOURCE = path.join(SOURCE_DIR, sampleName); if (!fs.existsSync(OUTPUT)) { fs.mkdirSync(OUTPUT, { recursive: true }); } if (!fs.existsSync(SOURCE)) { fs.mkdirSync(SOURCE, { recursive: true }); } const words = fs .readFileSync(WORDS_FILE) .toString() .split("\n") .filter((w) => !w.includes("#")); function getCookedFiles() { return fs.readdirSync(OUTPUT).map((f) => f.replace(".ogg", "")); } function getSourceFiles() { return fs.readdirSync(SOURCE).map((f) => f.replace(".wav", "")); } function execCommand(command) { return new Promise((resolve, reject) => { exec(command, (error, stdout, stderr) => { if (error) { console.error("Error running TTS: " + stderr); reject(stderr); } resolve(stdout); }); }); } async function runTTS(word) { const ttsComamnd = TTS_COMMAND.replace("$word", word) .replace("$model", TTS_MODEL) .replace("$sample", SAMPLE) .replace("$outPath", `${path.join(SOURCE, word + ".wav")}`); console.log(`Running tts for ${word} using ${ttsComamnd}`); await execCommand(ttsComamnd); } async function doCommit() { const tempLookup = getCookedFiles().reduce((acc, file) => { acc[file] = true; return acc; }, {}); for (const stagedFile of getSourceFiles()) { if (!tempLookup[stagedFile]) { const ffmpegCommand = FFMPEG_COMMAND.replace("$file", path.join(SOURCE, stagedFile + ".wav")).replace( "$outPath", path.join(OUTPUT, stagedFile + ".ogg") ); console.log(`Running ${ffmpegCommand}`); await execCommand(ffmpegCommand); } // Lesson learnt: Do NOT delete the source... // fs.rmSync(path.join(STAGE, stagedFile + ".wav")); } console.log("All files committed"); } async function main() { await doCommit(); const tempLookup = getSourceFiles().reduce((acc, file) => { acc[file] = true; return acc; }, {}); const wordToTTS = []; for (let word of words) { word = word.trim(); if (word) { if (!tempLookup[word]) { wordToTTS.push(word); } else { console.log(`Skipping ${word} since it exists in either stage or out`); } } } for (const word of wordToTTS) { await runTTS(word); } await doCommit(); } main();